source: sasview/park_integration/src/sans/fit/ParkFitting.py @ ee53c72

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 ee53c72 was ee53c72, checked in by Jae Cho <jhjcho@…>, 13 years ago

why we did sort the params? This sort made the param values wrong place in the results.

  • Property mode set to 100644
File size: 15.3 KB
Line 
1
2
3
4"""
5ParkFitting module contains SansParameter,Model,Data
6FitArrange, ParkFit,Parameter classes.All listed classes work together
7to perform a simple fit with park optimizer.
8"""
9#import time
10import numpy
11#import park
12from park import fit
13from park import fitresult
14from  park.fitresult import FitParameter
15import park.simplex
16from park.assembly import Assembly
17from park.assembly import Part
18from park.fitmc import FitSimplex
19import park.fitmc
20from park.fitmc import FitMC
21from park.fit import Fitter
22from park.formatnum import format_uncertainty
23#from Loader import Load
24from sans.fit.AbstractFitEngine import FitEngine
25 
26class SansFitResult(fitresult.FitResult):
27    def __init__(self, *args, **kwrds):
28        fitresult.FitResult.__init__(self, *args, **kwrds)
29        self.theory = None
30        self.inputs = []
31       
32class SansFitSimplex(FitSimplex):
33    """
34    Local minimizer using Nelder-Mead simplex algorithm.
35
36    Simplex is robust and derivative free, though not very efficient.
37
38    This class wraps the bounds contrained Nelder-Mead simplex
39    implementation for `park.simplex.simplex`.
40    """
41    radius = 0.05
42    """Size of the initial simplex; this is a portion between 0 and 1"""
43    xtol = 1
44    #xtol = 1e-4
45    """Stop when simplex vertices are within xtol of each other"""
46    ftol = 5e-5
47    """Stop when vertex values are within ftol of each other"""
48    maxiter = None
49    """Maximum number of iterations before fit terminates"""
50    def fit(self, fitness, x0):
51        """Run the fit"""
52        self.cancel = False
53        pars = fitness.fit_parameters()
54        bounds = numpy.array([p.range for p in pars]).T
55        result = park.simplex.simplex(fitness, x0, bounds=bounds,
56                                 radius=self.radius, xtol=self.xtol,
57                                 ftol=self.ftol, maxiter=self.maxiter,
58                                 abort_test=self._iscancelled)
59        #print "calls:",result.calls
60        #print "simplex returned",result.x,result.fx
61        # Need to make our own copy of the fit results so that the
62        # values don't get stomped on by the next fit iteration.
63        fitpars = [SansFitParameter(pars[i].name,pars[i].range,v, pars[i].model, pars[i].data)
64                   for i,v in enumerate(result.x)]
65        res = SansFitResult(fitpars, result.calls, result.fx)
66        res.inputs = [(pars[i].model, pars[i].data) for i,v in enumerate(result.x)]
67        # Compute the parameter uncertainties from the jacobian
68        res.calc_cov(fitness)
69        return res
70     
71class SansFitter(Fitter):
72    """
73    """
74    def fit(self, fitness, handler):
75        """
76        Global optimizer.
77
78        This function should return immediately
79        """
80        # Determine initial value and bounds
81        pars = fitness.fit_parameters()
82        bounds = numpy.array([p.range for p in pars]).T
83        x0 = [p.value for p in pars]
84
85        # Initialize the monitor and results.
86        # Need to make our own copy of the fit results so that the
87        # values don't get stomped on by the next fit iteration.
88        handler.done = False
89        self.handler = handler
90        fitpars = [SansFitParameter(pars[i].name, pars[i].range, v,
91                                     pars[i].model, pars[i].data)
92                   for i,v in enumerate(x0)]
93        handler.result = fitresult.FitResult(fitpars, 0, numpy.NaN)
94
95        # Run the fit (fit should perform _progress and _improvement updates)
96        # This function may return before the fit is complete.
97        self._fit(fitness, x0, bounds)
98       
99class SansFitMC(SansFitter):
100    """
101    Monte Carlo optimizer.
102
103    This implements `park.fit.Fitter`.
104    """
105    localfit = SansFitSimplex()
106    start_points = 10
107
108    def _fit(self, objective, x0, bounds):
109        """
110        Run a monte carlo fit.
111
112        This procedure maps a local optimizer across a set of initial points.
113        """
114        park.fitmc.fitmc(objective, x0, bounds, self.localfit,
115              self.start_points, self.handler)
116
117       
118class SansPart(Part):
119    """
120    Part of a fitting assembly.  Part holds the model itself and
121    associated data.  The part can be initialized with a fitness
122    object or with a pair (model,data) for the default fitness function.
123
124    fitness (Fitness)
125        object implementing the `park.assembly.Fitness` interface.  In
126        particular, fitness should provide a parameterset attribute
127        containing a ParameterSet and a residuals method returning a vector
128        of residuals.
129    weight (dimensionless)
130        weight for the model.  See comments in assembly.py for details.
131    isfitted (boolean)
132        True if the model residuals should be included in the fit.
133        The model parameters may still be used in parameter
134        expressions, but there will be no comparison to the data.
135    residuals (vector)
136        Residuals for the model if they have been calculated, or None
137    degrees_of_freedom
138        Number of residuals minus number of fitted parameters.
139        Degrees of freedom for individual models does not make
140        sense in the presence of expressions combining models,
141        particularly in the case where a model has many parameters
142        but no data or many computed parameters.  The degrees of
143        freedom for the model is set to be at least one.
144    chisq
145        sum(residuals**2); use chisq/degrees_of_freedom to
146        get the reduced chisq value.
147
148        Get/set the weight on the given model.
149
150        assembly.weight(3) returns the weight on model 3 (0-origin)
151        assembly.weight(3,0.5) sets the weight on model 3 (0-origin)
152    """
153
154    def __init__(self, fitness, weight=1., isfitted=True):
155        Part.__init__(self, fitness=fitness, weight=weight,
156                       isfitted=isfitted)
157       
158        self.model, self.data = fitness[0], fitness[1]
159
160class SansFitParameter(FitParameter):
161    """
162    Fit result for an individual parameter.
163    """
164    def __init__(self, name, range, value, model, data):
165        FitParameter.__init__(self, name, range, value)
166        self.model = model
167        self.data = data
168       
169    def summarize(self):
170        """
171        Return parameter range string.
172
173        E.g.,  "       Gold .....|.... 5.2043 in [2,7]"
174        """
175        bar = ['.']*10
176        lo,hi = self.range
177        if numpy.isfinite(lo)and numpy.isfinite(hi):
178            portion = (self.value-lo)/(hi-lo)
179            if portion < 0: portion = 0.
180            elif portion >= 1: portion = 0.99999999
181            barpos = int(math.floor(portion*len(bar)))
182            bar[barpos] = '|'
183        bar = "".join(bar)
184        lostr = "[%g"%lo if numpy.isfinite(lo) else "(-inf"
185        histr = "%g]"%hi if numpy.isfinite(hi) else "inf)"
186        valstr = format_uncertainty(self.value, self.stderr)
187        model_name = str(None)
188        if self.model is not None:
189            model_name = self.model.name
190        data_name = str(None)
191        if self.data is not None:
192            data_name = self.data.name
193           
194        return "%25s %s %s in %s,%s, %s, %s"  % (self.name,bar,valstr,lostr,histr, 
195                                                 model_name, data_name)
196    def __repr__(self):
197        #return "FitParameter('%s')"%self.name
198        return str(self.__class__)
199   
200class MyAssembly(Assembly):
201    def __init__(self, models, curr_thread=None):
202        Assembly.__init__(self, models)
203        self.curr_thread = curr_thread
204        self.chisq = None
205        self._cancel = False
206        self.theory = None
207       
208    def fit_parameters(self):
209        """
210        Return an alphabetical list of the fitting parameters.
211
212        This function is called once at the beginning of a fit,
213        and serves as a convenient place to precalculate what
214        can be precalculated such as the set of fitting parameters
215        and the parameter expressions evaluator.
216        """
217        self.parameterset.setprefix()
218        self._fitparameters = self.parameterset.fitted
219        self._restraints = self.parameterset.restrained
220        pars = self.parameterset.flatten()
221        context = self.parameterset.gather_context()
222        self._fitexpression = park.expression.build_eval(pars,context)
223        #print "constraints",self._fitexpression.__doc__
224
225        #self._fitparameters.sort(lambda a,b: cmp(a.path,b.path))
226        # Convert to fitparameter a object
227       
228        fitpars = [SansFitParameter(p.path,p.range,p.value, p.model, p.data)
229                   for p in self._fitparameters]
230        #print "fitpars", fitpars
231        return fitpars
232   
233    def all_results(self, result):
234        """
235        Extend result from the fit with the calculated parameters.
236        """
237        calcpars = [SansFitParameter(p.path,p.range,p.value, p.model, p.data)
238                    for p in self.parameterset.computed]
239        result.parameters += calcpars
240        result.theory = self.theory
241
242    def eval(self):
243        """
244        Recalculate the theory functions, and from them, the
245        residuals and chisq.
246
247        :note: Call this after the parameters have been updated.
248        """
249        # Handle abort from a separate thread.
250        self._cancel = False
251        if self.curr_thread != None:
252            try:
253                self.curr_thread.isquit()
254            except:
255                self._cancel = True
256
257        # Evaluate the computed parameters
258        try:
259            self._fitexpression()
260        except NameError:
261            pass
262
263        # Check that the resulting parameters are in a feasible region.
264        if not self.isfeasible(): return numpy.inf
265
266        resid = []
267        k = len(self._fitparameters)
268        for m in self.parts:
269            # In order to support abort, need to be able to propagate an
270            # external abort signal from self.abort() into an abort signal
271            # for the particular model.  Can't see a way to do this which
272            # doesn't involve setting a state variable.
273            self._current_model = m
274            if self._cancel: return numpy.inf
275            if m.isfitted and m.weight != 0:
276                m.residuals, self.theory = m.fitness.residuals()
277                N = len(m.residuals)
278                m.degrees_of_freedom = N-k if N>k else 1
279                m.chisq = numpy.sum(m.residuals**2)
280                resid.append(m.weight*m.residuals)
281        self.residuals = numpy.hstack(resid)
282        N = len(self.residuals)
283        self.degrees_of_freedom = N-k if N>k else 1
284        self.chisq = numpy.sum(self.residuals**2)
285        return self.chisq
286   
287class ParkFit(FitEngine):
288    """
289    ParkFit performs the Fit.This class can be used as follow:
290    #Do the fit Park
291    create an engine: engine = ParkFit()
292    Use data must be of type plottable
293    Use a sans model
294   
295    Add data with a dictionnary of FitArrangeList where Uid is a key and data
296    is saved in FitArrange object.
297    engine.set_data(data,Uid)
298   
299    Set model parameter "M1"= model.name add {model.parameter.name:value}.
300   
301    :note: Set_param() if used must always preceded set_model()
302         for the fit to be performed.
303    engine.set_param( model,"M1", {'A':2,'B':4})
304   
305    Add model with a dictionnary of FitArrangeList{} where Uid is a key
306    and model
307    is save in FitArrange object.
308    engine.set_model(model,Uid)
309   
310    engine.fit return chisqr,[model.parameter 1,2,..],[[err1....][..err2...]]
311    chisqr1, out1, cov1=engine.fit({model.parameter.name:value},qmin,qmax)
312   
313    :note: {model.parameter.name:value} is ignored in fit function since
314        the user should make sure to call set_param himself.
315       
316    """
317    def __init__(self):
318        """
319        Creates a dictionary (self.fitArrangeList={})of FitArrange elements
320        with Uid as keys
321        """
322        FitEngine.__init__(self)
323        self.fit_arrange_dict = {}
324        self.param_list = []
325       
326    def create_assembly(self, curr_thread):
327        """
328        Extract sansmodel and sansdata from
329        self.FitArrangelist ={Uid:FitArrange}
330        Create parkmodel and park data ,form a list couple of parkmodel
331        and parkdata
332        create an assembly self.problem=  park.Assembly([(parkmodel,parkdata)])
333        """
334        mylist = []
335        #listmodel = []
336        #i = 0
337        fitproblems = []
338        for fproblem in self.fit_arrange_dict.itervalues():
339            if fproblem.get_to_fit() == 1:
340                fitproblems.append(fproblem)
341        if len(fitproblems) == 0: 
342            raise RuntimeError, "No Assembly scheduled for Park fitting."
343            return
344        for item in fitproblems:
345            parkmodel = item.get_model()
346            for p in parkmodel.parameterset:
347                ## does not allow status change for constraint parameters
348                if p.status != 'computed':
349                    if p.get_name()in item.pars:
350                        ## make parameters selected for
351                        #fit will be between boundaries
352                        p.set(p.range)         
353                    else:
354                        p.status = 'fixed'
355            data_list = item.get_data()
356            parkdata = data_list
357            fitness = (parkmodel, parkdata)
358            mylist.append(fitness)
359        self.problem = MyAssembly(models=mylist, curr_thread=curr_thread)
360       
361 
362    def fit(self, q=None, handler=None, curr_thread=None, ftol=1.49012e-8):
363        """
364        Performs fit with park.fit module.It can  perform fit with one model
365        and a set of data, more than two fit of  one model and sets of data or
366        fit with more than two model associated with their set of data and
367        constraints
368       
369        :param pars: Dictionary of parameter names for the model and their
370            values.
371        :param qmin: The minimum value of data's range to be fit
372        :param qmax: The maximum value of data's range to be fit
373       
374        :note: all parameter are ignored most of the time.Are just there
375            to keep ScipyFit and ParkFit interface the same.
376           
377        :return: result.fitness Value of the goodness of fit metric
378        :return: result.pvec list of parameter with the best value
379            found during fitting
380        :return: result.cov Covariance matrix
381       
382        """
383        self.create_assembly(curr_thread=curr_thread)
384        localfit = SansFitSimplex()
385        localfit.ftol = ftol
386       
387        # See `park.fitresult.FitHandler` for details.
388        fitter = SansFitMC(localfit=localfit, start_points=1)
389        if handler == None:
390            handler = fitresult.ConsoleUpdate(improvement_delta=0.1)
391        result = fit.fit(self.problem, fitter=fitter, handler=handler)
392        self.problem.all_results(result)
393       
394        #print "park------", result.inputs
395        #for (model, data) in result.inputs:
396        #    print model.name, data.name
397        #for p in result.parameters:
398        #    print "simul ----", p , p.__class__, p.model.name, p.data.name
399   
400        if result != None:
401            if q != None:
402                q.put(result)
403                return q
404            return result
405        else:
406            raise ValueError, "SVD did not converge"
407           
Note: See TracBrowser for help on using the repository browser.