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

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

park chi2/N from the engine

  • 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
11import math
12#import park
13from park import fit
14from park import fitresult
15from  park.fitresult import FitParameter
16import park.simplex
17from park.assembly import Assembly
18from park.assembly import Part
19from park.fitmc import FitSimplex
20import park.fitmc
21from park.fitmc import FitMC
22from park.fit import Fitter
23from park.formatnum import format_uncertainty
24#from Loader import Load
25from sans.fit.AbstractFitEngine import FitEngine
26 
27class SansFitResult(fitresult.FitResult):
28    def __init__(self, *args, **kwrds):
29        fitresult.FitResult.__init__(self, *args, **kwrds)
30        self.theory = None
31        self.inputs = []
32       
33class SansFitSimplex(FitSimplex):
34    """
35    Local minimizer using Nelder-Mead simplex algorithm.
36
37    Simplex is robust and derivative free, though not very efficient.
38
39    This class wraps the bounds contrained Nelder-Mead simplex
40    implementation for `park.simplex.simplex`.
41    """
42    radius = 0.05
43    """Size of the initial simplex; this is a portion between 0 and 1"""
44    xtol = 1
45    #xtol = 1e-4
46    """Stop when simplex vertices are within xtol of each other"""
47    ftol = 5e-5
48    """Stop when vertex values are within ftol of each other"""
49    maxiter = None
50    """Maximum number of iterations before fit terminates"""
51    def fit(self, fitness, x0):
52        """Run the fit"""
53        self.cancel = False
54        pars = fitness.fit_parameters()
55        bounds = numpy.array([p.range for p in pars]).T
56        result = park.simplex.simplex(fitness, x0, bounds=bounds,
57                                 radius=self.radius, xtol=self.xtol,
58                                 ftol=self.ftol, maxiter=self.maxiter,
59                                 abort_test=self._iscancelled)
60        #print "calls:",result.calls
61        #print "simplex returned",result.x,result.fx
62        # Need to make our own copy of the fit results so that the
63        # values don't get stomped on by the next fit iteration.
64        fitpars = [SansFitParameter(pars[i].name,pars[i].range,v, pars[i].model, pars[i].data)
65                   for i,v in enumerate(result.x)]
66        res = SansFitResult(fitpars, result.calls, result.fx)
67        res.inputs = [(pars[i].model, pars[i].data) for i,v in enumerate(result.x)]
68        # Compute the parameter uncertainties from the jacobian
69        res.calc_cov(fitness)
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        self.theory = None
208       
209    def fit_parameters(self):
210        """
211        Return an alphabetical list of the fitting parameters.
212
213        This function is called once at the beginning of a fit,
214        and serves as a convenient place to precalculate what
215        can be precalculated such as the set of fitting parameters
216        and the parameter expressions evaluator.
217        """
218        self.parameterset.setprefix()
219        self._fitparameters = self.parameterset.fitted
220        self._restraints = self.parameterset.restrained
221        pars = self.parameterset.flatten()
222        context = self.parameterset.gather_context()
223        self._fitexpression = park.expression.build_eval(pars,context)
224        #print "constraints",self._fitexpression.__doc__
225
226        self._fitparameters.sort(lambda a,b: cmp(a.path,b.path))
227        # Convert to fitparameter a object
228       
229        fitpars = [SansFitParameter(p.path,p.range,p.value, p.model, p.data)
230                   for p in self._fitparameters]
231        #print "fitpars", fitpars
232        return fitpars
233   
234    def all_results(self, result):
235        """
236        Extend result from the fit with the calculated parameters.
237        """
238        calcpars = [SansFitParameter(p.path,p.range,p.value, p.model, p.data)
239                    for p in self.parameterset.computed]
240        result.parameters += calcpars
241        result.theory = self.theory
242
243    def eval(self):
244        """
245        Recalculate the theory functions, and from them, the
246        residuals and chisq.
247
248        :note: Call this after the parameters have been updated.
249        """
250        # Handle abort from a separate thread.
251        self._cancel = False
252        if self.curr_thread != None:
253            try:
254                self.curr_thread.isquit()
255            except:
256                self._cancel = True
257
258        # Evaluate the computed parameters
259        try:
260            self._fitexpression()
261        except NameError:
262            pass
263
264        # Check that the resulting parameters are in a feasible region.
265        if not self.isfeasible(): return numpy.inf
266
267        resid = []
268        k = len(self._fitparameters)
269        for m in self.parts:
270            # In order to support abort, need to be able to propagate an
271            # external abort signal from self.abort() into an abort signal
272            # for the particular model.  Can't see a way to do this which
273            # doesn't involve setting a state variable.
274            self._current_model = m
275            if self._cancel: return numpy.inf
276            if m.isfitted and m.weight != 0:
277                m.residuals, self.theory = m.fitness.residuals()
278                N = len(m.residuals)
279                m.degrees_of_freedom = N-k if N>k else 1
280                m.chisq = numpy.sum(m.residuals**2/N) 
281                resid.append(m.weight*m.residuals/math.sqrt(N))
282        self.residuals = numpy.hstack(resid)
283        N = len(self.residuals)
284        self.degrees_of_freedom = N-k if N>k else 1
285        self.chisq = numpy.sum(self.residuals**2)
286        return self.chisq
287   
288class ParkFit(FitEngine):
289    """
290    ParkFit performs the Fit.This class can be used as follow:
291    #Do the fit Park
292    create an engine: engine = ParkFit()
293    Use data must be of type plottable
294    Use a sans model
295   
296    Add data with a dictionnary of FitArrangeList where Uid is a key and data
297    is saved in FitArrange object.
298    engine.set_data(data,Uid)
299   
300    Set model parameter "M1"= model.name add {model.parameter.name:value}.
301   
302    :note: Set_param() if used must always preceded set_model()
303         for the fit to be performed.
304    engine.set_param( model,"M1", {'A':2,'B':4})
305   
306    Add model with a dictionnary of FitArrangeList{} where Uid is a key
307    and model
308    is save in FitArrange object.
309    engine.set_model(model,Uid)
310   
311    engine.fit return chisqr,[model.parameter 1,2,..],[[err1....][..err2...]]
312    chisqr1, out1, cov1=engine.fit({model.parameter.name:value},qmin,qmax)
313   
314    :note: {model.parameter.name:value} is ignored in fit function since
315        the user should make sure to call set_param himself.
316       
317    """
318    def __init__(self):
319        """
320        Creates a dictionary (self.fitArrangeList={})of FitArrange elements
321        with Uid as keys
322        """
323        FitEngine.__init__(self)
324        self.fit_arrange_dict = {}
325        self.param_list = []
326       
327    def create_assembly(self, curr_thread):
328        """
329        Extract sansmodel and sansdata from
330        self.FitArrangelist ={Uid:FitArrange}
331        Create parkmodel and park data ,form a list couple of parkmodel
332        and parkdata
333        create an assembly self.problem=  park.Assembly([(parkmodel,parkdata)])
334        """
335        mylist = []
336        #listmodel = []
337        #i = 0
338        fitproblems = []
339        for fproblem in self.fit_arrange_dict.itervalues():
340            if fproblem.get_to_fit() == 1:
341                fitproblems.append(fproblem)
342        if len(fitproblems) == 0: 
343            raise RuntimeError, "No Assembly scheduled for Park fitting."
344            return
345        for item in fitproblems:
346            parkmodel = item.get_model()
347            for p in parkmodel.parameterset:
348                ## does not allow status change for constraint parameters
349                if p.status != 'computed':
350                    if p.get_name()in item.pars:
351                        ## make parameters selected for
352                        #fit will be between boundaries
353                        p.set(p.range)         
354                    else:
355                        p.status = 'fixed'
356            data_list = item.get_data()
357            parkdata = data_list
358            fitness = (parkmodel, parkdata)
359            mylist.append(fitness)
360        self.problem = MyAssembly(models=mylist, curr_thread=curr_thread)
361       
362 
363    def fit(self, q=None, handler=None, curr_thread=None, ftol=1.49012e-8):
364        """
365        Performs fit with park.fit module.It can  perform fit with one model
366        and a set of data, more than two fit of  one model and sets of data or
367        fit with more than two model associated with their set of data and
368        constraints
369       
370        :param pars: Dictionary of parameter names for the model and their
371            values.
372        :param qmin: The minimum value of data's range to be fit
373        :param qmax: The maximum value of data's range to be fit
374       
375        :note: all parameter are ignored most of the time.Are just there
376            to keep ScipyFit and ParkFit interface the same.
377           
378        :return: result.fitness Value of the goodness of fit metric
379        :return: result.pvec list of parameter with the best value
380            found during fitting
381        :return: result.cov Covariance matrix
382       
383        """
384        self.create_assembly(curr_thread=curr_thread)
385        localfit = SansFitSimplex()
386        localfit.ftol = ftol
387       
388        # See `park.fitresult.FitHandler` for details.
389        fitter = SansFitMC(localfit=localfit, start_points=1)
390        if handler == None:
391            handler = fitresult.ConsoleUpdate(improvement_delta=0.1)
392        result = fit.fit(self.problem, fitter=fitter, handler=handler)
393        self.problem.all_results(result)
394       
395        #print "park------", result.inputs
396        #for (model, data) in result.inputs:
397        #    print model.name, data.name
398        #for p in result.parameters:
399        #    print "simul ----", p , p.__class__, p.model.name, p.data.name
400   
401        if result != None:
402            if q != None:
403                q.put(result)
404                return q
405            return result
406        else:
407            raise ValueError, "SVD did not converge"
408           
Note: See TracBrowser for help on using the repository browser.