


@ 7,10 +7,58 @@ 




# Imported 20100120 from $PWQMC77/expt/Hybridproj/analyzeEh.py 




# (dated 20090323). 




# 




# fit_func_base was imported 20150520 from Cr2_analysis_cbs.py 




# (dated 20141017, CVS rev 1.143). 




# 




# Some references on fitting: 




# * http://stackoverflow.com/questions/529184/simplemultidimensionalcurvefitting 




# * http://www.scipy.org/Cookbook/OptimizationDemo1 (not as thorough, but maybe useful) 









""" 




wpylib.math.fitting 









Basic tools for twodimensional curve fitting 














ABOUT THE FITTING METHODS 









We depend on module scipy.optimize and (optionally) lmfit to provide the 




minimization routines for us. 









The following methods are currently supported for scipy.optimize: 









* `fmin` 




The NelderMead Simplex algorithm. 









* `fmin_bfgs` or `bfgs` 




The BroydenFletcherGoldfarbShanno (BFGS) algorithm 









* `anneal` 




Similated annealing algorithm 









* `leastsq` 




The LevenbergMarquardt nonlinear least square (NLLS) method 









See the documentation of `scipy.optimize` for more details. 




The `fmin` algorithm is the slowest although it is fairly foor proof to 




converge it (it may take many iterations). 




The leastsq` algorithm is the best but it requires parameter guess that is 




reasonable. 




I don't have much success with `anneal`it seems to behave erratically in 




my limited experience. YMMV. 














The lmfit package is supported if it can be found at runtime. 




This package provides richer set of features, including constraints on 




parameters and parameter interdependency. 




Various minimization methods under this package are available. 




To use lmfit, use keyword `lmfit:<method>` as the fit method name. 




Example: `lmfit:leastsq`. 




See the documentation here: 









http://cars9.uchicago.edu/software/python/lmfit/fitting.html#fitmethodslabel 




""" 









import numpy 




import scipy.optimize 




from wpylib.db.result_base import result_base 



@ 379,3 +427,147 @@ def fit_func(Funct, Data=None, Guess=None, Params=None, 




raise ValueError, "Invalid `outfmt' argument = " + x 



















# Imported 20150520 from Cr2_analysis_cbs.py . 




class fit_func_base(object): 




"""Base class for function 2D fitting object. 




This is an enhanced OO interface to fit_func. 









In the derived class, a __call__ method must be implemented with 




this prototype: 









def __call__(self, C, x) 









where 




 `C' is the parameters which we sought through the fitting 




procedure, and 




 `x' is the x values of the data samples against which we want 




to do the curve fitting. 









A few useradjustable parameters need to be attached as attributes 




to this object: 









 fit_method 




 fit_opts (a dict or multi_fit_opts object) 




 debug 




 dbg_params 




 Params 









`fit_method' is a string containing the name of the fitting method to use, 




see this module document. 









Additional attributes are required to support lmfitbased fitting: 









 param_names: a list/tuple of parameter names, in the same order as in 




the legacy 'C' __call__ argument above. 









The inputdatabased automatic parameter guess is specified via Guess parameter. 




See wpylib.math.fitting.fit_func for detail. 









 if Guess==None (default), then it attempts to use self.Guess_xy() method 




(better, new default) or old self.Guess() method. 




 if Guess==False (only for lmfit case), existing values from Params object 




will be used. 




 TODO: dictlike Guess should be made possible. 




 otherwise, the guess values will be used as the initial values. 














""" 




class multi_fit_opts(dict): 




"""A class for defining default control parameters for different fit methods. 




The fit method name is the dict key, and the value, which is also a dict, 




is the default set of fitting control parameters for that particular fit method. 




""" 




pass 




# Some reasonable parameters are set: 




fit_default_opts = multi_fit_opts( 




fmin=dict(xtol=1e5, maxfun=100000, maxiter=10000, disp=0), 




fmin_bfgs=dict(gtol=1e6, disp=0), 




leastsq=dict(xtol=1e8, epsfcn=1e6), 




) 




fit_default_opts["lmfit:leastsq"] = dict(xtol=1e8, epsfcn=1e6) 




debug = 1 




dbg_params = 1 




fit_method = 'fmin' 




fit_opts = fit_default_opts 




#fit_opts = dict(xtol=1e5, maxfun=100000, maxiter=10000, disp=0) 




def fit(self, x, y, dy=None, fit_opts=None, Funct_hook=None, Guess=None): 




"""Main entry function for fitting.""" 




x = numpy.asarray(x) 




if len(x.shape) == 1: 




# fix common "mistake" for 1D domain: make it 2D 




x = x.reshape((1, x.shape[0])) 




if fit_opts == None: 




# Use class default if it is available 




fit_opts = getattr(self, "fit_opts", {}) 




if isinstance(fit_opts, self.multi_fit_opts): # multiple choice :) 




fit_opts = fit_opts.get(self.fit_method, {}) 




if Guess == None: 




Guess = getattr(self, "Guess", None) 




if self.dbg_params: 




self.dbg_params_log = [] 




if self.debug >= 5: 




print "fit: Input Params = ", getattr(self, "Params", None) 




self.last_fit = fit_func( 




Funct=self, 




Funct_hook=Funct_hook, 




x=x, y=y, dy=dy, 




Guess=Guess, 




Params=getattr(self, "Params", None), 




method=self.fit_method, 




opts=fit_opts, 




debug=self.debug, 




outfmt=0, # yield full result 




) 




if self.use_lmfit_method: 




if not hasattr(self, "Params"): 




self.Params = self.last_fit.params 




return self.last_fit['xopt'] 









def func_call_hook(self, C, x, y): 




"""Common hook function called when calling 'THE' 




function, e.g. for debugging purposes.""" 




from copy import copy 




if self.dbg_params: 




if not hasattr(self, "dbg_params_log"): 




self.dbg_params_log = [] 




self.dbg_params_log.append(copy(C)) 




#print "Call morse2_fit_func(%s, %s) > %s" % (C, x, y) 









def get_params(self, C, *names): 




"""Special support function to extract the values (or 




representative objects) of the parameters contained in 'C', 




the list of parameters. 









In the legacy case, C is simply a tuple/list of numbers. 









In the lmfit case, C is a Parameters object. 




""" 




try: 




from lmfit import Parameters 




# new way: using lmfit.Parameters: 




if isinstance(C, Parameters): 




return tuple(C[k].value for k in names) 




except: 




pass 









# old way: using positional parameters 




return tuple(C) 









@property 




def use_lmfit_method(self): 




return self.fit_method.startswith("lmfit:") 









@staticmethod 




def domain_array(x): 




"""Creates a domain array (x) for nonlinear fitting. 




Also accomodates a common "mistake" for 1D domain by making it 




correctly 2D in shape. 




""" 




x = numpy.asarray(x) 




if len(x.shape) == 1: 




# fix common "mistake" for 1D domain: make it 2D 




x = x.reshape((1, x.shape[0])) 




return x 













