Changeset ca7a626 in sasview for sansview/perspectives


Ignore:
Timestamp:
Mar 27, 2009 1:51:25 PM (16 years ago)
Author:
Gervaise Alina <gervyh@…>
Branches:
master, ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc, costrafo411, magnetic_scatt, release-4.1.1, release-4.1.2, release-4.2.2, release_4.0.1, ticket-1009, ticket-1094-headless, ticket-1242-2d-resolution, ticket-1243, ticket-1249, ticket885, unittest-saveload
Children:
4e6b5939
Parents:
eef2e0ed
Message:

combine single fit and simultaneous fit

Location:
sansview/perspectives/fitting
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • sansview/perspectives/fitting/fit_thread.py

    rc77d859 rca7a626  
    102102    """Thread performing the fit """ 
    103103     
    104     def __init__(self,parent, fn,pars=None,cpage=None, qmin=None,qmax=None, 
     104    def __init__(self,parent, fn,pars=None,cpage=None, 
    105105                 completefn = None, 
    106106                 updatefn   = None, 
     
    117117        self.pars = pars 
    118118        self.starttime = 0 
    119          
    120         self.qmin = qmin 
    121         self.qmax = qmax 
    122119        
    123120        self.done= False 
     
    160157                          pars = self.pars, 
    161158                          cpage= self.cpage, 
    162                           qmin = self.qmin, 
    163                           qmax = self.qmax, 
    164                            
    165159                          elapsed=elapsed ) 
     160             
    166161        except KeyboardInterrupt: 
    167162            # Thread was interrupted, just proceed and re-raise. 
  • sansview/perspectives/fitting/fitpage.py

    r1ae3fe1 rca7a626  
    329329        self.qmax_x =float( self.qmax.GetValue()) 
    330330         
    331         self.manager.schedule_for_fit( value=1,fitproblem =None)  
    332         
     331        self.manager.schedule_for_fit( value=1,page=self,fitproblem =None)  
     332        self.manager.set_fit_range(page= self,qmin= self.qmin_x, qmax= self.qmax_x) 
    333333        #single fit  
    334         self.manager.on_single_fit(qmin=self.qmin_x,qmax=self.qmax_x) 
     334        #self.manager.on_single_fit(qmin=self.qmin_x,qmax=self.qmax_x) 
     335        self.manager.onFit() 
    335336             
    336337        self.sizer5.Layout() 
  • sansview/perspectives/fitting/fitting.py

    r0a518e4c rca7a626  
    235235        return data 
    236236 
    237  
    238     
     237    def set_fit_range(self, page, qmin, qmax): 
     238        """ 
     239            Set the fitting range of a given page 
     240        """ 
     241        if page in self.page_finder.iterkeys(): 
     242            fitproblem= self.page_finder[page] 
     243            fitproblem.set_range(qmin= qmin, qmax= qmax) 
    239244                     
    240     def schedule_for_fit(self,value=0,fitproblem =None):   
     245    def schedule_for_fit(self,value=0,page=None,fitproblem =None):   
    241246        """ 
    242247            Set the fit problem field to 0 or 1 to schedule that problem to fit. 
     
    249254            fitproblem.schedule_tofit(value) 
    250255        else: 
    251             current_pg=self.fit_panel.get_current_page()  
    252             for page, val in self.page_finder.iteritems(): 
    253                 if page ==current_pg : 
    254                     val.schedule_tofit(value) 
    255                     break 
     256            if page in self.page_finder.iterkeys(): 
     257                fitproblem= self.page_finder[page] 
     258                fitproblem.schedule_tofit(value) 
     259           
    256260                       
    257261                     
     
    303307                is cancelled" , type="stop")) 
    304308             
     309     
    305310       
    306     def on_single_fit(self,id=0,qmin=None, qmax=None): 
    307         """  
    308             perform fit for the  current page  and return chisqr,out and cov 
    309             @param engineName: type of fit to be performed 
    310             @param id: unique id corresponding to a fit problem(model, set of data) 
    311             @param model: model to fit 
    312              
    313         """ 
    314         #set an engine to perform fit 
    315         from sans.fit.Fitting import Fit 
    316         self.fitter = Fit(self._fit_engine) 
    317         #Setting an id to store model and data in fit engine 
    318         self.fit_id = id 
    319         
    320         page_fitted = None 
    321          
    322         #Get information (model , data) related to the page on  
    323         #with the fit will be perform 
    324         current_pg= self.fit_panel.get_current_page()  
    325         simul_pg= self.sim_page 
    326         pars=[]    
    327         ## Check that the current page is different from self.sim_page 
    328         if current_pg != simul_pg: 
    329             value = self.page_finder[current_pg] 
    330             metadata =  value.get_fit_data() 
    331             model = value.get_model() 
    332             smearer = value.get_smearer() 
    333             
    334             #Create list of parameters for fitting used 
    335             templist=[] 
    336             try: 
    337                 ## get the list of parameter names to fit 
    338                 templist = current_pg.get_param_list() 
    339              
    340                 for element in templist: 
    341                     pars.append(str(element[1])) 
    342      
    343                 pars.sort() 
    344                 #Do the single fit 
    345                 self.fitter.set_model(Model(model), self.fit_id, pars) 
    346                 dy=[] 
    347                 x=[] 
    348                 y=[] 
    349                 ## checking the validity of error 
    350                 if metadata.__class__ in  ["Data1D","Theory1D"]: 
    351                     for i in range(len(metadata.dy)): 
    352                         if metadata.dy[i] !=0: 
    353                             dy.append(metadata.dy[i]) 
    354                             x.append(metadata.x[i]) 
    355                             y.append(metadata.y[i]) 
    356                     if len(dy)>0:         
    357                         metadata.dy=numpy.zeros(len(dy)) 
    358                         metadata.dy=dy 
    359                         metadata.y=numpy.zeros(len(y)) 
    360                         metadata.y=y 
    361                         metadata.x=numpy.zeros(len(x)) 
    362                         metadata.x=x 
    363                 
    364                 self.fitter.set_data(data=metadata,Uid=self.fit_id, 
    365                                      smearer=smearer,qmin= qmin,qmax=qmax ) 
    366                  
    367                 self.fitter.select_problem_for_fit(Uid= self.fit_id, 
    368                                                    value= value.get_scheduled()) 
    369                 page_fitted=current_pg 
    370                 
    371             except: 
    372                 msg= "Single Fit error: %s" % sys.exc_value 
    373                 wx.PostEvent(self.parent, StatusEvent(status= msg )) 
    374                 return 
    375             # make sure to keep an alphabetic order  
    376             #of parameter names in the list       
    377             try: 
    378                 ## If a thread is already started, stop it 
    379                 if self.calc_fit!= None and self.calc_fit.isrunning(): 
    380                     self.calc_fit.stop() 
    381                          
    382                 self.calc_fit=FitThread(parent =self.parent, 
    383                                             fn= self.fitter, 
    384                                             pars= pars, 
    385                                             cpage= page_fitted, 
    386                                            qmin=qmin, 
    387                                            qmax=qmax, 
    388                                            
    389                                            completefn=self._single_fit_completed, 
    390                                            updatefn=None) 
    391                 self.calc_fit.queue() 
    392                 self.calc_fit.ready(2.5) 
     311    def set_smearer(self,smearer, qmin=None, qmax=None): 
     312        """ 
     313            Get a smear object and store it to a fit problem 
     314            @param smearer: smear object to allow smearing data 
     315        """    
     316        current_pg=self.fit_panel.get_current_page() 
     317        self.page_finder[current_pg].set_smearer(smearer) 
     318        ## draw model 1D with smeared data 
     319        data =  self.page_finder[current_pg].get_plotted_data() 
     320        model = self.page_finder[current_pg].get_model() 
     321        ## if user has already selected a model to plot 
     322        ## redraw the model with data smeared 
     323         
     324        smearer =self.page_finder[current_pg].get_smearer() 
     325        if smearer != None: 
     326            self.draw_model( model=model, data= data, smearer= smearer, 
     327                qmin= qmin, qmax= qmax) 
     328 
     329     
     330     
     331    def draw_model(self, model, data= None,smearer= None, 
     332                   enable1D= True, enable2D= False, 
     333                   qmin= DEFAULT_QMIN, qmax= DEFAULT_QMAX, qstep= DEFAULT_NPTS): 
     334        """ 
     335             Draw model. 
     336             @param model: the model to draw 
     337             @param name: the name of the model to draw 
     338             @param data: the data on which the model is based to be drawn 
     339             @param description: model's description 
     340             @param enable1D: if true enable drawing model 1D 
     341             @param enable2D: if true enable drawing model 2D 
     342             @param qmin:  Range's minimum value to draw model 
     343             @param qmax:  Range's maximum value to draw model 
     344             @param qstep: number of step to divide the x and y-axis 
    393345              
    394             except: 
    395                 wx.PostEvent(self.parent, StatusEvent(status="Single Fit error: %s" % sys.exc_value)) 
    396                 return 
    397           
    398     def on_simul_fit(self, id=0,qmin=None,qmax=None): 
    399         """  
    400             perform fit for all the pages selected on simpage and return chisqr,out and cov 
    401             @param engineName: type of fit to be performed 
    402             @param id: unique id corresponding to a fit problem(model, set of data) 
    403              in park_integration 
    404             @param model: model to fit 
    405              
    406         """ 
    407         ##Setting an id to store model and data 
    408         self.fit_id= id 
    409          
     346        """ 
     347        ## draw model 1D with no loaded data 
     348        self._draw_model1D( model= model, data= data,enable1D=enable1D, smearer= smearer, 
     349                           qmin= qmin, qmax= qmax, qstep= qstep ) 
     350        ## draw model 2D with no initial data 
     351        self._draw_model2D(model=model, 
     352                           data = data, 
     353                           enable2D= enable2D, 
     354                           qmin=qmin, 
     355                           qmax=qmax, 
     356                           qstep=qstep) 
     357         
     358    def onFit(self): 
     359        """ 
     360            perform fit  
     361        """ 
    410362        ##  count the number of fitproblem schedule to fit  
    411363        fitproblem_count= 0 
     
    420372        self.fitter= Fit(self._fit_engine) 
    421373         
    422          
     374        if self._fit_engine=="park": 
     375            engineType="Simutaneous Fit" 
     376        else: 
     377            engineType="Single Fit" 
     378             
     379        fproblemId = 0 
     380        current_pg=None 
    423381        for page, value in self.page_finder.iteritems(): 
    424382            try: 
    425383                if value.get_scheduled()==1: 
    426                     metadata = value.get_fit_data() 
    427                     model = value.get_model() 
    428                     smearer = value.get_smearer() 
    429                     qmin , qmax = value.get_range() 
    430                     ## store page 
    431                     cpage= page 
    432384                    #Get list of parameters name to fit 
    433385                    pars = [] 
     
    435387                    templist = page.get_param_list() 
    436388                    for element in templist: 
    437                         try: 
    438                             name = str(element[0].GetLabelText()) 
    439                             pars.append(name) 
    440                         except: 
    441                             wx.PostEvent(self.parent, StatusEvent(status="Simultaneous Fit error: %s" % sys.exc_value)) 
    442                             return 
    443                     ## create a park model and reset parameter value if constraint 
    444                     ## is given 
    445                     new_model = Model(model) 
    446                     param = value.get_model_param() 
    447                     if len(param)>0: 
    448                         for item in param: 
    449                             param_value = item[1] 
    450                             param_name = item[0] 
    451                             ## check if constraint 
    452                             if param_value !=None and param_name != None: 
    453                                 new_model.parameterset[ param_name].set( param_value ) 
    454                     self.fitter.set_model(model= new_model, Uid=self.fit_id, pars=pars)  
    455                     ## check that non -zero value are send as dy in the fit engine 
    456                     dy=[] 
    457                     x=[] 
    458                     y=[] 
    459                     if metadata.__class__ in  ["Data1D","Theory1D"]: 
    460                         for i in range(len(metadata.dy)): 
    461                             if metadata.dy[i] !=0: 
    462                                 dy.append(metadata.dy[i]) 
    463                                 x.append(metadata.x[i]) 
    464                                 y.append(metadata.y[i]) 
    465                             if len(dy)>0:         
    466                                 metadata.dy=numpy.zeros(len(dy)) 
    467                                 metadata.dy=dy 
    468                                 metadata.y=numpy.zeros(len(y)) 
    469                                 metadata.y=y 
    470                                 metadata.x=numpy.zeros(len(x)) 
    471                                 metadata.x=x 
    472                                
    473                     self.fitter.set_data( data= metadata, smearer=smearer, 
    474                                          Uid=self.fit_id, qmin=qmin, qmax=qmax) 
    475                     self.fitter.select_problem_for_fit(Uid= self.fit_id, 
    476                                                        value= value.get_scheduled()) 
    477                     self.fit_id += 1  
    478                     value.clear_model_param() 
    479                     
     389                        name = str(element[0].GetLabelText()) 
     390                        pars.append(name) 
     391                    #Set Engine  (model , data) related to the page on  
     392                    self._fit_helper( current_pg=page, value=value,pars=pars, 
     393                                      id=fproblemId, title= engineType )  
     394                    fproblemId += 1  
     395                    current_pg= page 
    480396            except: 
    481                 msg= "Simultaneous Fit error: %s" % sys.exc_value 
     397                msg= "%s error: %s" % (engineType,sys.exc_value) 
    482398                wx.PostEvent(self.parent, StatusEvent(status= msg )) 
    483399                return  
    484              
    485400        #Do the simultaneous fit 
    486401        try: 
     
    488403            if self.calc_fit!= None and self.calc_fit.isrunning(): 
    489404                self.calc_fit.stop() 
    490                         ## perform single fit 
    491             if  fitproblem_count==1: 
     405            ## perform single fit 
     406            if self._fit_engine=="scipy": 
     407                qmin, qmax= current_pg.get_range() 
    492408                self.calc_fit=FitThread(parent =self.parent, 
    493409                                        fn= self.fitter, 
    494                                        qmin=qmin, 
    495                                        qmax=qmax, 
    496                                        cpage=cpage, 
     410                                       cpage=current_pg, 
    497411                                       pars= pars, 
    498                                        completefn= self._simul_fit_completed, 
     412                                       completefn= self._single_fit_completed, 
    499413                                       updatefn=None) 
    500414                       
     
    503417                self.calc_fit=FitThread(parent =self.parent, 
    504418                                        fn= self.fitter, 
    505                                        qmin=qmin, 
    506                                        qmax=qmax, 
    507419                                       completefn= self._simul_fit_completed, 
    508420                                       updatefn=None) 
     
    511423             
    512424        except: 
    513             msg= "Simultaneous Fit error: %s" % sys.exc_value 
     425            msg= "%s error: %s" % (engineType,sys.exc_value) 
    514426            wx.PostEvent(self.parent, StatusEvent(status= msg )) 
    515             return 
    516        
    517     def set_smearer(self,smearer, qmin=None, qmax=None): 
    518         """ 
    519             Get a smear object and store it to a fit problem 
    520             @param smearer: smear object to allow smearing data 
    521         """    
    522         current_pg=self.fit_panel.get_current_page() 
    523         self.page_finder[current_pg].set_smearer(smearer) 
    524         ## draw model 1D with smeared data 
    525         data =  self.page_finder[current_pg].get_plotted_data() 
    526         model = self.page_finder[current_pg].get_model() 
    527         ## if user has already selected a model to plot 
    528         ## redraw the model with data smeared 
    529          
    530         smearer =self.page_finder[current_pg].get_smearer() 
    531         if smearer != None: 
    532             self.draw_model( model=model, data= data, smearer= smearer, 
    533                 qmin= qmin, qmax= qmax) 
    534  
    535      
    536      
    537     def draw_model(self, model, data= None,smearer= None, 
    538                    enable1D= True, enable2D= False, 
    539                    qmin= DEFAULT_QMIN, qmax= DEFAULT_QMAX, qstep= DEFAULT_NPTS): 
    540         """ 
    541              Draw model. 
    542              @param model: the model to draw 
    543              @param name: the name of the model to draw 
    544              @param data: the data on which the model is based to be drawn 
    545              @param description: model's description 
    546              @param enable1D: if true enable drawing model 1D 
    547              @param enable2D: if true enable drawing model 2D 
    548              @param qmin:  Range's minimum value to draw model 
    549              @param qmax:  Range's maximum value to draw model 
    550              @param qstep: number of step to divide the x and y-axis 
    551               
    552         """ 
    553         ## draw model 1D with no loaded data 
    554         self._draw_model1D( model= model, data= data,enable1D=enable1D, smearer= smearer, 
    555                            qmin= qmin, qmax= qmax, qstep= qstep ) 
    556         ## draw model 2D with no initial data 
    557         self._draw_model2D(model=model, 
    558                            data = data, 
    559                            enable2D= enable2D, 
    560                            qmin=qmin, 
    561                            qmax=qmax, 
    562                            qstep=qstep) 
    563            
    564     def _fit_helper(self,current_pg, id ): 
     427            return  
     428               
     429        
     430         
     431         
     432         
     433         
     434    def _fit_helper(self,current_pg,pars,value, id, title="Single Fit " ): 
    565435        """ 
    566436            helper for fitting 
    567437        """ 
    568         #set an engine to perform fit 
    569         from sans.fit.Fitting import Fit 
    570         self.fitter = Fit(self._fit_engine) 
    571         #Setting an id to store model and data in fit engine 
    572         self.fit_id = id 
    573         page_fitted = None 
    574         pars=[]    
    575         value = self.page_finder[current_pg] 
    576         metadata =  value.get_fit_data() 
     438        metadata = value.get_fit_data() 
    577439        model = value.get_model() 
    578440        smearer = value.get_smearer() 
    579         
     441        qmin , qmax = value.get_range() 
     442        self.fit_id =id 
    580443        #Create list of parameters for fitting used 
    581444        templist=[] 
     445        pars=pars 
    582446        try: 
    583             ## get the list of parameter names to fit 
    584             templist = current_pg.get_param_list() 
    585          
    586             for element in templist: 
    587                 pars.append(str(element[1])) 
    588  
    589             pars.sort() 
    590447            ## create a park model and reset parameter value if constraint 
    591448            ## is given 
     
    599456                    if param_value !=None and param_name != None: 
    600457                        new_model.parameterset[ param_name].set( param_value ) 
    601          
     458             
     459             
    602460            #Do the single fit 
    603461            self.fitter.set_model(new_model, self.fit_id, pars) 
     
    622480            self.fitter.set_data(data=metadata,Uid=self.fit_id, 
    623481                                 smearer=smearer,qmin= qmin,qmax=qmax ) 
    624              
     482            
    625483            self.fitter.select_problem_for_fit(Uid= self.fit_id, 
    626484                                               value= value.get_scheduled()) 
    627             page_fitted=current_pg 
    628485            
    629486        except: 
    630             msg= "Single Fit error: %s" % sys.exc_value 
     487            msg= title +" error: %s" % sys.exc_value 
    631488            wx.PostEvent(self.parent, StatusEvent(status= msg )) 
    632489            return 
     
    679536                    return 
    680537     
    681     def _single_fit_completed(self,result,pars,cpage,qmin,qmax,elapsed=None): 
     538    def _single_fit_completed(self,result,pars,cpage, elapsed=None): 
    682539        """ 
    683540            Display fit result on one page of the notebook. 
     
    689546           
    690547        """ 
    691         wx.PostEvent(self.parent, StatusEvent(status="Single fit \ 
    692         complete! " , type="stop")) 
     548        #wx.PostEvent(self.parent, StatusEvent(status="Single fit \ 
     549        #complete! " , type="stop")) 
    693550        try: 
    694551            for page, value in self.page_finder.iteritems(): 
     
    708565            metadata =  self.page_finder[cpage].get_fit_data() 
    709566            model = self.page_finder[cpage].get_model() 
    710             
     567            qmin, qmax= self.page_finder[cpage].get_range() 
    711568            #Replot models 
    712569            msg= "Single Fit completed. plotting... %s:"%model.name 
     
    720577        
    721578        
    722     def _simul_fit_completed(self,result,qmin,qmax, elapsed=None,pars=None,cpage=None): 
     579    def _simul_fit_completed(self,result,pars=None,cpage=None, elapsed=None): 
    723580        """ 
    724581            Parameter estimation completed,  
     
    727584            @param elapsed: computation time 
    728585        """ 
    729         if cpage!=None: 
    730             self._single_fit_completed(result=result, pars=pars, cpage=cpage, 
    731                                        qmin=qmin, qmax=qmax) 
    732             return 
    733         else: 
    734             wx.PostEvent(self.parent, StatusEvent(status="Simultaneous fit \ 
    735             complete ", type="stop")) 
    736             
    737             ## fit more than 1 model at the same time  
    738             try: 
    739                 for page, value in self.page_finder.iteritems(): 
    740                     if value.get_scheduled()==1: 
    741                         model = value.get_model() 
    742                         metadata =  value.get_plotted_data() 
    743                         small_out = [] 
    744                         small_cov = [] 
    745                         i = 0 
    746                         #Separate result in to data corresponding to each page 
    747                         for p in result.parameters: 
    748                             model_name,param_name = self.split_string(p.name)   
    749                             if model.name == model_name: 
    750                                 p_name= model.name+"."+param_name 
    751                                 if p.name == p_name: 
    752                                     small_out.append(p.value ) 
    753                                     model.setParam(param_name,p.value)  
    754                                     if p.stderr==None: 
    755                                         p.stderr=numpy.nan 
    756                                         small_cov.append(p.stderr) 
    757                                         
    758                                     else: 
    759                                         small_cov.append(p.stderr) 
     586        wx.PostEvent(self.parent, StatusEvent(status="Simultaneous fit \ 
     587        complete ", type="stop")) 
     588        
     589        ## fit more than 1 model at the same time  
     590        try: 
     591            for page, value in self.page_finder.iteritems(): 
     592                if value.get_scheduled()==1: 
     593                    model = value.get_model() 
     594                    metadata =  value.get_plotted_data() 
     595                    small_out = [] 
     596                    small_cov = [] 
     597                    i = 0 
     598                    #Separate result in to data corresponding to each page 
     599                    for p in result.parameters: 
     600                        model_name,param_name = self.split_string(p.name)   
     601                        if model.name == model_name: 
     602                            p_name= model.name+"."+param_name 
     603                            if p.name == p_name: 
     604                                small_out.append(p.value ) 
     605                                model.setParam(param_name,p.value)  
     606                                if p.stderr==None: 
     607                                    p.stderr=numpy.nan 
     608                                    small_cov.append(p.stderr) 
     609                                    
    760610                                else: 
    761                                     value= model.getParam(param_name) 
    762                                     small_out.append(value ) 
    763                                     small_cov.append(numpy.nan) 
    764                                  
    765                         # Display result on each page  
    766                         page.onsetValues(result.fitness, small_out,small_cov) 
    767                         #Replot models 
    768                         msg= "Simultaneous Fit completed. plotting... %s:"%model.name 
    769                         wx.PostEvent(self.parent, StatusEvent(status="%s " % msg)) 
    770                         self.draw_model( model=model, data= metadata,qmin= qmin, qmax= qmax) 
    771                          
    772             except: 
    773                  msg= "Simultaneous Fit completed but Following error occurred: " 
    774                  wx.PostEvent(self.parent, StatusEvent(status="%s%s" %(msg,sys.exc_value))) 
    775                  return  
     611                                    small_cov.append(p.stderr) 
     612                            else: 
     613                                value= model.getParam(param_name) 
     614                                small_out.append(value ) 
     615                                small_cov.append(numpy.nan) 
     616                    # Display result on each page  
     617                    page.onsetValues(result.fitness, small_out,small_cov) 
     618                    #Replot models 
     619                    msg= "Simultaneous Fit completed. plotting... %s:"%model.name 
     620                    wx.PostEvent(self.parent, StatusEvent(status="%s " % msg)) 
     621                    qmin, qmax= page.get_range() 
     622                    self.draw_model( model=model, data= metadata,qmin= qmin, qmax= qmax) 
     623                     
     624        except: 
     625             msg= "Simultaneous Fit completed but Following error occurred: " 
     626             wx.PostEvent(self.parent, StatusEvent(status="%s%s" %(msg,sys.exc_value))) 
     627             return  
    776628              
    777629                            
  • sansview/perspectives/fitting/simfitpage.py

    r1f57dfd rca7a626  
    125125        ## model was actually selected from this page to be fit 
    126126        if len(self.model_toFit) >= 1 : 
    127             self.manager.on_simul_fit() 
     127            self.manager.onFit() 
    128128        else: 
    129129            msg= "Select at least one model to fit " 
Note: See TracChangeset for help on using the changeset viewer.