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

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

remove comments

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