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

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

making sure each result know it data and model

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