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

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

fixing batch plot problems

  • 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        res.theory = result.fx
70        return res
71     
72class SansFitter(Fitter):
73    """
74    """
75    def fit(self, fitness, handler):
76        """
77        Global optimizer.
78
79        This function should return immediately
80        """
81        # Determine initial value and bounds
82        pars = fitness.fit_parameters()
83        bounds = numpy.array([p.range for p in pars]).T
84        x0 = [p.value for p in pars]
85
86        # Initialize the monitor and results.
87        # Need to make our own copy of the fit results so that the
88        # values don't get stomped on by the next fit iteration.
89        handler.done = False
90        self.handler = handler
91        fitpars = [SansFitParameter(pars[i].name, pars[i].range, v,
92                                     pars[i].model, pars[i].data)
93                   for i,v in enumerate(x0)]
94        handler.result = fitresult.FitResult(fitpars, 0, numpy.NaN)
95
96        # Run the fit (fit should perform _progress and _improvement updates)
97        # This function may return before the fit is complete.
98        self._fit(fitness, x0, bounds)
99       
100class SansFitMC(SansFitter):
101    """
102    Monte Carlo optimizer.
103
104    This implements `park.fit.Fitter`.
105    """
106    localfit = SansFitSimplex()
107    start_points = 10
108
109    def _fit(self, objective, x0, bounds):
110        """
111        Run a monte carlo fit.
112
113        This procedure maps a local optimizer across a set of initial points.
114        """
115        park.fitmc.fitmc(objective, x0, bounds, self.localfit,
116              self.start_points, self.handler)
117
118       
119class SansPart(Part):
120    """
121    Part of a fitting assembly.  Part holds the model itself and
122    associated data.  The part can be initialized with a fitness
123    object or with a pair (model,data) for the default fitness function.
124
125    fitness (Fitness)
126        object implementing the `park.assembly.Fitness` interface.  In
127        particular, fitness should provide a parameterset attribute
128        containing a ParameterSet and a residuals method returning a vector
129        of residuals.
130    weight (dimensionless)
131        weight for the model.  See comments in assembly.py for details.
132    isfitted (boolean)
133        True if the model residuals should be included in the fit.
134        The model parameters may still be used in parameter
135        expressions, but there will be no comparison to the data.
136    residuals (vector)
137        Residuals for the model if they have been calculated, or None
138    degrees_of_freedom
139        Number of residuals minus number of fitted parameters.
140        Degrees of freedom for individual models does not make
141        sense in the presence of expressions combining models,
142        particularly in the case where a model has many parameters
143        but no data or many computed parameters.  The degrees of
144        freedom for the model is set to be at least one.
145    chisq
146        sum(residuals**2); use chisq/degrees_of_freedom to
147        get the reduced chisq value.
148
149        Get/set the weight on the given model.
150
151        assembly.weight(3) returns the weight on model 3 (0-origin)
152        assembly.weight(3,0.5) sets the weight on model 3 (0-origin)
153    """
154
155    def __init__(self, fitness, weight=1., isfitted=True):
156        Part.__init__(self, fitness=fitness, weight=weight,
157                       isfitted=isfitted)
158       
159        self.model, self.data = fitness[0], fitness[1]
160
161class SansFitParameter(FitParameter):
162    """
163    Fit result for an individual parameter.
164    """
165    def __init__(self, name, range, value, model, data):
166        FitParameter.__init__(self, name, range, value)
167        self.model = model
168        self.data = data
169       
170    def summarize(self):
171        """
172        Return parameter range string.
173
174        E.g.,  "       Gold .....|.... 5.2043 in [2,7]"
175        """
176        bar = ['.']*10
177        lo,hi = self.range
178        if numpy.isfinite(lo)and numpy.isfinite(hi):
179            portion = (self.value-lo)/(hi-lo)
180            if portion < 0: portion = 0.
181            elif portion >= 1: portion = 0.99999999
182            barpos = int(math.floor(portion*len(bar)))
183            bar[barpos] = '|'
184        bar = "".join(bar)
185        lostr = "[%g"%lo if numpy.isfinite(lo) else "(-inf"
186        histr = "%g]"%hi if numpy.isfinite(hi) else "inf)"
187        valstr = format_uncertainty(self.value, self.stderr)
188        model_name = str(None)
189        if self.model is not None:
190            model_name = self.model.name
191        data_name = str(None)
192        if self.data is not None:
193            data_name = self.data.name
194           
195        return "%25s %s %s in %s,%s, %s, %s"  % (self.name,bar,valstr,lostr,histr, 
196                                                 model_name, data_name)
197    def __repr__(self):
198        #return "FitParameter('%s')"%self.name
199        return str(self.__class__)
200   
201class MyAssembly(Assembly):
202    def __init__(self, models, curr_thread=None):
203        Assembly.__init__(self, models)
204        self.curr_thread = curr_thread
205        self.chisq = None
206        self._cancel = False
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        #print "all_results", calcpars
240        result.parameters += calcpars
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, _ = 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.