source: sasview/src/sans/fit/AbstractFitEngine.py @ 6c00702

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 6c00702 was 6c00702, checked in by pkienzle, 10 years ago

correct fitting tests so that they run (though not yet automatically)

  • Property mode set to 100644
File size: 26.8 KB
Line 
1
2import  copy
3#import logging
4import sys
5import numpy
6import math
7import park
8from sans.dataloader.data_info import Data1D
9from sans.dataloader.data_info import Data2D
10_SMALLVALUE = 1.0e-10   
11   
12class SansParameter(park.Parameter):
13    """
14    SANS model parameters for use in the PARK fitting service.
15    The parameter attribute value is redirected to the underlying
16    parameter value in the SANS model.
17    """
18    def __init__(self, name, model, data):
19        """
20            :param name: the name of the model parameter
21            :param model: the sans model to wrap as a park model
22        """
23        park.Parameter.__init__(self, name)
24        self._model, self._name = model, name
25        self.data = data
26        self.model = model
27        #set the value for the parameter of the given name
28        self.set(model.getParam(name))
29         
30    def _getvalue(self):
31        """
32        override the _getvalue of park parameter
33       
34        :return value the parameter associates with self.name
35       
36        """
37        return self._model.getParam(self.name)
38   
39    def _setvalue(self, value):
40        """
41        override the _setvalue pf park parameter
42       
43        :param value: the value to set on a given parameter
44       
45        """
46        self._model.setParam(self.name, value)
47       
48    value = property(_getvalue, _setvalue)
49   
50    def _getrange(self):
51        """
52        Override _getrange of park parameter
53        return the range of parameter
54        """
55        #if not  self.name in self._model.getDispParamList():
56        lo, hi = self._model.details[self.name][1:3]
57        if lo is None: lo = -numpy.inf
58        if hi is None: hi = numpy.inf
59        if lo > hi:
60            raise ValueError, "wrong fit range for parameters"
61       
62        return lo, hi
63   
64    def get_name(self):
65        """
66        """
67        return self._getname()
68   
69    def _setrange(self, r):
70        """
71        override _setrange of park parameter
72       
73        :param r: the value of the range to set
74       
75        """
76        self._model.details[self.name][1:3] = r
77    range = property(_getrange, _setrange)
78   
79   
80class Model(park.Model):
81    """
82    PARK wrapper for SANS models.
83    """
84    def __init__(self, sans_model, sans_data=None, **kw):
85        """
86        :param sans_model: the sans model to wrap using park interface
87       
88        """
89        park.Model.__init__(self, **kw)
90        self.model = sans_model
91        self.name = sans_model.name
92        self.data = sans_data
93        #list of parameters names
94        self.sansp = sans_model.getParamList()
95        #list of park parameter
96        self.parkp = [SansParameter(p, sans_model, sans_data) for p in self.sansp]
97        #list of parameter set
98        self.parameterset = park.ParameterSet(sans_model.name, pars=self.parkp)
99        self.pars = []
100 
101    def get_params(self, fitparams):
102        """
103        return a list of value of paramter to fit
104       
105        :param fitparams: list of paramaters name to fit
106       
107        """
108        list_params = []
109        self.pars = []
110        self.pars = fitparams
111        for item in fitparams:
112            for element in self.parkp:
113                if element.name == str(item):
114                    list_params.append(element.value)
115        return list_params
116   
117    def set_params(self, paramlist, params):
118        """
119        Set value for parameters to fit
120       
121        :param params: list of value for parameters to fit
122       
123        """
124        try:
125            for i in range(len(self.parkp)):
126                for j in range(len(paramlist)):
127                    if self.parkp[i].name == paramlist[j]:
128                        self.parkp[i].value = params[j]
129                        self.model.setParam(self.parkp[i].name, params[j])
130        except:
131            raise
132 
133    def eval(self, x):
134        """
135            Override eval method of park model.
136       
137            :param x: the x value used to compute a function
138        """
139        try:
140            return self.model.evalDistribution(x)
141        except:
142            raise
143       
144    def eval_derivs(self, x, pars=[]):
145        """
146        Evaluate the model and derivatives wrt pars at x.
147
148        pars is a list of the names of the parameters for which derivatives
149        are desired.
150
151        This method needs to be specialized in the model to evaluate the
152        model function.  Alternatively, the model can implement is own
153        version of residuals which calculates the residuals directly
154        instead of calling eval.
155        """
156        return []
157
158   
159class FitData1D(Data1D):
160    """
161        Wrapper class  for SANS data
162        FitData1D inherits from DataLoader.data_info.Data1D. Implements
163        a way to get residuals from data.
164    """
165    def __init__(self, x, y, dx=None, dy=None, smearer=None, data=None):
166        """
167            :param smearer: is an object of class QSmearer or SlitSmearer
168               that will smear the theory data (slit smearing or resolution
169               smearing) when set.
170           
171            The proper way to set the smearing object would be to
172            do the following: ::
173           
174                from sans.models.qsmearing import smear_selection
175                smearer = smear_selection(some_data)
176                fitdata1d = FitData1D( x= [1,3,..,],
177                                        y= [3,4,..,8],
178                                        dx=None,
179                                        dy=[1,2...], smearer= smearer)
180           
181            :Note: that some_data _HAS_ to be of
182                class DataLoader.data_info.Data1D
183                Setting it back to None will turn smearing off.
184               
185        """
186        Data1D.__init__(self, x=x, y=y, dx=dx, dy=dy)
187        self.sans_data = data
188        self.smearer = smearer
189        self._first_unsmeared_bin = None
190        self._last_unsmeared_bin = None
191        # Check error bar; if no error bar found, set it constant(=1)
192        # TODO: Should provide an option for users to set it like percent,
193        # constant, or dy data
194        if dy == None or dy == [] or dy.all() == 0:
195            self.dy = numpy.ones(len(y))
196        else:
197            self.dy = numpy.asarray(dy).copy()
198
199        ## Min Q-value
200        #Skip the Q=0 point, especially when y(q=0)=None at x[0].
201        if min(self.x) == 0.0 and self.x[0] == 0 and\
202                     not numpy.isfinite(self.y[0]):
203            self.qmin = min(self.x[self.x != 0])
204        else:
205            self.qmin = min(self.x)
206        ## Max Q-value
207        self.qmax = max(self.x)
208       
209        # Range used for input to smearing
210        self._qmin_unsmeared = self.qmin
211        self._qmax_unsmeared = self.qmax
212        # Identify the bin range for the unsmeared and smeared spaces
213        self.idx = (self.x >= self.qmin) & (self.x <= self.qmax)
214        self.idx_unsmeared = (self.x >= self._qmin_unsmeared) \
215                            & (self.x <= self._qmax_unsmeared)
216 
217    def set_fit_range(self, qmin=None, qmax=None):
218        """ to set the fit range"""
219        # Skip Q=0 point, (especially for y(q=0)=None at x[0]).
220        # ToDo: Find better way to do it.
221        if qmin == 0.0 and not numpy.isfinite(self.y[qmin]):
222            self.qmin = min(self.x[self.x != 0])
223        elif qmin != None:
224            self.qmin = qmin
225        if qmax != None:
226            self.qmax = qmax
227        # Determine the range needed in unsmeared-Q to cover
228        # the smeared Q range
229        self._qmin_unsmeared = self.qmin
230        self._qmax_unsmeared = self.qmax
231       
232        self._first_unsmeared_bin = 0
233        self._last_unsmeared_bin = len(self.x) - 1
234       
235        if self.smearer != None:
236            self._first_unsmeared_bin, self._last_unsmeared_bin = \
237                    self.smearer.get_bin_range(self.qmin, self.qmax)
238            self._qmin_unsmeared = self.x[self._first_unsmeared_bin]
239            self._qmax_unsmeared = self.x[self._last_unsmeared_bin]
240           
241        # Identify the bin range for the unsmeared and smeared spaces
242        self.idx = (self.x >= self.qmin) & (self.x <= self.qmax)
243        ## zero error can not participate for fitting
244        self.idx = self.idx & (self.dy != 0)
245        self.idx_unsmeared = (self.x >= self._qmin_unsmeared) \
246                            & (self.x <= self._qmax_unsmeared)
247
248    def get_fit_range(self):
249        """
250            Return the range of data.x to fit
251        """
252        return self.qmin, self.qmax
253       
254    def residuals(self, fn):
255        """
256            Compute residuals.
257           
258            If self.smearer has been set, use if to smear
259            the data before computing chi squared.
260           
261            :param fn: function that return model value
262           
263            :return: residuals
264        """
265        # Compute theory data f(x)
266        fx = numpy.zeros(len(self.x))
267        fx[self.idx_unsmeared] = fn(self.x[self.idx_unsmeared])
268       
269        ## Smear theory data
270        if self.smearer is not None:
271            fx = self.smearer(fx, self._first_unsmeared_bin,
272                              self._last_unsmeared_bin)
273        ## Sanity check
274        if numpy.size(self.dy) != numpy.size(fx):
275            msg = "FitData1D: invalid error array "
276            msg += "%d <> %d" % (numpy.shape(self.dy), numpy.size(fx))
277            raise RuntimeError, msg
278        return (self.y[self.idx] - fx[self.idx]) / self.dy[self.idx], fx[self.idx]
279           
280    def residuals_deriv(self, model, pars=[]):
281        """
282            :return: residuals derivatives .
283           
284            :note: in this case just return empty array
285        """
286        return []
287   
288   
289class FitData2D(Data2D):
290    """
291        Wrapper class  for SANS data
292    """
293    def __init__(self, sans_data2d, data=None, err_data=None):
294        Data2D.__init__(self, data=data, err_data=err_data)
295        """
296            Data can be initital with a data (sans plottable)
297            or with vectors.
298        """
299        self.res_err_image = []
300        self.idx = []
301        self.qmin = None
302        self.qmax = None
303        self.smearer = None
304        self.radius = 0
305        self.res_err_data = []
306        self.sans_data = sans_data2d
307        self.set_data(sans_data2d)
308
309    def set_data(self, sans_data2d, qmin=None, qmax=None):
310        """
311            Determine the correct qx_data and qy_data within range to fit
312        """
313        self.data = sans_data2d.data
314        self.err_data = sans_data2d.err_data
315        self.qx_data = sans_data2d.qx_data
316        self.qy_data = sans_data2d.qy_data
317        self.mask = sans_data2d.mask
318
319        x_max = max(math.fabs(sans_data2d.xmin), math.fabs(sans_data2d.xmax))
320        y_max = max(math.fabs(sans_data2d.ymin), math.fabs(sans_data2d.ymax))
321       
322        ## fitting range
323        if qmin == None:
324            self.qmin = 1e-16
325        if qmax == None:
326            self.qmax = math.sqrt(x_max * x_max + y_max * y_max)
327        ## new error image for fitting purpose
328        if self.err_data == None or self.err_data == []:
329            self.res_err_data = numpy.ones(len(self.data))
330        else:
331            self.res_err_data = copy.deepcopy(self.err_data)
332        #self.res_err_data[self.res_err_data==0]=1
333       
334        self.radius = numpy.sqrt(self.qx_data**2 + self.qy_data**2)
335       
336        # Note: mask = True: for MASK while mask = False for NOT to mask
337        self.idx = ((self.qmin <= self.radius) &\
338                            (self.radius <= self.qmax))
339        self.idx = (self.idx) & (self.mask)
340        self.idx = (self.idx) & (numpy.isfinite(self.data))
341
342    def set_smearer(self, smearer):
343        """
344            Set smearer
345        """
346        if smearer == None:
347            return
348        self.smearer = smearer
349        self.smearer.set_index(self.idx)
350        self.smearer.get_data()
351
352    def set_fit_range(self, qmin=None, qmax=None):
353        """
354            To set the fit range
355        """
356        if qmin == 0.0:
357            self.qmin = 1e-16
358        elif qmin != None:
359            self.qmin = qmin
360        if qmax != None:
361            self.qmax = qmax
362        self.radius = numpy.sqrt(self.qx_data**2 + self.qy_data**2)
363        self.idx = ((self.qmin <= self.radius) &\
364                            (self.radius <= self.qmax))
365        self.idx = (self.idx) & (self.mask)
366        self.idx = (self.idx) & (numpy.isfinite(self.data))
367        self.idx = (self.idx) & (self.res_err_data != 0)
368
369    def get_fit_range(self):
370        """
371        return the range of data.x to fit
372        """
373        return self.qmin, self.qmax
374     
375    def residuals(self, fn):
376        """
377        return the residuals
378        """
379        if self.smearer != None:
380            fn.set_index(self.idx)
381            # Get necessary data from self.data and set the data for smearing
382            fn.get_data()
383
384            gn = fn.get_value()
385        else:
386            gn = fn([self.qx_data[self.idx],
387                     self.qy_data[self.idx]])
388        # use only the data point within ROI range
389        res = (self.data[self.idx] - gn) / self.res_err_data[self.idx]
390
391        return res, gn
392       
393    def residuals_deriv(self, model, pars=[]):
394        """
395        :return: residuals derivatives .
396       
397        :note: in this case just return empty array
398       
399        """
400        return []
401   
402   
403class FitAbort(Exception):
404    """
405    Exception raise to stop the fit
406    """
407    #pass
408    #print"Creating fit abort Exception"
409
410
411class SansAssembly:
412    """
413    Sans Assembly class a class wrapper to be call in optimizer.leastsq method
414    """
415    def __init__(self, paramlist, model=None, data=None, fitresult=None,
416                 handler=None, curr_thread=None, msg_q=None):
417        """
418        :param Model: the model wrapper fro sans -model
419        :param Data: the data wrapper for sans data
420       
421        """
422        self.model = model
423        self.data = data
424        self.paramlist = paramlist
425        self.msg_q = msg_q
426        self.curr_thread = curr_thread
427        self.handler = handler
428        self.fitresult = fitresult
429        self.res = []
430        self.true_res = []
431        self.func_name = "Functor"
432        self.theory = None
433       
434    def chisq(self):
435        """
436        Calculates chi^2
437       
438        :param params: list of parameter values
439       
440        :return: chi^2
441       
442        """
443        total = 0
444        for item in self.true_res:
445            total += item * item
446        if len(self.true_res) == 0:
447            return None
448        return total / len(self.true_res)
449   
450    def __call__(self, params):
451        """
452            Compute residuals
453            :param params: value of parameters to fit
454        """
455        #import thread
456        self.model.set_params(self.paramlist, params)
457        #print "params", params
458        self.true_res, theory = self.data.residuals(self.model.eval)
459        self.theory = copy.deepcopy(theory)
460        # check parameters range
461        if self.check_param_range():
462            # if the param value is outside of the bound
463            # just silent return res = inf
464            return self.res
465        self.res = self.true_res
466       
467        if self.fitresult is not None:
468            self.fitresult.set_model(model=self.model)
469            self.fitresult.residuals = self.true_res
470            self.fitresult.iterations += 1
471            self.fitresult.theory = theory
472           
473            #fitness = self.chisq(params=params)
474            fitness = self.chisq()
475            self.fitresult.pvec = params
476            self.fitresult.set_fitness(fitness=fitness)
477            if self.msg_q is not None:
478                self.msg_q.put(self.fitresult)
479               
480            if self.handler is not None:
481                self.handler.set_result(result=self.fitresult)
482                self.handler.update_fit()
483
484            if self.curr_thread != None:
485                try:
486                    self.curr_thread.isquit()
487                except:
488                    #msg = "Fitting: Terminated...       Note: Forcing to stop "
489                    #msg += "fitting may cause a 'Functor error message' "
490                    #msg += "being recorded in the log file....."
491                    #self.handler.stop(msg)
492                    raise
493         
494        return self.res
495   
496    def check_param_range(self):
497        """
498        Check the lower and upper bound of the parameter value
499        and set res to the inf if the value is outside of the
500        range
501        :limitation: the initial values must be within range.
502        """
503
504        #time.sleep(0.01)
505        is_outofbound = False
506        # loop through the fit parameters
507        for p in self.model.parameterset:
508            param_name = p.get_name()
509            if param_name in self.paramlist:
510               
511                # if the range was defined, check the range
512                if numpy.isfinite(p.range[0]):
513                    if p.value == 0:
514                        # This value works on Scipy
515                        # Do not change numbers below
516                        value = _SMALLVALUE
517                    else:
518                        value = p.value
519                    # For leastsq, it needs a bit step back from the boundary
520                    val = p.range[0] - value * _SMALLVALUE
521                    if p.value < val:
522                        self.res *= 1e+6
523                       
524                        is_outofbound = True
525                        break
526                if numpy.isfinite(p.range[1]):
527                    # This value works on Scipy
528                    # Do not change numbers below
529                    if p.value == 0:
530                        value = _SMALLVALUE
531                    else:
532                        value = p.value
533                    # For leastsq, it needs a bit step back from the boundary
534                    val = p.range[1] + value * _SMALLVALUE
535                    if p.value > val:
536                        self.res *= 1e+6
537                        is_outofbound = True
538                        break
539
540        return is_outofbound
541   
542   
543class FitEngine:
544    def __init__(self):
545        """
546        Base class for scipy and park fit engine
547        """
548        #List of parameter names to fit
549        self.param_list = []
550        #Dictionnary of fitArrange element (fit problems)
551        self.fit_arrange_dict = {}
552        self.fitter_id = None
553       
554    def set_model(self, model, id, pars=[], constraints=[], data=None):
555        """
556        set a model on a given  in the fit engine.
557       
558        :param model: sans.models type
559        :param id: is the key of the fitArrange dictionary where model is saved as a value
560        :param pars: the list of parameters to fit
561        :param constraints: list of
562            tuple (name of parameter, value of parameters)
563            the value of parameter must be a string to constraint 2 different
564            parameters.
565            Example: 
566            we want to fit 2 model M1 and M2 both have parameters A and B.
567            constraints can be ``constraints = [(M1.A, M2.B+2), (M1.B= M2.A *5),...,]``
568           
569             
570        :note: pars must contains only name of existing model's parameters
571       
572        """
573        if model == None:
574            raise ValueError, "AbstractFitEngine: Need to set model to fit"
575       
576        new_model = model
577        if not issubclass(model.__class__, Model):
578            new_model = Model(model, data)
579       
580        if len(constraints) > 0:
581            for constraint in constraints:
582                name, value = constraint
583                try:
584                    new_model.parameterset[str(name)].set(str(value))
585                except:
586                    msg = "Fit Engine: Error occurs when setting the constraint"
587                    msg += " %s for parameter %s " % (value, name)
588                    raise ValueError, msg
589               
590        if len(pars) > 0:
591            temp = []
592            for item in pars:
593                if item in new_model.model.getParamList():
594                    temp.append(item)
595                    self.param_list.append(item)
596                else:
597                   
598                    msg = "wrong parameter %s used " % str(item)
599                    msg += "to set model %s. Choose " % str(new_model.model.name)
600                    msg += "parameter name within %s" % \
601                                str(new_model.model.getParamList())
602                    raise ValueError, msg
603             
604            #A fitArrange is already created but contains data_list only at id
605            if self.fit_arrange_dict.has_key(id):
606                self.fit_arrange_dict[id].set_model(new_model)
607                self.fit_arrange_dict[id].pars = pars
608            else:
609            #no fitArrange object has been create with this id
610                fitproblem = FitArrange()
611                fitproblem.set_model(new_model)
612                fitproblem.pars = pars
613                self.fit_arrange_dict[id] = fitproblem
614                vals = []
615                for name in pars:
616                    vals.append(new_model.model.getParam(name))
617                self.fit_arrange_dict[id].vals = vals
618        else:
619            raise ValueError, "park_integration:missing parameters"
620   
621    def set_data(self, data, id, smearer=None, qmin=None, qmax=None):
622        """
623        Receives plottable, creates a list of data to fit,set data
624        in a FitArrange object and adds that object in a dictionary
625        with key id.
626       
627        :param data: data added
628        :param id: unique key corresponding to a fitArrange object with data
629        """
630        if data.__class__.__name__ == 'Data2D':
631            fitdata = FitData2D(sans_data2d=data, data=data.data,
632                                 err_data=data.err_data)
633        else:
634            fitdata = FitData1D(x=data.x, y=data.y,
635                                 dx=data.dx, dy=data.dy, smearer=smearer)
636        fitdata.sans_data = data
637       
638        fitdata.set_fit_range(qmin=qmin, qmax=qmax)
639        #A fitArrange is already created but contains model only at id
640        if id in self.fit_arrange_dict:
641            self.fit_arrange_dict[id].add_data(fitdata)
642        else:
643        #no fitArrange object has been create with this id
644            fitproblem = FitArrange()
645            fitproblem.add_data(fitdata)
646            self.fit_arrange_dict[id] = fitproblem
647   
648    def get_model(self, id):
649        """
650        :param id: id is key in the dictionary containing the model to return
651       
652        :return:  a model at this id or None if no FitArrange element was
653            created with this id
654        """
655        if id in self.fit_arrange_dict:
656            return self.fit_arrange_dict[id].get_model()
657        else:
658            return None
659   
660    def remove_fit_problem(self, id):
661        """remove   fitarrange in id"""
662        if id in self.fit_arrange_dict:
663            del self.fit_arrange_dict[id]
664           
665    def select_problem_for_fit(self, id, value):
666        """
667        select a couple of model and data at the id position in dictionary
668        and set in self.selected value to value
669       
670        :param value: the value to allow fitting.
671                can only have the value one or zero
672        """
673        if id in self.fit_arrange_dict:
674            self.fit_arrange_dict[id].set_to_fit(value)
675             
676    def get_problem_to_fit(self, id):
677        """
678        return the self.selected value of the fit problem of id
679       
680        :param id: the id of the problem
681        """
682        if id in self.fit_arrange_dict:
683            self.fit_arrange_dict[id].get_to_fit()
684   
685   
686class FitArrange:
687    def __init__(self):
688        """
689        Class FitArrange contains a set of data for a given model
690        to perform the Fit.FitArrange must contain exactly one model
691        and at least one data for the fit to be performed.
692       
693        model: the model selected by the user
694        Ldata: a list of data what the user wants to fit
695           
696        """
697        self.model = None
698        self.data_list = []
699        self.pars = []
700        self.vals = []
701        self.selected = 0
702       
703    def set_model(self, model):
704        """
705        set_model save a copy of the model
706       
707        :param model: the model being set
708        """
709        self.model = model
710       
711    def add_data(self, data):
712        """
713        add_data fill a self.data_list with data to fit
714       
715        :param data: Data to add in the list
716        """
717        if not data in self.data_list:
718            self.data_list.append(data)
719           
720    def get_model(self):
721        """
722        :return: saved model
723        """
724        return self.model
725     
726    def get_data(self):
727        """
728        :return: list of data data_list
729        """
730        return self.data_list[0]
731     
732    def remove_data(self, data):
733        """
734        Remove one element from the list
735       
736        :param data: Data to remove from data_list
737        """
738        if data in self.data_list:
739            self.data_list.remove(data)
740           
741    def set_to_fit(self, value=0):
742        """
743        set self.selected to 0 or 1  for other values raise an exception
744       
745        :param value: integer between 0 or 1
746        """
747        self.selected = value
748       
749    def get_to_fit(self):
750        """
751        return self.selected value
752        """
753        return self.selected
754   
755   
756IS_MAC = True
757if sys.platform.count("win32") > 0:
758    IS_MAC = False
759
760
761class FResult(object):
762    """
763    Storing fit result
764    """
765    def __init__(self, model=None, param_list=None, data=None):
766        self.calls = None
767        self.pars = []
768        self.fitness = None
769        self.chisqr = None
770        self.pvec = []
771        self.cov = []
772        self.info = None
773        self.mesg = None
774        self.success = None
775        self.stderr = None
776        self.residuals = []
777        self.index = []
778        self.parameters = None
779        self.is_mac = IS_MAC
780        self.model = model
781        self.data = data
782        self.theory = []
783        self.param_list = param_list
784        self.iterations = 0
785        self.inputs = []
786        self.fitter_id = None
787        if self.model is not None and self.data is not None:
788            self.inputs = [(self.model, self.data)]
789     
790    def set_model(self, model):
791        """
792        """
793        self.model = model
794       
795    def set_fitness(self, fitness):
796        """
797        """
798        self.fitness = fitness
799       
800    def __str__(self):
801        """
802        """
803        if self.pvec == None and self.model is None and self.param_list is None:
804            return "No results"
805        n = len(self.model.parameterset)
806       
807        result_param = zip(xrange(n), self.model.parameterset)
808        msg1 = ["[Iteration #: %s ]" % self.iterations]
809        msg3 = ["=== goodness of fit: %s ===" % (str(self.fitness))]
810        if not self.is_mac:
811            msg2 = ["P%-3d  %s......|.....%s" % \
812                (p[0], p[1], p[1].value)\
813                  for p in result_param if p[1].name in self.param_list]
814            msg = msg1 + msg3 + msg2
815        else:
816            msg = msg1 + msg3
817        msg = "\n".join(msg)
818        return msg
819   
820    def print_summary(self):
821        """
822        """
823        print self
Note: See TracBrowser for help on using the repository browser.