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

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

make sure the ftol value is used for park

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