def multi_1dgauss_mpfit(xax, data, ngauss=1, err=None, params=None, fixed=None, limitedmin=None, limitedmax=None, minpars=None, maxpars=None, force_Sigma_bounds=False, factor_Chi2=1.01, verbose=True, veryverbose=False, linear_method="nnls", minSigma=None, maxSigma=None, mpfitprint=mpfitprint(), **fcnargs): """ An improvement on gaussfit. Lets you fit multiple 1D gaussians. :param xax: x axis :param data: count axis :param ngauss: How many gaussians to fit? Default 1 :param err: error corresponding to data :param params: Fit parameters -- [width] * ngauss If len(params) == 0, ngauss will be set to len(params) These parameters need to have length = ngauss. If ngauss > 1 and length = 1 , they will be replicated ngauss times, otherwise they will be reset to defaults: :param fixed: Is parameter fixed? :param limitedmin/minpars: set lower limits on each parameter (default: width>0) :param limitedmax/maxpars: set upper limits on each parameter :param force_Sigma_bounds: force the Sigmas to be within the radii range with some margin default to True :param factor_Chi2: if one Gaussian contribute less than (factor-1) to the Chi2, we remove it If set to 1, it means only zero Gaussians will be removed If set to default=1.01, it means any Gaussian contributing to less than 1% will be removed :param minSigma, maxSigma: default to None but can be set for bounds for Sigma :param linearmethod: Method used to solve the linear part of the problem Two methods are implemented: "nnls" -> NNLS (default, included in scipy) "bvls" -> LLSP/BVLS in openopt (only if available) The variable Exist_OpenOpt is (internally) set to True if available :param **fcnargs: Will be passed to MPFIT, you can for example use xtol, gtol, ftol, quiet :param verbose: self-explanatory :param veryverbose: self-explanatory :return Fit parameters: :return Model: :return Fit errors: :return chi2: """ import copy ## Set up some default parameters for mpfit if "xtol" not in fcnargs.keys() : fcnargs["xtol"] = 1.e-10 if "gtol" not in fcnargs.keys() : fcnargs["gtol"] = 1.e-10 if "ftol" not in fcnargs.keys() : fcnargs["ftol"] = 1.e-10 if "quiet" not in fcnargs.keys() : fcnargs["quiet"] = True ## Checking the method used for the linear part if linear_method == "bvls" and not Exist_OpenOpt : print "WARNING: you selected BVLS, but OpenOpt is not installed" print "WARNING: we will therefore use NNLS instead" linear_method == "nnls" ## Checking if the linear_method is implemented if linear_method.lower() not in dic_linear_methods.keys(): print "ERROR: you should use one of the following linear_method: ", dic_linear_methods.keys() return 0, 0, 0, 0 f_Get_Iamp = dic_linear_methods[linear_method.lower()] ## If no coordinates is given, create them if xax is None: xax = np.arange(len(data)) if not isinstance(xax,np.ndarray): xax = (np.asarray(xax)).ravel() sxax = xax[xax != 0] min_xax = np.min(sxax) max_xax = np.max(sxax) # DlSigma = 0.5 * np.log10(max_xax / min_xax) / ngauss DlSigma = 0.02 lminS = np.log10(min_xax) lmaxS = np.log10(max_xax) if minSigma is None : minSigma = min_xax if maxSigma is None : maxSigma = max_xax / sqrt(2.) if isinstance(params,np.ndarray): params=params.tolist() if params is not None : if (len(params) > ngauss): ngauss = len(params) if verbose : print "WARNING: Your input parameters do not fit the Number of input Gaussians" print "WARNING: the new number of input Gaussians is: ", ngauss ## if no input parameters are given, we set up the guess as a log spaced sigma between min and max default_params = np.logspace(lminS + DlSigma, lmaxS - DlSigma, ngauss) newdefault_minpars = copy.copy(default_minpars) newdefault_maxpars = copy.copy(default_maxpars) if force_Sigma_bounds : newdefault_minpars[0] = minSigma newdefault_maxpars[0] = maxSigma else : newdefault_minpars[0] = minSigma / 100. newdefault_maxpars[0] = maxSigma * 100. ## Set up the default parameters if needed params = set_parameters_and_default_1D(params, default_params, ngauss) fixed = set_parameters_and_default_1D(fixed, default_fixed, ngauss) limitedmin = set_parameters_and_default_1D(limitedmin, default_limitedmin, ngauss) limitedmax = set_parameters_and_default_1D(limitedmax, default_limitedmax, ngauss) minpars = set_parameters_and_default_1D(minpars, newdefault_minpars, ngauss) maxpars = set_parameters_and_default_1D(maxpars, newdefault_maxpars, ngauss) ## ------------------------------------------------------------------------------------- ## mpfit function which returns the residual from the best fit N 1D Gaussians ## Parameters are just sigma,q,pa - the amplitudes are optimised at each step ## Two versions are available depending on whether BVLS or NNLS is used (and available) ## ------------------------------------------------------------------------------------- def mpfitfun(p, fjac=None, x=None, err=None, data=None, f_Get_Iamp=None): Iamp = f_Get_Iamp(p, x, data) newp = (np.vstack((Iamp, p.transpose()))).transpose() if err is None : return [0,fitn1dgauss_residuals(newp, x, data)] else : return [0,fitn1dgauss_residuals_err(newp, x, data, err)] ## Information about the parameters if veryverbose : print "------------------" print "GUESS: Sig " print "------------------" for i in xrange(ngauss) : print "GAUSS %02d: %8.3e"%(i+1, params[i]) print "--------------------------------------" ## Information about the parameters parnames = {0:"SIGMA"} parinfo = [ {'n':ii, 'value':params[ii], 'limits':[minpars[ii],maxpars[ii]], 'limited':[limitedmin[ii],limitedmax[ii]], 'fixed':fixed[ii], 'parname':parnames[0]+str(ii+1), 'error':ii} for ii in xrange(len(params)) ] ## Fit with mpfit of q, sigma, pa on xax, yax, and data (+err) fa = {'x': xax, 'data': data, 'err':err, 'f_Get_Iamp':f_Get_Iamp} if verbose: print "------ Starting the minimisation -------" result = mpfit(mpfitfun, functkw=fa, iterfunct=mpfitprint, nprint=1, parinfo=parinfo, **fcnargs) ## Getting these best fit values into the dictionnary bestparinfo = [ {'n':ii, 'value':result.params[ii], 'limits':[minpars[ii],maxpars[ii]], 'limited':[limitedmin[ii],limitedmax[ii]], 'fixed':fixed[ii], 'parname':parnames[0]+str(ii), 'error':ii} for ii in xrange(len(result.params)) ] ## Recompute the best amplitudes to output the right parameters ## And renormalising them Iamp = f_Get_Iamp(result.params, xax, data) Ibestpar_array = (np.vstack((Iamp, result.params.transpose()))).transpose() ## Getting rid of the non-relevant Gaussians ## If parameters factor_Chi2 is set we use it as a threshold to remove gaussians ## Otherwise we just remove the zeros if err is None : nerr = np.ones_like(data) else : nerr = err ## First get the Chi2 from this round bestChi2 = fitn1dgauss_chi2_err(Ibestpar_array, xax, data, nerr) result.ind = range(ngauss) k = 0 Removed_Gaussians = [] for i in xrange(ngauss) : ## Derive the Chi2 WITHOUT the ith Gaussian newChi2 = fitn1dgauss_chi2_err(np.delete(Ibestpar_array, i, 0), xax, data, nerr) ## If this Chi2 is smaller than factor_Chi2 times the best value, then remove ## It just means that Gaussian is not an important contributor if newChi2 <= factor_Chi2 * bestChi2 : val = bestparinfo.pop(k) result.ind.pop(k) Removed_Gaussians.append(i+1) else : k += 1 if veryverbose : if len(Removed_Gaussians) != 0 : print "WARNING Removed Gaussians ", Removed_Gaussians print "WARNING: (not contributing enough to the fit)" ngauss = len(result.ind) ## New minimisation after removing all the non relevant Gaussians newresult = mpfit(mpfitfun, functkw=fa, iterfunct=mpfitprint, nprint=1, parinfo=bestparinfo, **fcnargs) newresult.ind = range(ngauss) bestfit_params = newresult.params ## We add the Amplitudes to the array and renormalise them Iamp = f_Get_Iamp(bestfit_params, xax, data) Ibestfit_params = (np.vstack((Iamp, bestfit_params.transpose()))).transpose() Ibestfit_params[:,0] /= (sq2pi * Ibestfit_params[:,1]) ## And we sort them with Sigma Ibestfit_params = Ibestfit_params[Ibestfit_params[:,1].argsort()] if newresult.status == 0: raise Exception(newresult.errmsg) if verbose : print "=============================" print "FIT: Imax Sig " print "=============================" for i in xrange(ngauss) : print "GAUSS %02d: %8.3e %8.3f"%(i+1, Ibestfit_params[i,0], Ibestfit_params[i,1]) print "Chi2: ",newresult.fnorm," Reduced Chi2: ",newresult.fnorm/len(data) return Ibestfit_params, newresult, n_centred_onedgaussian_Imax(pars=Ibestfit_params)(xax)
def multi_2dgauss_mpfit(xax, yax, data, ngauss=1, err=None, params=None, paramsPSF=None, fixed=None, limitedmin=None, limitedmax=None, minpars=None, maxpars=None, force_Sigma_bounds=True, factor_Chi2=1.01, verbose=True, veryverbose=False, linear_method="nnls", default_q=0.3, default_PA=0.0, samePA=True, minSigma=None, maxSigma=None, mpfitprint=_mpfitprint(), **fcnargs): """ An improvement on gaussfit. Lets you fit multiple 2D gaussians. Inputs: xax - x axis yax - y axis data - count axis ngauss - How many gaussians to fit? Default 1 err - error corresponding to data These parameters need to have length = 3*ngauss. If ngauss > 1 and length = 3, they will be replicated ngauss times, otherwise they will be reset to defaults: params - Fit parameters: [width, axis ratio, pa] * ngauss If len(params) % 3 == 0, ngauss will be set to len(params) / 3 fixed - Is parameter fixed? limitedmin/minpars - set lower limits on each parameter (default: width>0) limitedmax/maxpars - set upper limits on each parameter force_Sigma_bounds: force the Sigmas to be within the radii range with some margin default to True factor_Chi2 : if one Gaussian contribute less than (factor-1) to the Chi2, we remove it If set to 1, it means only zero Gaussians will be removed If set to default=1.01, it means any Gaussian contributing to less than 1% will be removed minSigma, maxSigma: default to None but can be set for bounds for Sigma linearmethod: Method used to solve the linear part of the problem Two methods are implemented: "nnls" -> NNLS (default, included in scipy) "bvls" -> LLSP/BVLS in openopt (only if available) The variable Exist_OpenOpt is (internally) set to True if available **fcnargs - Will be passed to MPFIT, you can for example use: xtol, gtol, ftol, quiet verbose - self-explanatory veryverbose - self-explanatory Returns: Fit parameters Model Fit errors chi2 """ import copy ## Set up some default parameters for mpfit if "xtol" not in fcnargs.keys() : fcnargs["xtol"] = 1.e-7 if "gtol" not in fcnargs.keys() : fcnargs["gtol"] = 1.e-7 if "ftol" not in fcnargs.keys() : fcnargs["ftol"] = 1.e-7 if "quiet" not in fcnargs.keys() : fcnargs["quiet"] = True ## Checking the method used for the linear part if linear_method == "bvls" and not Exist_OpenOpt : print "WARNING: you selected BVLS, but OpenOpt is not installed" print "WARNING: we will therefore use NNLS instead" linear_method == "nnls" ## Checking if the linear_method is implemented if linear_method.lower() not in dic_linear_methods.keys(): print "ERROR: you should use one of the following linear_method: ", dic_linear_methods.keys() return 0, 0, 0, 0 f_Get_Iamp = dic_linear_methods[linear_method.lower()] ## If no coordinates is given, create them if xax is None: xax = np.arange(len(data)) if yax is None: yax = np.arange(len(data)) if not isinstance(xax,np.ndarray): xax = np.asarray(xax) if not isinstance(yax,np.ndarray): yax = np.asarray(yax) if not isinstance(data,np.ndarray): data = np.asarray(data) xax = xax.ravel() yax = yax.ravel() datashape = data.shape data = data.ravel() ## Polar coordinates r, theta = convert_xy_to_polar(xax, yax) selxy = (xax != 0) & (yax != 0) rin = sqrt(xax[selxy]**2+yax[selxy]**2) if minSigma is None : minSigma = np.min(rin) if maxSigma is None : maxSigma = np.max(rin) / sqrt(SLOPE_outer) lminSigma = np.log10(minSigma) lmaxSigma = np.log10(maxSigma) DlSigma = 0.5 * (lmaxSigma - lminSigma) / ngauss if isinstance(params,np.ndarray): params=params.tolist() if params is not None : if len(params) != ngauss and (len(params) / 3) > ngauss: ngauss = len(params) / 3 if verbose : print "WARNING: Your input parameters do not fit the Number of input Gaussians" print "WARNING: the new number of input Gaussians is: ", ngauss ## Extracting the parameters for the PSF and normalising the Imax for integral = 1 if paramsPSF is None : paramsPSF = _default_parPSF paramsPSF = norm_PSFParam(paramsPSF) ## if no input parameters are given, we set up the guess as a log spaced sigma between min and max default_params = np.concatenate((np.log10(np.logspace(lminSigma + DlSigma, lmaxSigma - DlSigma, ngauss)), \ np.array([default_q]*ngauss), np.array([default_PA]*ngauss))).reshape(3,ngauss).transpose().ravel() newdefault_minpars = copy.copy(default_minpars) newdefault_maxpars = copy.copy(default_maxpars) if force_Sigma_bounds : newdefault_minpars[0] = lminSigma newdefault_maxpars[0] = lmaxSigma else : newdefault_minpars[0] = lminSigma - np.log10(2.) newdefault_maxpars[0] = lmaxSigma + np.log10(2.) ## Set up the default parameters if needed params = set_parameters_and_default(params, default_params, ngauss) fixed = set_parameters_and_default(fixed, default_fixed, ngauss) limitedmin = set_parameters_and_default(limitedmin, default_limitedmin, ngauss) limitedmax = set_parameters_and_default(limitedmax, default_limitedmax, ngauss) minpars = set_parameters_and_default(minpars, newdefault_minpars, ngauss) maxpars = set_parameters_and_default(maxpars, newdefault_maxpars, ngauss) ## ------------------------------------------------------------------------------------- ## mpfit function which returns the residual from the best fit N 2D Gaussians ## Parameters are just sigma,q,pa - the amplitudes are optimised at each step ## Two versions are available depending on whether BVLS or NNLS is used (and available) ## ------------------------------------------------------------------------------------- def mpfitfun(p, parPSF, fjac=None, r=None, theta=None, err=None, data=None, f_Get_Iamp=None, samePA=False): if samePA : p = regrow_PA(p) nGnorm, Iamp = f_Get_Iamp(p, parPSF, r, theta, data) if err is None : return [0,fitn2dgauss_residuals1(nGnorm, Iamp)] else : return [0,fitn2dgauss_residuals_err1(err, nGnorm, Iamp)] # newp = (np.vstack((Iamp, p.reshape(ngauss,3).transpose()))).transpose() # if err is None : # return [0,fitn2dgauss_residuals(newp, parPSF, r, theta, data)] # else : # return [0,fitn2dgauss_residuals_err(newp, parPSF, r, theta, data, err)] ## ------------------------------------------------------------------------------------- ## Information about the parameters if verbose : print "--------------------------------------" print "GUESS: Sig Q PA" print "--------------------------------------" for i in xrange(ngauss) : print "GAUSS %02d: %8.3e %8.3f %8.3f"%(i+1, 10**(params[3*i]), params[3*i+1], params[3*i+2]) print "--------------------------------------" ## Information about the parameters parnames = {0:"LOGSIGMA",1:"AXIS RATIO",2:"POSITION ANGLE"} parinfo = [ {'n':ii, 'value':params[ii], 'limits':[minpars[ii],maxpars[ii]], 'limited':[limitedmin[ii],limitedmax[ii]], 'fixed':fixed[ii], 'parname':parnames[ii%3]+str(ii/3+1)} for ii in xrange(len(params)) ] ## If samePA we remove all PA parameters except the last one ## We could use the 'tied' approach but we prefer setting up just one parameter if samePA : parinfo = shrink_PA(parinfo) ## Fit with mpfit of q, sigma, pa on xax, yax, and data (+err) fa = {'parPSF':paramsPSF, 'r': r, 'theta': theta, 'data': data, 'err':err, 'f_Get_Iamp':f_Get_Iamp, 'samePA':samePA} result = mpfit(mpfitfun, functkw=fa, iterfunct=mpfitprint, nprint=10, parinfo=parinfo, **fcnargs) ## Getting these best fit values into the dictionnary if samePA : result.params = regrow_PA(result.params) bestparinfo = [ {'n':ii, 'value':result.params[ii], 'limits':[minpars[ii],maxpars[ii]], 'limited':[limitedmin[ii],limitedmax[ii]], 'fixed':fixed[ii], 'parname':parnames[ii%3]+str(ii/3)} for ii in xrange(len(result.params)) ] ## Recompute the best amplitudes to output the right parameters ## And renormalising them nGnorm, Iamp = f_Get_Iamp(result.params, paramsPSF, r, theta, data) # Ibestpar_array = (np.vstack((Iamp, result.params.reshape(ngauss,3).transpose()))).transpose() bestpar_array = result.params.reshape(ngauss,3) ## Getting rid of the non-relevant Gaussians ## If parameters factor_Chi2 is set we use it as a threshold to remove gaussians ## Otherwise we just remove the zeros if err is None : nerr = np.ones_like(data) else : nerr = err ## First get the Chi2 from this round # bestChi2 = fitn2dgauss_chi2_err(Ibestpar_array, paramsPSF, r, theta, data, nerr) bestChi2 = np.sum(fitn2dgauss_residuals1(nGnorm, Iamp)**2) result.ind = range(ngauss) k = 0 Removed_Gaussians = [] for i in xrange(ngauss) : ## Derive the Chi2 WITHOUT the ith Gaussian new_nGnorm, new_Iamp = f_Get_Iamp(np.delete(bestpar_array, i, 0), paramsPSF, r, theta, data) newChi2 = np.sum(fitn2dgauss_residuals1(new_nGnorm, new_Iamp)**2) # newChi2 = fitn2dgauss_chi2_err(np.delete(Ibestpar_array, i, 0), paramsPSF, r, theta, data, nerr) ## If this Chi2 is smaller than factor_Chi2 times the best value, then remove ## It just means that Gaussian is not an important contributor if newChi2 <= factor_Chi2 * bestChi2 : val = bestparinfo.pop(3*k) val = bestparinfo.pop(3*k) val = bestparinfo.pop(3*k) result.ind.pop(k) Removed_Gaussians.append(i+1) else : k += 1 if veryverbose : if len(Removed_Gaussians) != 0 : print "WARNING Removed Gaussians ", Removed_Gaussians print "WARNING: (not contributing enough to the fit)" ngauss = len(result.ind) ## New minimisation after removing all the non relevant Gaussians if samePA : bestparinfo = shrink_PA(bestparinfo) newresult = mpfit(mpfitfun, functkw=fa, iterfunct=mpfitprint, nprint=10, parinfo=bestparinfo, **fcnargs) newresult.ind = range(ngauss) if samePA : newresult.params = regrow_PA(newresult.params) bestfit_params = newresult.params.reshape(ngauss, 3) ## We add the Amplitudes to the array and renormalise them nGnorm, Iamp = f_Get_Iamp(bestfit_params, paramsPSF, r, theta, data) Ibestfit_params = (np.vstack((Iamp, bestfit_params.transpose()))).transpose() ## Going back to sigma from logsigma Ibestfit_params[:,1] = 10**(Ibestfit_params[:,1]) Ibestfit_params[:,0] /= (2.0 * Ibestfit_params[:,1]**2 * Ibestfit_params[:,2] * pi) ## And we sort them with Sigma Ibestfit_params = Ibestfit_params[Ibestfit_params[:,1].argsort()] if newresult.status == 0: raise Exception(newresult.errmsg) if verbose : print "==================================================" print "FIT: Imax Sig Q PA" print "==================================================" for i in xrange(ngauss) : print "GAUSS %02d: %8.3e %8.3f %8.3f %8.3f"%(i+1, Ibestfit_params[i,0], Ibestfit_params[i,1], Ibestfit_params[i,2], Ibestfit_params[i,3]) print "Chi2: ",newresult.fnorm," Reduced Chi2: ",newresult.fnorm/len(data) return Ibestfit_params, newresult, n_centred_twodgaussian_Imax(pars=Ibestfit_params, parPSF=paramsPSF)(r, theta).reshape(datashape)
def multi_1dgauss_mpfit(xax, data, ngauss=1, err=None, params=None, fixed=None, limitedmin=None, limitedmax=None, minpars=None, maxpars=None, force_Sigma_bounds=False, factor_Chi2=1.01, verbose=True, veryverbose=False, linear_method="nnls", minSigma=None, maxSigma=None, mpfitprint=mpfitprint(), **fcnargs): """ An improvement on gaussfit. Lets you fit multiple 1D gaussians. :param xax: x axis :param data: count axis :param ngauss: How many gaussians to fit? Default 1 :param err: error corresponding to data :param params: Fit parameters -- [width] * ngauss If len(params) == 0, ngauss will be set to len(params) These parameters need to have length = ngauss. If ngauss > 1 and length = 1 , they will be replicated ngauss times, otherwise they will be reset to defaults: :param fixed: Is parameter fixed? :param limitedmin/minpars: set lower limits on each parameter (default: width>0) :param limitedmax/maxpars: set upper limits on each parameter :param force_Sigma_bounds: force the Sigmas to be within the radii range with some margin default to True :param factor_Chi2: if one Gaussian contribute less than (factor-1) to the Chi2, we remove it If set to 1, it means only zero Gaussians will be removed If set to default=1.01, it means any Gaussian contributing to less than 1% will be removed :param minSigma, maxSigma: default to None but can be set for bounds for Sigma :param linearmethod: Method used to solve the linear part of the problem Two methods are implemented: "nnls" -> NNLS (default, included in scipy) "bvls" -> LLSP/BVLS in openopt (only if available) The variable Exist_OpenOpt is (internally) set to True if available :param **fcnargs: Will be passed to MPFIT, you can for example use xtol, gtol, ftol, quiet :param verbose: self-explanatory :param veryverbose: self-explanatory :return Fit parameters: :return Model: :return Fit errors: :return chi2: """ import copy ## Set up some default parameters for mpfit if "xtol" not in fcnargs.keys(): fcnargs["xtol"] = 1.e-10 if "gtol" not in fcnargs.keys(): fcnargs["gtol"] = 1.e-10 if "ftol" not in fcnargs.keys(): fcnargs["ftol"] = 1.e-10 if "quiet" not in fcnargs.keys(): fcnargs["quiet"] = True ## Checking the method used for the linear part if linear_method == "bvls" and not Exist_OpenOpt: print "WARNING: you selected BVLS, but OpenOpt is not installed" print "WARNING: we will therefore use NNLS instead" linear_method == "nnls" ## Checking if the linear_method is implemented if linear_method.lower() not in dic_linear_methods.keys(): print "ERROR: you should use one of the following linear_method: ", dic_linear_methods.keys( ) return 0, 0, 0, 0 f_Get_Iamp = dic_linear_methods[linear_method.lower()] ## If no coordinates is given, create them if xax is None: xax = np.arange(len(data)) if not isinstance(xax, np.ndarray): xax = (np.asarray(xax)).ravel() sxax = xax[xax != 0] min_xax = np.min(sxax) max_xax = np.max(sxax) # DlSigma = 0.5 * np.log10(max_xax / min_xax) / ngauss DlSigma = 0.02 lminS = np.log10(min_xax) lmaxS = np.log10(max_xax) if minSigma is None: minSigma = min_xax if maxSigma is None: maxSigma = max_xax / sqrt(2.) if isinstance(params, np.ndarray): params = params.tolist() if params is not None: if (len(params) > ngauss): ngauss = len(params) if verbose: print "WARNING: Your input parameters do not fit the Number of input Gaussians" print "WARNING: the new number of input Gaussians is: ", ngauss ## if no input parameters are given, we set up the guess as a log spaced sigma between min and max default_params = np.logspace(lminS + DlSigma, lmaxS - DlSigma, ngauss) newdefault_minpars = copy.copy(default_minpars) newdefault_maxpars = copy.copy(default_maxpars) if force_Sigma_bounds: newdefault_minpars[0] = minSigma newdefault_maxpars[0] = maxSigma else: newdefault_minpars[0] = minSigma / 100. newdefault_maxpars[0] = maxSigma * 100. ## Set up the default parameters if needed params = set_parameters_and_default_1D(params, default_params, ngauss) fixed = set_parameters_and_default_1D(fixed, default_fixed, ngauss) limitedmin = set_parameters_and_default_1D(limitedmin, default_limitedmin, ngauss) limitedmax = set_parameters_and_default_1D(limitedmax, default_limitedmax, ngauss) minpars = set_parameters_and_default_1D(minpars, newdefault_minpars, ngauss) maxpars = set_parameters_and_default_1D(maxpars, newdefault_maxpars, ngauss) ## ------------------------------------------------------------------------------------- ## mpfit function which returns the residual from the best fit N 1D Gaussians ## Parameters are just sigma,q,pa - the amplitudes are optimised at each step ## Two versions are available depending on whether BVLS or NNLS is used (and available) ## ------------------------------------------------------------------------------------- def mpfitfun(p, fjac=None, x=None, err=None, data=None, f_Get_Iamp=None): Iamp = f_Get_Iamp(p, x, data) newp = (np.vstack((Iamp, p.transpose()))).transpose() if err is None: return [0, fitn1dgauss_residuals(newp, x, data)] else: return [0, fitn1dgauss_residuals_err(newp, x, data, err)] ## Information about the parameters if veryverbose: print "------------------" print "GUESS: Sig " print "------------------" for i in xrange(ngauss): print "GAUSS %02d: %8.3e" % (i + 1, params[i]) print "--------------------------------------" ## Information about the parameters parnames = {0: "SIGMA"} parinfo = [{ 'n': ii, 'value': params[ii], 'limits': [minpars[ii], maxpars[ii]], 'limited': [limitedmin[ii], limitedmax[ii]], 'fixed': fixed[ii], 'parname': parnames[0] + str(ii + 1), 'error': ii } for ii in xrange(len(params))] ## Fit with mpfit of q, sigma, pa on xax, yax, and data (+err) fa = {'x': xax, 'data': data, 'err': err, 'f_Get_Iamp': f_Get_Iamp} if verbose: print "------ Starting the minimisation -------" result = mpfit(mpfitfun, functkw=fa, iterfunct=mpfitprint, nprint=1, parinfo=parinfo, **fcnargs) ## Getting these best fit values into the dictionnary bestparinfo = [{ 'n': ii, 'value': result.params[ii], 'limits': [minpars[ii], maxpars[ii]], 'limited': [limitedmin[ii], limitedmax[ii]], 'fixed': fixed[ii], 'parname': parnames[0] + str(ii), 'error': ii } for ii in xrange(len(result.params))] ## Recompute the best amplitudes to output the right parameters ## And renormalising them Iamp = f_Get_Iamp(result.params, xax, data) Ibestpar_array = (np.vstack((Iamp, result.params.transpose()))).transpose() ## Getting rid of the non-relevant Gaussians ## If parameters factor_Chi2 is set we use it as a threshold to remove gaussians ## Otherwise we just remove the zeros if err is None: nerr = np.ones_like(data) else: nerr = err ## First get the Chi2 from this round bestChi2 = fitn1dgauss_chi2_err(Ibestpar_array, xax, data, nerr) result.ind = range(ngauss) k = 0 Removed_Gaussians = [] for i in xrange(ngauss): ## Derive the Chi2 WITHOUT the ith Gaussian newChi2 = fitn1dgauss_chi2_err(np.delete(Ibestpar_array, i, 0), xax, data, nerr) ## If this Chi2 is smaller than factor_Chi2 times the best value, then remove ## It just means that Gaussian is not an important contributor if newChi2 <= factor_Chi2 * bestChi2: val = bestparinfo.pop(k) result.ind.pop(k) Removed_Gaussians.append(i + 1) else: k += 1 if veryverbose: if len(Removed_Gaussians) != 0: print "WARNING Removed Gaussians ", Removed_Gaussians print "WARNING: (not contributing enough to the fit)" ngauss = len(result.ind) ## New minimisation after removing all the non relevant Gaussians newresult = mpfit(mpfitfun, functkw=fa, iterfunct=mpfitprint, nprint=1, parinfo=bestparinfo, **fcnargs) newresult.ind = range(ngauss) bestfit_params = newresult.params ## We add the Amplitudes to the array and renormalise them Iamp = f_Get_Iamp(bestfit_params, xax, data) Ibestfit_params = (np.vstack( (Iamp, bestfit_params.transpose()))).transpose() Ibestfit_params[:, 0] /= (sq2pi * Ibestfit_params[:, 1]) ## And we sort them with Sigma Ibestfit_params = Ibestfit_params[Ibestfit_params[:, 1].argsort()] if newresult.status == 0: raise Exception(newresult.errmsg) if verbose: print "=============================" print "FIT: Imax Sig " print "=============================" for i in xrange(ngauss): print "GAUSS %02d: %8.3e %8.3f" % (i + 1, Ibestfit_params[i, 0], Ibestfit_params[i, 1]) print "Chi2: ", newresult.fnorm, " Reduced Chi2: ", newresult.fnorm / len( data) resultMultiGaussian1D = BaseMultiGaussian1D(Ibestfit_params[:, 0], Ibestfit_params[:, 1]) return resultMultiGaussian1D, newresult, resultMultiGaussian1D.evaluate( xax)
def fitGH_mpfit(xax, data, degGH=2, err=None, params=None, fixed=None, limitedmin=None, limitedmax=None, minpars=None, maxpars=None, verbose=True, veryverbose=False, **fcnargs): """ Fitting routine for a Gauss-Hermite function using mpfit :param xax: x axis :param data: count axis :param err: error corresponding to data :param degGH: order of the Gauss-Hermite moments to fit. Must be >=2 If =2 (default), the programme will only fit a single Gaussian If > 2, it will add as many h_n as needed. :param params: Input fit parameters: Vgauss, Sgauss, h3, h4, h5... Length should correspond to degGH. If not, a guess will be used. :param fixed: Is parameter fixed? :param limitedmin/minpars: set lower limits on each parameter (default: width>0) :param limitedmax/maxpars: set upper limits on each parameter :param iprint: if > 0 and verbose, print every iprint iterations of lmfit. default is 50 :param lmfit_method: method to pass on to lmfit ('leastsq', 'lbfgsb', 'anneal'). Default is leastsq (most efficient for the problem) :param **fcnargs: Will be passed to MPFIT, you can for example use xtol, gtol, ftol, quiet :param verbose: self-explanatory :param veryverbose: self-explanatory :returns Fit parameters: :returns Model: :returns Fit errors: :returns chi2: """ ## Set up some default parameters for mpfit if "xtol" not in fcnargs.keys() : fcnargs["xtol"] = 1.e-10 if "gtol" not in fcnargs.keys() : fcnargs["gtol"] = 1.e-10 if "ftol" not in fcnargs.keys() : fcnargs["ftol"] = 1.e-10 if "quiet" not in fcnargs.keys() : fcnargs["quiet"] = True ## If no coordinates is given, create them and use pixel as a unit ## The x axis will be from 0 to N-1, N being the number of data points ## which will be considered as "ordered" on a regular 1d grid if xax is None: xax = np.arange(len(data)) else : ## Make sure that x is an array (if given) if not isinstance(xax,np.ndarray): xax = (np.asarray(xax)).ravel() ## Compute the moments of the distribution for later purposes ## This provides Imax, V and Sigma from 1d moments momProf = moment1d(xax, data) if isinstance(params,np.ndarray): params=params.tolist() if params is not None : if (len(params) > degGH): print "ERROR: input parameter array (params) is larger than expected" print "ERROR: It is %d while it should be smaller or equal to %s (degGH)", len(params), degGH return 0., 0., 0., 0. elif (len(params) < degGH): if verbose : print "WARNING: Your input parameters do not fit the Degre set up for the GH moments" print "WARNING: the given value of degGH (%d) will be kept ", degGH print "WARNING: A guess will be used for the input fitting parameters " default_params= np.zeros(degGH, dtype=np.float64) + 0.02 default_params[:2] = momProf[1:] default_minpars= np.zeros(degGH, dtype=np.float64) - 0.2 default_minpars[:2] = [np.min(xax), np.min(np.diff(xax)) / 3.] default_maxpars= np.zeros(degGH, dtype=np.float64) + 0.2 default_maxpars[:2] = [np.max(xax), (np.max(xax) - np.min(xax)) / 3.] default_limitedmin = [True] * degGH default_limitedmax = [True] * degGH default_fixed = [False] * degGH ## Set up the default parameters if needed params = _set_GHparameters(params, default_params, degGH) fixed = _set_GHparameters(fixed, default_fixed, degGH) limitedmin = _set_GHparameters(limitedmin, default_limitedmin, degGH) limitedmax = _set_GHparameters(limitedmax, default_limitedmax, degGH) minpars = _set_GHparameters(minpars, default_minpars, degGH) maxpars = _set_GHparameters(maxpars, default_maxpars, degGH) ## ------------------------------------------------------------------------------------- ## mpfit function which returns the residual from the best fit Gauss-Hermite ## Parameters are just V, Sigma, H3,... Hn - the amplitudes are optimised at each step ## ------------------------------------------------------------------------------------- def mpfitfun(p, fjac=None, x=None, err=None, data=None): GH = np.concatenate(([1.0], p)) ufit = GaussHermite(x, GH) GH[0] = _Solve_Amplitude(data, ufit, err) if err is None : return [0, _fitGH_residuals(GH, x, data)] else : return [0, _fitGH_residuals_err(GH, x, data, err)] ## ------------------------------------------------------------------------------------- ## Printing routine for mpfit ## ------------------------------------------------------------------------------------- def mpfitprint(mpfitfun, p, iter, fnorm, functkw=None, parinfo=None, quiet=0, dof=None) : print "Chi2 = ", fnorm ## ------------------------------------------------------------------------------------- ## Information about the parameters parnames = {0:"V", 1:"S"} for i in xrange(2, degGH) : parnames[i] = "H_%02d"%(i+1) ## Information about the parameters if veryverbose : print "--------------------------------------" print "GUESS: " print "------" for i in xrange(degGH) : print " %s : %8.3f"%(parnames[i], params[i]) print "--------------------------------------" parinfo = [ {'n':ii, 'value':params[ii], 'limits':[minpars[ii],maxpars[ii]], 'limited':[limitedmin[ii],limitedmax[ii]], 'fixed':fixed[ii], 'parname':parnames[ii], 'error':ii} for ii in xrange(len(params)) ] ## Fit with mpfit of q, sigma, pa on xax, yax, and data (+err) fa = {'x': xax, 'data': data, 'err':err} if verbose: print "------ Starting the minimisation -------" result = mpfit(mpfitfun, functkw=fa, iterfunct=mpfitprint, nprint=1, parinfo=parinfo, **fcnargs) ## Recompute the best amplitudes to output the right parameters ## And renormalising them GH = np.concatenate(([1.0], result.params)) ufit = GaussHermite(xax, GH) Iamp = _Solve_Amplitude(data, ufit, err) Ibestpar_array = np.concatenate(([Iamp], result.params)) if result.status == 0: raise Exception(result.errmsg) if verbose : print "=====" print "FIT: " print "=================================" print " I V Sig " print " %8.3f %8.3f %8.3f "%(Ibestpar_array[0], Ibestpar_array[1], Ibestpar_array[2]) print "=================================" for i in xrange(2, degGH) : print "GH %02d: %8.4f "%(i+1, Ibestpar_array[i+1]) print "=================================" print "Chi2: ",result.fnorm," Reduced Chi2: ",result.fnorm/len(data) return Ibestpar_array, result, GaussHermite(xax, Ibestpar_array)