source: sasview/park_integration/AbstractFitEngine.py @ bffc2ad

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since bffc2ad was b64fa56, checked in by Gervaise Alina <gervyh@…>, 16 years ago

putting 1 to zero value on dy in fitting

  • Property mode set to 100644
File size: 19.6 KB
RevLine 
[4c718654]1
[39c3263]2import park,numpy,math
[48882d1]3
4class SansParameter(park.Parameter):
5    """
6        SANS model parameters for use in the PARK fitting service.
7        The parameter attribute value is redirected to the underlying
8        parameter value in the SANS model.
9    """
10    def __init__(self, name, model):
[ca6d914]11        """
12            @param name: the name of the model parameter
13            @param model: the sans model to wrap as a park model
14        """
15        self._model, self._name = model,name
16        #set the value for the parameter of the given name
17        self.set(model.getParam(name))
[48882d1]18         
[ca6d914]19    def _getvalue(self):
20        """
21            override the _getvalue of park parameter
22            @return value the parameter associates with self.name
23        """
24        return self._model.getParam(self.name)
[48882d1]25   
[ca6d914]26    def _setvalue(self,value):
27        """
28            override the _setvalue pf park parameter
29            @param value: the value to set on a given parameter
30        """
[48882d1]31        self._model.setParam(self.name, value)
32       
33    value = property(_getvalue,_setvalue)
34   
35    def _getrange(self):
[ca6d914]36        """
37            Override _getrange of park parameter
38            return the range of parameter
39        """
[c79ee796]40        if not  self.name in self._model.getDispParamList():
41            lo,hi = self._model.details[self.name][1:]
42            if lo is None: lo = -numpy.inf
43            if hi is None: hi = numpy.inf
44        else:
45            lo= -numpy.inf
46            hi= numpy.inf
[05f14dd]47        if lo >= hi:
48            raise ValueError,"wrong fit range for parameters"
49       
[48882d1]50        return lo,hi
51   
52    def _setrange(self,r):
[ca6d914]53        """
54            override _setrange of park parameter
55            @param r: the value of the range to set
56        """
[48882d1]57        self._model.details[self.name][1:] = r
58    range = property(_getrange,_setrange)
[a9e04aa]59   
60class Model(park.Model):
[48882d1]61    """
62        PARK wrapper for SANS models.
63    """
[388309d]64    def __init__(self, sans_model, **kw):
[ca6d914]65        """
66            @param sans_model: the sans model to wrap using park interface
67        """
[a9e04aa]68        park.Model.__init__(self, **kw)
[48882d1]69        self.model = sans_model
[ca6d914]70        self.name = sans_model.name
71        #list of parameters names
[48882d1]72        self.sansp = sans_model.getParamList()
[ca6d914]73        #list of park parameter
[48882d1]74        self.parkp = [SansParameter(p,sans_model) for p in self.sansp]
[ca6d914]75        #list of parameterset
[48882d1]76        self.parameterset = park.ParameterSet(sans_model.name,pars=self.parkp)
77        self.pars=[]
[ca6d914]78 
79 
[48882d1]80    def getParams(self,fitparams):
[ca6d914]81        """
82            return a list of value of paramter to fit
83            @param fitparams: list of paramaters name to fit
84        """
[48882d1]85        list=[]
86        self.pars=[]
87        self.pars=fitparams
88        for item in fitparams:
89            for element in self.parkp:
90                 if element.name ==str(item):
91                     list.append(element.value)
92        return list
93   
[ca6d914]94   
[e71440c]95    def setParams(self,paramlist, params):
[ca6d914]96        """
97            Set value for parameters to fit
98            @param params: list of value for parameters to fit
99        """
[e71440c]100        try:
101            for i in range(len(self.parkp)):
102                for j in range(len(paramlist)):
103                    if self.parkp[i].name==paramlist[j]:
104                        self.parkp[i].value = params[j]
105                        self.model.setParam(self.parkp[i].name,params[j])
106        except:
107            raise
[ca6d914]108 
[48882d1]109    def eval(self,x):
[ca6d914]110        """
111            override eval method of park model.
112            @param x: the x value used to compute a function
113        """
[48882d1]114        return self.model.runXY(x)
[388309d]115   
116   
[a9e04aa]117
118
[48882d1]119class Data(object):
120    """ Wrapper class  for SANS data """
121    def __init__(self,x=None,y=None,dy=None,dx=None,sans_data=None):
[ca6d914]122        """
123            Data can be initital with a data (sans plottable)
124            or with vectors.
125        """
[48882d1]126        if  sans_data !=None:
127            self.x= sans_data.x
128            self.y= sans_data.y
129            self.dx= sans_data.dx
130            self.dy= sans_data.dy
131           
132        elif (x!=None and y!=None and dy!=None):
133                self.x=x
134                self.y=y
135                self.dx=dx
136                self.dy=dy
137        else:
138            raise ValueError,\
139            "Data is missing x, y or dy, impossible to compute residuals later on"
140        self.qmin=None
141        self.qmax=None
142       
[ca6d914]143       
[48882d1]144    def setFitRange(self,mini=None,maxi=None):
145        """ to set the fit range"""
146        self.qmin=mini
147        self.qmax=maxi
[ca6d914]148       
149       
[48882d1]150    def getFitRange(self):
[ca6d914]151        """
152            @return the range of data.x to fit
153        """
154        return self.qmin, self.qmax
155     
156     
[48882d1]157    def residuals(self, fn):
158        """ @param fn: function that return model value
159            @return residuals
160        """
161        x,y,dy = [numpy.asarray(v) for v in (self.x,self.y,self.dy)]
162        if self.qmin==None and self.qmax==None: 
[ca6d914]163            fx =numpy.asarray([fn(v) for v in x])
[48882d1]164            return (y - fx)/dy
165        else:
166            idx = (x>=self.qmin) & (x <= self.qmax)
[ca6d914]167            fx = numpy.asarray([fn(item)for item in x[idx ]])
[48882d1]168            return (y[idx] - fx)/dy[idx]
[e71440c]169       
[48882d1]170    def residuals_deriv(self, model, pars=[]):
171        """
172            @return residuals derivatives .
173            @note: in this case just return empty array
174        """
175        return []
[b64fa56]176   
177   
[7d0c1a8]178class FitData1D(object):
179    """ Wrapper class  for SANS data """
[b461b6d7]180    def __init__(self,sans_data1d, smearer=None):
[7d0c1a8]181        """
182            Data can be initital with a data (sans plottable)
183            or with vectors.
[109e60ab]184           
185            self.smearer is an object of class QSmearer or SlitSmearer
186            that will smear the theory data (slit smearing or resolution
187            smearing) when set.
188           
189            The proper way to set the smearing object would be to
190            do the following:
191           
192            from DataLoader.qsmearing import smear_selection
193            fitdata1d = FitData1D(some_data)
194            fitdata1d.smearer = smear_selection(some_data)
195           
196            Note that some_data _HAS_ to be of class DataLoader.data_info.Data1D
197           
198            Setting it back to None will turn smearing off.
199           
[7d0c1a8]200        """
[b461b6d7]201       
202        self.smearer = smearer
203     
[109e60ab]204        # Initialize from Data1D object
[7d0c1a8]205        self.data=sans_data1d
206        self.x= sans_data1d.x
207        self.y= sans_data1d.y
208        self.dx= sans_data1d.dx
209        self.dy= sans_data1d.dy
[109e60ab]210       
211        ## Min Q-value
[20d30e9]212        self.qmin= min (self.data.x)
[109e60ab]213        ## Max Q-value
[20d30e9]214        self.qmax= max (self.data.x)
[7d0c1a8]215       
216       
[20d30e9]217    def setFitRange(self,qmin=None,qmax=None):
[7d0c1a8]218        """ to set the fit range"""
[eef2e0ed]219        if qmin!=None:
220            self.qmin = qmin
221        if qmax !=None:
222            self.qmax = qmax
[7d0c1a8]223       
224       
225    def getFitRange(self):
226        """
227            @return the range of data.x to fit
228        """
229        return self.qmin, self.qmax
230     
231     
232    def residuals(self, fn):
[109e60ab]233        """
234            Compute residuals.
235           
236            If self.smearer has been set, use if to smear
237            the data before computing chi squared.
238           
239            @param fn: function that return model value
240            @return residuals
241        """
[b64fa56]242        x,y = [numpy.asarray(v) for v in (self.x,self.y)]
243        if self.dy ==None or self.dy==[]:
244            dy= numpy.zeros(len(y)) 
245        else:
246            dy= numpy.asarray(self.dy)
247        dy[dy==0]=1
[20d30e9]248        idx = (x>=self.qmin) & (x <= self.qmax)
249 
[109e60ab]250        # Compute theory data f(x)
251        fx = numpy.zeros(len(x))
252        fx[idx] = numpy.asarray([fn(v) for v in x[idx]])
253       
254        # Smear theory data
[aed7c57]255     
[109e60ab]256        if self.smearer is not None:
257            fx = self.smearer(fx)
[20d30e9]258       
[109e60ab]259        # Sanity check
260        if numpy.size(dy) < numpy.size(x):
261            raise RuntimeError, "FitData1D: invalid error array"
262                           
263        return (y[idx] - fx[idx])/dy[idx]
264     
[20d30e9]265 
[7d0c1a8]266       
267    def residuals_deriv(self, model, pars=[]):
268        """
269            @return residuals derivatives .
270            @note: in this case just return empty array
271        """
272        return []
273   
274   
275class FitData2D(object):
276    """ Wrapper class  for SANS data """
277    def __init__(self,sans_data2d):
278        """
279            Data can be initital with a data (sans plottable)
280            or with vectors.
281        """
282        self.data=sans_data2d
[415bc97]283        self.image = sans_data2d.data
284        self.err_image = sans_data2d.err_data
[7d0c1a8]285        self.x_bins= sans_data2d.x_bins
286        self.y_bins= sans_data2d.y_bins
287       
[20d30e9]288        x = max(self.data.xmin, self.data.xmax)
289        y = max(self.data.ymin, self.data.ymax)
290       
291        ## fitting range
292        self.qmin = 0
293        self.qmax = math.sqrt(x*x +y*y)
[7d0c1a8]294       
295       
[20d30e9]296       
297    def setFitRange(self,qmin=None,qmax=None):
[7d0c1a8]298        """ to set the fit range"""
[eef2e0ed]299        if qmin!=None:
300            self.qmin= qmin
301        if qmax!=None:
302            self.qmax= qmax
[20d30e9]303     
[7d0c1a8]304       
305    def getFitRange(self):
306        """
307            @return the range of data.x to fit
308        """
[20d30e9]309        return self.qmin, self.qmax
[7d0c1a8]310     
311     
312    def residuals(self, fn):
313        """ @param fn: function that return model value
314            @return residuals
315        """
316        res=[]
[b64fa56]317        if self.err_image== None or self.err_image ==[]:
318            self.err_image= numpy.zeros(len(self.x_bins),len(self.y_bins))
319        self.err_image[self.err_image==0]=1
[20d30e9]320       
[0e51519]321        for i in range(len(self.y_bins)):
322            for j in range(len(self.x_bins)):
[20d30e9]323                radius = math.pow(self.data.x_bins[i],2)+math.pow(self.data.y_bins[j],2)
324                if self.qmin <= radius and radius <= self.qmax:
325                    res.append( (self.image[j][i]- fn([self.x_bins[i],self.y_bins[j]]))\
326                            /self.err_image[j][i] )
[0e51519]327       
328        return numpy.array(res)
329       
330         
[7d0c1a8]331    def residuals_deriv(self, model, pars=[]):
332        """
333            @return residuals derivatives .
334            @note: in this case just return empty array
335        """
336        return []
[48882d1]337   
338class sansAssembly:
[ca6d914]339    """
340         Sans Assembly class a class wrapper to be call in optimizer.leastsq method
341    """
[e71440c]342    def __init__(self,paramlist,Model=None , Data=None):
[ca6d914]343        """
344            @param Model: the model wrapper fro sans -model
345            @param Data: the data wrapper for sans data
346        """
347        self.model = Model
348        self.data  = Data
[e71440c]349        self.paramlist=paramlist
[ca6d914]350        self.res=[]
[48882d1]351    def chisq(self, params):
352        """
353            Calculates chi^2
354            @param params: list of parameter values
355            @return: chi^2
356        """
357        sum = 0
358        for item in self.res:
359            sum += item*item
[20d30e9]360       
[26cb768]361        return sum/ len(self.res)
[20d30e9]362   
[48882d1]363    def __call__(self,params):
[ca6d914]364        """
365            Compute residuals
366            @param params: value of parameters to fit
367        """
[681f0dc]368        #import thread
[e71440c]369        self.model.setParams(self.paramlist,params)
[48882d1]370        self.res= self.data.residuals(self.model.eval)
[681f0dc]371        #print "residuals",thread.get_ident()
[48882d1]372        return self.res
373   
[4c718654]374class FitEngine:
[ee5b04c]375    def __init__(self):
[ca6d914]376        """
377            Base class for scipy and park fit engine
378        """
379        #List of parameter names to fit
[ee5b04c]380        self.paramList=[]
[ca6d914]381        #Dictionnary of fitArrange element (fit problems)
382        self.fitArrangeDict={}
383       
[4c718654]384    def _concatenateData(self, listdata=[]):
385        """ 
386            _concatenateData method concatenates each fields of all data contains ins listdata.
387            @param listdata: list of data
[ca6d914]388            @return Data: Data is wrapper class for sans plottable. it is created with all parameters
389             of data concatenanted
[4c718654]390            @raise: if listdata is empty  will return None
391            @raise: if data in listdata don't contain dy field ,will create an error
392            during fitting
393        """
[109e60ab]394        #TODO: we have to refactor the way we handle data.
395        # We should move away from plottables and move towards the Data1D objects
396        # defined in DataLoader. Data1D allows data manipulations, which should be
397        # used to concatenate.
398        # In the meantime we should switch off the concatenation.
399        #if len(listdata)>1:
400        #    raise RuntimeError, "FitEngine._concatenateData: Multiple data files is not currently supported"
401        #return listdata[0]
402       
[4c718654]403        if listdata==[]:
404            raise ValueError, " data list missing"
405        else:
406            xtemp=[]
407            ytemp=[]
408            dytemp=[]
[48882d1]409            self.mini=None
410            self.maxi=None
[4c718654]411               
[7d0c1a8]412            for item in listdata:
413                data=item.data
[48882d1]414                mini,maxi=data.getFitRange()
415                if self.mini==None and self.maxi==None:
416                    self.mini=mini
417                    self.maxi=maxi
418                else:
419                    if mini < self.mini:
420                        self.mini=mini
421                    if self.maxi < maxi:
422                        self.maxi=maxi
423                       
424                   
[4c718654]425                for i in range(len(data.x)):
426                    xtemp.append(data.x[i])
427                    ytemp.append(data.y[i])
428                    if data.dy is not None and len(data.dy)==len(data.y):   
429                        dytemp.append(data.dy[i])
430                    else:
[ee5b04c]431                        raise RuntimeError, "Fit._concatenateData: y-errors missing"
[20d30e9]432            data= Data(x=xtemp,y=ytemp,dy=dytemp)
[48882d1]433            data.setFitRange(self.mini, self.maxi)
434            return data
[ca6d914]435       
436       
437    def set_model(self,model,Uid,pars=[]):
438        """
439            set a model on a given uid in the fit engine.
440            @param model: the model to fit
441            @param Uid :is the key of the fitArrange dictionnary where model is saved as a value
442            @param pars: the list of parameters to fit
443            @note : pars must contains only name of existing model's paramaters
444        """
[f44dbc7]445        if len(pars) >0:
[6831a99]446            if model==None:
[f44dbc7]447                raise ValueError, "AbstractFitEngine: Specify parameters to fit"
[6831a99]448            else:
[aed7c57]449                temp=[]
[ca6d914]450                for item in pars:
451                    if item in model.model.getParamList():
[aed7c57]452                        temp.append(item)
[ca6d914]453                        self.paramList.append(item)
454                    else:
455                        raise ValueError,"wrong paramter %s used to set model %s. Choose\
456                            parameter name within %s"%(item, model.model.name,str(model.model.getParamList()))
457                        return
[6831a99]458            #A fitArrange is already created but contains dList only at Uid
[ca6d914]459            if self.fitArrangeDict.has_key(Uid):
460                self.fitArrangeDict[Uid].set_model(model)
[aed7c57]461                self.fitArrangeDict[Uid].pars= pars
[6831a99]462            else:
463            #no fitArrange object has been create with this Uid
[48882d1]464                fitproblem = FitArrange()
[6831a99]465                fitproblem.set_model(model)
[aed7c57]466                fitproblem.pars= pars
[ca6d914]467                self.fitArrangeDict[Uid] = fitproblem
[aed7c57]468               
[d4b0687]469        else:
[6831a99]470            raise ValueError, "park_integration:missing parameters"
[48882d1]471   
[20d30e9]472    def set_data(self,data,Uid,smearer=None,qmin=None,qmax=None):
[d4b0687]473        """ Receives plottable, creates a list of data to fit,set data
474            in a FitArrange object and adds that object in a dictionary
475            with key Uid.
476            @param data: data added
477            @param Uid: unique key corresponding to a fitArrange object with data
[ca6d914]478        """
[f2817bb]479        if data.__class__.__name__=='Data2D':
[f8ce013]480            fitdata=FitData2D(data)
481        else:
[b461b6d7]482            fitdata=FitData1D(data, smearer)
[20d30e9]483       
484        fitdata.setFitRange(qmin=qmin,qmax=qmax)
[d4b0687]485        #A fitArrange is already created but contains model only at Uid
[ca6d914]486        if self.fitArrangeDict.has_key(Uid):
[f8ce013]487            self.fitArrangeDict[Uid].add_data(fitdata)
[d4b0687]488        else:
489        #no fitArrange object has been create with this Uid
490            fitproblem= FitArrange()
[f8ce013]491            fitproblem.add_data(fitdata)
[ca6d914]492            self.fitArrangeDict[Uid]=fitproblem   
[20d30e9]493   
[d4b0687]494    def get_model(self,Uid):
495        """
496            @param Uid: Uid is key in the dictionary containing the model to return
497            @return  a model at this uid or None if no FitArrange element was created
498            with this Uid
499        """
[ca6d914]500        if self.fitArrangeDict.has_key(Uid):
501            return self.fitArrangeDict[Uid].get_model()
[d4b0687]502        else:
503            return None
504   
505    def remove_Fit_Problem(self,Uid):
506        """remove   fitarrange in Uid"""
[ca6d914]507        if self.fitArrangeDict.has_key(Uid):
508            del self.fitArrangeDict[Uid]
[a9e04aa]509           
510    def select_problem_for_fit(self,Uid,value):
511        """
512            select a couple of model and data at the Uid position in dictionary
513            and set in self.selected value to value
514            @param value: the value to allow fitting. can only have the value one or zero
515        """
516        if self.fitArrangeDict.has_key(Uid):
517             self.fitArrangeDict[Uid].set_to_fit( value)
[eef2e0ed]518             
519             
[a9e04aa]520    def get_problem_to_fit(self,Uid):
521        """
522            return the self.selected value of the fit problem of Uid
523           @param Uid: the Uid of the problem
524        """
525        if self.fitArrangeDict.has_key(Uid):
526             self.fitArrangeDict[Uid].get_to_fit()
[4c718654]527   
[d4b0687]528class FitArrange:
529    def __init__(self):
530        """
531            Class FitArrange contains a set of data for a given model
532            to perform the Fit.FitArrange must contain exactly one model
533            and at least one data for the fit to be performed.
534            model: the model selected by the user
535            Ldata: a list of data what the user wants to fit
536           
537        """
538        self.model = None
539        self.dList =[]
[aed7c57]540        self.pars=[]
[a9e04aa]541        #self.selected  is zero when this fit problem is not schedule to fit
542        #self.selected is 1 when schedule to fit
543        self.selected = 0
[d4b0687]544       
545    def set_model(self,model):
546        """
547            set_model save a copy of the model
548            @param model: the model being set
549        """
550        self.model = model
551       
552    def add_data(self,data):
553        """
554            add_data fill a self.dList with data to fit
555            @param data: Data to add in the list 
556        """
557        if not data in self.dList:
558            self.dList.append(data)
559           
560    def get_model(self):
561        """ @return: saved model """
562        return self.model   
563     
564    def get_data(self):
565        """ @return:  list of data dList"""
[7d0c1a8]566        #return self.dList
567        return self.dList[0] 
[d4b0687]568     
569    def remove_data(self,data):
570        """
571            Remove one element from the list
572            @param data: Data to remove from dList
573        """
574        if data in self.dList:
575            self.dList.remove(data)
[a9e04aa]576    def set_to_fit (self, value=0):
577        """
578           set self.selected to 0 or 1  for other values raise an exception
579           @param value: integer between 0 or 1
580        """
581        self.selected= value
582       
583    def get_to_fit(self):
584        """
585            @return self.selected value
586        """
587        return self.selected
[94b44293]588   
[4c718654]589
590
591   
Note: See TracBrowser for help on using the repository browser.