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

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

make sure result similar for both engine

  • Property mode set to 100644
File size: 16.6 KB
RevLine 
[aa36f96]1
2
3
[792db7d5]4"""
[aa36f96]5ParkFitting module contains SansParameter,Model,Data
[b2f25dc5]6FitArrange, ParkFit,Parameter classes.All listed classes work together
7to perform a simple fit with park optimizer.
[792db7d5]8"""
[89f3b66]9#import time
[dfd3577]10import numpy
[c7e8898]11import math
[d59cb8c]12from  numpy.linalg.linalg import LinAlgError
[b2f25dc5]13#import park
14from park import fit
15from park import fitresult
[1cff677]16from  park.fitresult import FitParameter
17import park.simplex
[b2f25dc5]18from park.assembly import Assembly
[1cff677]19from park.assembly import Part
[b2f25dc5]20from park.fitmc import FitSimplex
[dfd3577]21import park.fitmc
[b2f25dc5]22from park.fitmc import FitMC
[1cff677]23from park.fit import Fitter
24from park.formatnum import format_uncertainty
[61cb28d]25#from Loader import Load
[b2f25dc5]26from sans.fit.AbstractFitEngine import FitEngine
[444c900e]27from sans.fit.AbstractFitEngine import FResult
[1cff677]28 
[634ca14]29class SansFitResult(fitresult.FitResult):
30    def __init__(self, *args, **kwrds):
31        fitresult.FitResult.__init__(self, *args, **kwrds)
[425e49ca]32        self.theory = None
[634ca14]33        self.inputs = []
34       
[1cff677]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"""
[425e49ca]49    ftol = 5e-5
[1cff677]50    """Stop when vertex values are within ftol of each other"""
51    maxiter = None
52    """Maximum number of iterations before fit terminates"""
[d59cb8c]53    def __init__(self, ftol=5e-5):
54        self.ftol = ftol
55       
[1cff677]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)]
[634ca14]71        res = SansFitResult(fitpars, result.calls, result.fx)
72        res.inputs = [(pars[i].model, pars[i].data) for i,v in enumerate(result.x)]
[1cff677]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)
[d4b0687]100
[1cff677]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
[d59cb8c]113    def __init__(self, localfit, start_points=10):
114        self.localfit = localfit
115        self.start_points = start_points
116       
[1cff677]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.
[fadea71]132
[1cff677]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.
[dfd3577]156
[1cff677]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   
[dfd3577]209class MyAssembly(Assembly):
210    def __init__(self, models, curr_thread=None):
[444c900e]211        """Build an assembly from a list of models."""
212        self.parts = []
213        for m in models:
214            self.parts.append(SansPart(m))
[dfd3577]215        self.curr_thread = curr_thread
[bd5ac39]216        self.chisq = None
217        self._cancel = False
[e03a14e]218        self.theory = None
[444c900e]219        self._reset()
[dfd3577]220       
[1cff677]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
[cef2da8]238        self._fitparameters.sort(lambda a,b: cmp(a.path,b.path))
[1cff677]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
[e03a14e]253        result.theory = self.theory
[1cff677]254
[dfd3577]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
[bd5ac39]271        try:
272            self._fitexpression()
273        except NameError:
274            pass
[dfd3577]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:
[e03a14e]289                m.residuals, self.theory = m.fitness.residuals()
[dfd3577]290                N = len(m.residuals)
291                m.degrees_of_freedom = N-k if N>k else 1
[1fa4db39]292                # dividing residuals by N in order to be consistent with Scipy
[c7e8898]293                m.chisq = numpy.sum(m.residuals**2/N) 
294                resid.append(m.weight*m.residuals/math.sqrt(N))
[dfd3577]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   
[4c718654]301class ParkFit(FitEngine):
[7705306]302    """
[aa36f96]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   
[b2f25dc5]319    Add model with a dictionnary of FitArrangeList{} where Uid is a key
320    and model
[aa36f96]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
[792db7d5]328        the user should make sure to call set_param himself.
[aa36f96]329       
[7705306]330    """
[916a15f]331    def __init__(self):
[792db7d5]332        """
[aa36f96]333        Creates a dictionary (self.fitArrangeList={})of FitArrange elements
334        with Uid as keys
[792db7d5]335        """
[b2f25dc5]336        FitEngine.__init__(self)
337        self.fit_arrange_dict = {}
338        self.param_list = []
[37d9521]339       
[dfd3577]340    def create_assembly(self, curr_thread):
[7705306]341        """
[b2f25dc5]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
[792db7d5]346        create an assembly self.problem=  park.Assembly([(parkmodel,parkdata)])
[7705306]347        """
[89f3b66]348        mylist = []
[b2f25dc5]349        #listmodel = []
350        #i = 0
[89f3b66]351        fitproblems = []
[b2f25dc5]352        for fproblem in self.fit_arrange_dict.itervalues():
[89f3b66]353            if fproblem.get_to_fit() == 1:
[a9e04aa]354                fitproblems.append(fproblem)
[89f3b66]355        if len(fitproblems) == 0: 
[a9e04aa]356            raise RuntimeError, "No Assembly scheduled for Park fitting."
357            return
358        for item in fitproblems:
359            parkmodel = item.get_model()
[9e85792]360            for p in parkmodel.parameterset:
[fe886ee]361                ## does not allow status change for constraint parameters
[89f3b66]362                if p.status != 'computed':
[b2f25dc5]363                    if p.get_name()in item.pars:
364                        ## make parameters selected for
365                        #fit will be between boundaries
[89f3b66]366                        p.set(p.range)         
[aed7c57]367                    else:
[89f3b66]368                        p.status = 'fixed'
[b2f25dc5]369            data_list = item.get_data()
370            parkdata = data_list
[89f3b66]371            fitness = (parkmodel, parkdata)
[ca6d914]372            mylist.append(fitness)
[dfd3577]373        self.problem = MyAssembly(models=mylist, curr_thread=curr_thread)
[126a761]374       
[dfd3577]375 
[93de635d]376    def fit(self, q=None, handler=None, curr_thread=None, ftol=1.49012e-8):
[7705306]377        """
[aa36f96]378        Performs fit with park.fit module.It can  perform fit with one model
379        and a set of data, more than two fit of  one model and sets of data or
[b2f25dc5]380        fit with more than two model associated with their set of data and
381        constraints
[aa36f96]382       
[b2f25dc5]383        :param pars: Dictionary of parameter names for the model and their
384            values.
[aa36f96]385        :param qmin: The minimum value of data's range to be fit
386        :param qmax: The maximum value of data's range to be fit
387       
[b2f25dc5]388        :note: all parameter are ignored most of the time.Are just there
389            to keep ScipyFit and ParkFit interface the same.
[aa36f96]390           
391        :return: result.fitness Value of the goodness of fit metric
[b2f25dc5]392        :return: result.pvec list of parameter with the best value
393            found during fitting
[aa36f96]394        :return: result.cov Covariance matrix
395       
[7705306]396        """
[dfd3577]397        self.create_assembly(curr_thread=curr_thread)
[1cff677]398        localfit = SansFitSimplex()
[93de635d]399        localfit.ftol = ftol
[681f0dc]400       
[916a15f]401        # See `park.fitresult.FitHandler` for details.
[1cff677]402        fitter = SansFitMC(localfit=localfit, start_points=1)
[681f0dc]403        if handler == None:
[89f3b66]404            handler = fitresult.ConsoleUpdate(improvement_delta=0.1)
[444c900e]405       
406        result_list = []
[d59cb8c]407        try:
408            result = fit.fit(self.problem, fitter=fitter, handler=handler)
409            self.problem.all_results(result)
410           
[444c900e]411        except LinAlgError:
[ee5b04c]412            raise ValueError, "SVD did not converge"
[444c900e]413   
414        for m in self.problem.parts:
415            residuals, theory = m.fitness.residuals()
416            small_result = FResult(model=m.model, data=m.data.sans_data)
417            small_result.theory = theory
418            small_result.residuals = residuals
419            small_result.pvec = []
420            small_result.cov = []
421            small_result.stderr = []
422            small_result.param_list = []
423            small_result.residuals = m.residuals
424            if result is not None:
425                for p in result.parameters:
426                    if p.data.name == small_result.data.name:
427                        small_result.index = m.data.idx
428                        small_result.fitness = result.fitness
429                        small_result.pvec.append(p.value)
430                        small_result.stderr.append(p.stderr)
431                        name = p.name.split('.')[1].strip()
432                        small_result.param_list.append(name)
433            result_list.append(small_result)   
434        if q != None:
435            q.put(result)
436            return q
437        print "park", len(result_list)
438        return result_list
439       
Note: See TracBrowser for help on using the repository browser.