source: sasview/park_integration/AbstractFitEngine.py @ 52ac10b

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 52ac10b was 52ac10b, checked in by Gervaise Alina <gervyh@…>, 15 years ago

try except added to get the smearer , sans.guitools.linemodel removed

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