def fit_blackbody(xdata, flux, guesses=(0, 0), err=None, blackbody_function=blackbody, quiet=True, **kwargs): """ guesses = Temperature, Arbitrary Scale OR Temperature, Beta, Arbitrary Scale """ def mpfitfun(x, y, err): if err is None: def f(p, fjac=None): return [ 0, (y - blackbody_function(x, *p, normalize=False, **kwargs)) ] else: def f(p, fjac=None): return [ 0, (y - blackbody_function( x, *p, normalize=False, **kwargs)) / err ] return f err = err if err is not None else flux * 0.0 + 1.0 mp = mpfit.mpfit(mpfitfun(xdata, flux, err), guesses, quiet=quiet) return mp
def multinh3fit(self, xax, data, err=None, parinfo=None, quiet=True, shh=True, debug=False, maxiter=200, use_lmfit=False, veryverbose=False, **kwargs): """ Fit multiple nh3 profiles (multiple can be 1) Inputs: xax - x axis data - y axis npeaks - How many nh3 profiles to fit? Default 1 (this could supersede onedgaussfit) err - error corresponding to data These parameters need to have length = 6*npeaks. If npeaks > 1 and length = 6, they will be replicated npeaks times, otherwise they will be reset to defaults: params - Fit parameters: [tkin, tex, ntot (or tau), width, offset, ortho fraction] * npeaks If len(params) % 6 == 0, npeaks will be set to len(params) / 6 fixed - Is parameter fixed? limitedmin/minpars - set lower limits on each parameter (default: width>0, Tex and Tkin > Tcmb) limitedmax/maxpars - set upper limits on each parameter parnames - default parameter names, important for setting kwargs in model ['tkin','tex','ntot','width','xoff_v','fortho'] quiet - should MPFIT output each iteration? shh - output final parameters? Returns: Fit parameters Model Fit errors chi2 """ if parinfo is None: parinfo = self.parinfo = self.make_parinfo(**kwargs) else: if isinstance(parinfo, ParinfoList): if not quiet: log.info("Using user-specified parinfo.") self.parinfo = parinfo else: if not quiet: log.info("Using something like a user-specified parinfo, but not.") self.parinfo = ParinfoList([p if isinstance(p,Parinfo) else Parinfo(p) for p in parinfo], preserve_order=True) fitfun_kwargs = dict((x,y) for (x,y) in kwargs.items() if x not in ('npeaks', 'params', 'parnames', 'fixed', 'limitedmin', 'limitedmax', 'minpars', 'maxpars', 'tied', 'max_tem_step')) if 'use_lmfit' in fitfun_kwargs: raise KeyError("use_lmfit was specified in a location where it " "is unacceptable") npars = len(parinfo)/self.npeaks self._validate_parinfo() def mpfitfun(x,y,err): if err is None: def f(p,fjac=None): return [0,(y-self.n_ammonia(pars=p, parnames=parinfo.parnames, **fitfun_kwargs)(x))] else: def f(p,fjac=None): return [0,(y-self.n_ammonia(pars=p, parnames=parinfo.parnames, **fitfun_kwargs)(x))/err] return f if veryverbose: log.info("GUESSES: ") log.info(str(parinfo)) #log.info "\n".join(["%s: %s" % (p['parname'],p['value']) for p in parinfo]) if use_lmfit: return self.lmfitter(xax, data, err=err, parinfo=parinfo, quiet=quiet, debug=debug) else: mp = mpfit(mpfitfun(xax,data,err), parinfo=parinfo, maxiter=maxiter, quiet=quiet, debug=debug) mpp = mp.params if mp.perror is not None: mpperr = mp.perror else: mpperr = mpp*0 chi2 = mp.fnorm if mp.status == 0: raise Exception(mp.errmsg) for i,p in enumerate(mpp): parinfo[i]['value'] = p parinfo[i]['error'] = mpperr[i] if not shh: log.info("Fit status: {0}".format(mp.status)) log.info("Fit message: {0}".format(mpfit_messages[mp.status])) log.info("Fit error message: {0}".format(mp.errmsg)) log.info("Final fit values: ") for i,p in enumerate(mpp): log.info(" ".join((parinfo[i]['parname'], str(p), " +/- ", str(mpperr[i])))) log.info(" ".join(("Chi2: ", str(mp.fnorm)," Reduced Chi2: ", str(mp.fnorm/len(data)), " DOF:", str(len(data)-len(mpp))))) self.mp = mp self.parinfo = parinfo self.mpp = self.parinfo.values self.mpperr = self.parinfo.errors self.mppnames = self.parinfo.names self.model = self.n_ammonia(pars=self.mpp, parnames=self.mppnames, **fitfun_kwargs)(xax) indiv_parinfo = [self.parinfo[jj*self.npars:(jj+1)*self.npars] for jj in xrange(len(self.parinfo)/self.npars)] modelkwargs = [ dict([(p['parname'].strip("0123456789").lower(),p['value']) for p in pi]) for pi in indiv_parinfo] self.tau_list = [ammonia(xax,return_tau=True,**mk) for mk in modelkwargs] return self.mpp,self.model,self.mpperr,chi2
def _baseline(self, spectrum, xarr=None, err=None, order=1, quiet=True, masktoexclude=None, powerlaw=False, xarr_fit_units='pixels', LoudDebug=False, renormalize='auto', zeroerr_is_OK=True, spline=False, **kwargs): """ Fit a baseline/continuum to a spectrum Parameters ---------- masktoexclude : boolean array True: will not be fit False: will be fit *if ALL are True, ALL pixels will be fit - just with silly weights* """ #if xmin == 'default': # if order <= 1 and mask is None: xmin = np.floor( spectrum.shape[-1]*0.1 ) # else: xmin = 0 #elif xmin is None: # xmin = 0 #if xmax == 'default': # if order <= 1 and mask is None: xmax = np.ceil( spectrum.shape[-1]*0.9 ) # else: xmax = spectrum.shape[-1] #elif xmax is None: # xmax = spectrum.shape[-1] if xarr is None: xarr = np.indices(spectrum.shape).squeeze() # A good alternate implementation of masking is to only pass mpfit the data # that is unmasked. That would require some manipulation above... if err is None: err = np.ones(spectrum.shape) else: # don't overwrite error err = err.copy() # assume anything with 0 error is GOOD if zeroerr_is_OK: err[err == 0] = 1. else: # flag it out! err[err == 0] = 1e10 #err[:xmin] = 1e10 #err[xmax:] = 1e10 if masktoexclude is not None: if masktoexclude.dtype.name != 'bool': masktoexclude = masktoexclude.astype('bool') err[masktoexclude] = 1e10 if LoudDebug: print "In _baseline: %i points masked out" % masktoexclude.sum() if (spectrum!=spectrum).sum() > 0: print "There is an error in baseline: some values are NaN" import pdb; pdb.set_trace() #xarrconv = xarr[xmin:xmax].as_unit(xarr_fit_units) OK = True-masktoexclude xarrconv = xarr.as_unit(xarr_fit_units) if powerlaw: # for powerlaw fitting, only consider positive data OK *= spectrum > 0 pguess = [np.median(spectrum[OK]),2.0] if LoudDebug: print "_baseline powerlaw Guesses: ",pguess def mpfitfun(data,err): #def f(p,fjac=None): return [0,np.ravel(((p[0] * (xarrconv[OK]-p[2])**(-p[1]))-data)/err)] # Logarithmic fitting: def f(p,fjac=None): #return [0, # np.ravel( (np.log10(data) - np.log10(p[0]) + p[1]*np.log10(xarrconv[OK]/p[2])) / (err/data) ) # ] return [0, np.ravel( (data - self.get_model(xarr=xarrconv[OK],baselinepars=p)) / (err/data) )] return f else: pguess = [0]*(order+1) if LoudDebug: print "_baseline Guesses: ",pguess def mpfitfun(data,err): def f(p,fjac=None): return [0,np.ravel((np.poly1d(p)(xarrconv[OK])-data)/err)] return f #scalefactor = 1.0 #if renormalize in ('auto',True): # datarange = spectrum.max() - spectrum.min() # if abs(datarange) < 1e-9 or abs(datarange) > 1e9: # scalefactor = np.median(np.abs(self.spectrum)) # print "BASELINE: Renormalizing data by factor %e to improve fitting procedure" % scalefactor # spectrum /= scalefactor # err /= scalefactor import pyspeckit.mpfit as mpfit mp = mpfit.mpfit(mpfitfun(spectrum[OK],err[OK]),xall=pguess,quiet=quiet) # mpfit doesn't need to take kwargs, I think ,**kwargs) if np.isnan(mp.fnorm): raise ValueError("chi^2 is NAN in baseline fitting") fitp = mp.params if powerlaw: #bestfit = (fitp[0]*(xarrconv)**(-fitp[1])).squeeze() bestfit = (fitp[0]*(xarrconv)**(-fitp[1])).squeeze() else: bestfit = np.poly1d(fitp)(xarrconv).squeeze() return bestfit,fitp
def fitter(self, xax, data, err=None, quiet=True, veryverbose=False, debug=False, parinfo=None, **kwargs): """ Run the fitter using mpfit. kwargs will be passed to _make_parinfo and mpfit. Parameters ---------- xax : SpectroscopicAxis The X-axis of the spectrum data : ndarray The data to fit err : ndarray (optional) The error on the data. If unspecified, will be uniform unity parinfo : ParinfoList The guesses, parameter limits, etc. See `pyspeckit.spectrum.parinfo` for details quiet : bool pass to mpfit. If False, will print out the parameter values for each iteration of the fitter veryverbose : bool print out a variety of mpfit output parameters debug : bool raise an exception (rather than a warning) if chi^2 is nan """ if parinfo is None: parinfo, kwargs = self._make_parinfo(debug=debug, **kwargs) else: log.debug("Using user-specified parinfo dict") # clean out disallowed kwargs (don't want to pass them to mpfit) #throwaway, kwargs = self._make_parinfo(debug=debug, **kwargs) self.xax = xax # the 'stored' xax is just a link to the original if hasattr(xax, 'as_unit') and self.fitunits is not None: # some models will depend on the input units. For these, pass in an X-axis in those units # (gaussian, voigt, lorentz profiles should not depend on units. Ammonia, formaldehyde, # H-alpha, etc. should) xax = copy.copy(xax) # xax.convert_to_unit(self.fitunits, quiet=quiet) xax = xax.as_unit(self.fitunits, quiet=quiet, **kwargs) elif self.fitunits is not None: raise TypeError("X axis does not have a convert method") if np.any(np.isnan(data)) or np.any(np.isinf(data)): err[np.isnan(data) + np.isinf(data)] = np.inf data[np.isnan(data) + np.isinf(data)] = 0 if np.any(np.isnan(err)): raise ValueError( "One or more of the error values is NaN." " This is not allowed. Errors can be infinite " "(which is equivalent to giving zero weight to " "a data point), but otherwise they must be positive " "floats.") elif np.any(err < 0): raise ValueError("At least one error value is negative, which is " "not allowed as negative errors are not " "meaningful in the optimization process.") for p in parinfo: log.debug(p) log.debug("\n".join([ "%s %i: tied: %s value: %s" % (p['parname'], p['n'], p['tied'], p['value']) for p in parinfo ])) mp = mpfit(self.mpfitfun(xax, data, err), parinfo=parinfo, quiet=quiet, debug=debug, **kwargs) mpp = mp.params if mp.perror is not None: mpperr = mp.perror else: mpperr = mpp * 0 chi2 = mp.fnorm if mp.status == 0: if "parameters are not within PARINFO limits" in mp.errmsg: log.warn(parinfo) raise mpfitException(mp.errmsg) for i, (p, e) in enumerate(zip(mpp, mpperr)): self.parinfo[i]['value'] = p self.parinfo[i]['error'] = e if veryverbose: log.info("Fit status: {0}".format(mp.status)) log.info("Fit error message: {0}".format(mp.errmsg)) log.info("Fit message: {0}".format(mpfit_messages[mp.status])) for i, p in enumerate(mpp): log.info("{0}: {1} +/- {2}".format(self.parinfo[i]['parname'], p, mpperr[i])) log.info("Chi2: {0} Reduced Chi2: {1} DOF:{2}".format( mp.fnorm, mp.fnorm / (len(data) - len(mpp)), len(data) - len(mpp))) self.mp = mp self.mpp = self.parinfo.values self.mpperr = self.parinfo.errors self.mppnames = self.parinfo.names self.model = self.n_modelfunc(self.parinfo, **self.modelfunc_kwargs)(xax) log.debug("Modelpars: {0}".format(self.mpp)) if np.isnan(chi2): if debug: raise ValueError("Error: chi^2 is nan") else: log.warn("Warning: chi^2 is nan") return mpp, self.model, mpperr, chi2
def multinh3fit(self, xax, data, npeaks=1, err=None, params=[20,20,1e10,1.0,0.0,0.5], fixed=[False,False,False,False,False,False], limitedmin=[True,True,True,True,False,True], limitedmax=[False,False,False,False,False,True], minpars=[2.73,2.73,0,0,0,0], maxpars=[0,0,0,0,0,1], quiet=True, shh=True, veryverbose=False, **kwargs): """ Fit multiple nh3 profiles Inputs: xax - x axis data - y axis npeaks - How many nh3 profiles to fit? Default 1 (this could supersede onedgaussfit) err - error corresponding to data These parameters need to have length = 6*npeaks. If npeaks > 1 and length = 6, they will be replicated npeaks times, otherwise they will be reset to defaults: params - Fit parameters: [amplitude, offset, Gfwhm, Lfwhm] * npeaks If len(params) % 6 == 0, npeaks will be set to len(params) / 6 fixed - Is parameter fixed? limitedmin/minpars - set lower limits on each parameter (default: width>0, Tex and Tkin > Tcmb) limitedmax/maxpars - set upper limits on each parameter quiet - should MPFIT output each iteration? shh - output final parameters? Returns: Fit parameters Model Fit errors chi2 """ self.npars = 6 if len(params) != npeaks and (len(params) / self.npars) > npeaks: npeaks = len(params) / self.npars self.npeaks = npeaks if isinstance(params,np.ndarray): params=params.tolist() # make sure all various things are the right length; if they're not, fix them using the defaults for parlist in (params,fixed,limitedmin,limitedmax,minpars,maxpars): if len(parlist) != self.npars*self.npeaks: # if you leave the defaults, or enter something that can be multiplied by 3 to get to the # right number of gaussians, it will just replicate if len(parlist) == self.npars: parlist *= npeaks elif parlist==params: parlist[:] = [20,20,1e10,1.0,0.0,0.5] * npeaks elif parlist==fixed: parlist[:] = [False,False,False,False,False,False] * npeaks elif parlist==limitedmax: parlist[:] = [False,False,False,False,False,True] * npeaks elif parlist==limitedmin: parlist[:] = [True,True,True,True,False,True] * npeaks elif parlist==minpars: parlist[:] = [2.73,0,0,0,0,0] * npeaks elif parlist==maxpars: parlist[:] = [0,0,0,0,0,1] * npeaks def mpfitfun(x,y,err): if err is None: def f(p,fjac=None): return [0,(y-self.n_ammonia(pars=p, **kwargs)(x))] else: def f(p,fjac=None): return [0,(y-self.n_ammonia(pars=p, **kwargs)(x))/err] return f parnames = {0:"TKIN",1:"TEX",2:"NTOT",3:"WIDTH",4:"XOFF_V",5:"FORTHO"} parinfo = [ {'n':ii, 'value':params[ii], 'limits':[minpars[ii],maxpars[ii]], 'limited':[limitedmin[ii],limitedmax[ii]], 'fixed':fixed[ii], 'parname':parnames[ii%self.npars]+str(ii/self.npars), 'mpmaxstep':0,'error':ii} for ii in xrange(len(params)) ] parinfo[0]['mpmaxstep'] = 1.0 parinfo[1]['mpmaxstep'] = 1.0 if veryverbose: print "GUESSES: " print "\n".join(["%s: %s" % (p['parname'],p['value']) for p in parinfo]) mp = mpfit(mpfitfun(xax,data,err),parinfo=parinfo,quiet=quiet) mpp = mp.params if mp.perror is not None: mpperr = mp.perror else: mpperr = mpp*0 chi2 = mp.fnorm if mp.status == 0: raise Exception(mp.errmsg) if not shh: print "Fit message: ",mp.errmsg print "Final fit values: " for i,p in enumerate(mpp): parinfo[i]['value'] = p print parinfo[i]['parname'],p," +/- ",mpperr[i] print "Chi2: ",mp.fnorm," Reduced Chi2: ",mp.fnorm/len(data)," DOF:",len(data)-len(mpp) if mpp[1] > mpp[0]: mpp[1] = mpp[0] # force Tex>Tkin to Tex=Tkin (already done in n_ammonia) self.mp = mp self.mpp = mpp self.mpperr = mpperr self.model = self.n_ammonia(pars=mpp,**kwargs)(xax) return mpp,self.n_ammonia(pars=mpp,**kwargs)(xax),mpperr,chi2
def multivoigtfit(self,xax, data, npeaks=1, err=None, params=[1,0,1,1], fixed=[False,False,False,False], limitedmin=[False,False,True,True], limitedmax=[False,False,False,False], minpars=[0,0,0,0], maxpars=[0,0,0,0], quiet=True, shh=True, veryverbose=False, **kwargs): """ Fit multiple voigt profiles Inputs: xax - x axis data - y axis npeaks - How many voigt profiles to fit? Default 1 (this could supersede onedgaussfit) err - error corresponding to data These parameters need to have length = 3*npeaks. If npeaks > 1 and length = 3, they will be replicated npeaks times, otherwise they will be reset to defaults: params - Fit parameters: [amplitude, offset, Gfwhm, Lfwhm] * npeaks If len(params) % 4 == 0, npeaks 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 quiet - should MPFIT output each iteration? shh - output final parameters? Returns: Fit parameters Model Fit errors chi2 """ if len(params) != npeaks and (len(params) / 4) > npeaks: npeaks = len(params) / 4 self.npeaks = npeaks if isinstance(params,numpy.ndarray): params=params.tolist() # make sure all various things are the right length; if they're not, fix them using the defaults for parlist in (params,fixed,limitedmin,limitedmax,minpars,maxpars): if len(parlist) != 4*npeaks: # if you leave the defaults, or enter something that can be multiplied by 3 to get to the # right number of gaussians, it will just replicate if len(parlist) == 4: parlist *= npeaks elif parlist==params: parlist[:] = [1,0,1,1] * npeaks elif parlist==fixed or parlist==limitedmax: parlist[:] = [False,False,False,False] * npeaks elif parlist==limitedmin: parlist[:] = [False,False,True,True] * npeaks elif parlist==minpars or parlist==maxpars: parlist[:] = [0,0,0,0] * npeaks def mpfitfun(x,y,err): if err is None: def f(p,fjac=None): return [0,(y-self.n_voigt(pars=p)(x))] else: def f(p,fjac=None): return [0,(y-self.n_voigt(pars=p)(x))/err] return f if xax == None: xax = numpy.arange(len(data)) parnames = {0:"AMPLITUDE",1:"SHIFT",2:"GFWHM",3:"LFWHM"} parinfo = [ {'n':ii, 'value':params[ii], 'limits':[minpars[ii],maxpars[ii]], 'limited':[limitedmin[ii],limitedmax[ii]], 'fixed':fixed[ii], 'parname':parnames[ii%4]+str(ii/4), 'error':ii} for ii in xrange(len(params)) ] if veryverbose: print "GUESSES: " print "\n".join(["%s: %s" % (p['parname'],p['value']) for p in parinfo]) mp = mpfit(mpfitfun(xax,data,err),parinfo=parinfo,quiet=quiet) mpp = mp.params if mp.perror is not None: mpperr = mp.perror else: mpperr = mpp*0 chi2 = mp.fnorm if mp.status == 0: raise Exception(mp.errmsg) if not shh: print "Fit message ",mp.errmsg print "Final fit values: " for i,p in enumerate(mpp): parinfo[i]['value'] = p print parinfo[i]['parname'],p," +/- ",mpperr[i] print "Chi2: ",mp.fnorm," Reduced Chi2: ",mp.fnorm/len(data)," DOF:",len(data)-len(mpp) self.mp = mp self.mpp = mpp self.mpperr = mpperr self.model = self.n_voigt(pars=mpp)(xax) return mpp,self.n_voigt(pars=mpp)(xax),mpperr,chi2
def multivoigtfit(self, xax, data, npeaks=1, err=None, params=[1, 0, 1, 1], fixed=[False, False, False, False], limitedmin=[False, False, True, True], limitedmax=[False, False, False, False], minpars=[0, 0, 0, 0], maxpars=[0, 0, 0, 0], quiet=True, shh=True, veryverbose=False, **kwargs): """ Fit multiple voigt profiles Inputs: xax - x axis data - y axis npeaks - How many voigt profiles to fit? Default 1 (this could supersede onedgaussfit) err - error corresponding to data These parameters need to have length = 3*npeaks. If npeaks > 1 and length = 3, they will be replicated npeaks times, otherwise they will be reset to defaults: params - Fit parameters: [amplitude, offset, Gfwhm, Lfwhm] * npeaks If len(params) % 4 == 0, npeaks 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 quiet - should MPFIT output each iteration? shh - output final parameters? Returns: Fit parameters Model Fit errors chi2 """ if len(params) != npeaks and (len(params) / 4) > npeaks: npeaks = len(params) / 4 self.npeaks = npeaks if isinstance(params, numpy.ndarray): params = params.tolist() # make sure all various things are the right length; if they're not, fix them using the defaults for parlist in (params, fixed, limitedmin, limitedmax, minpars, maxpars): if len(parlist) != 4 * npeaks: # if you leave the defaults, or enter something that can be multiplied by 3 to get to the # right number of gaussians, it will just replicate if len(parlist) == 4: parlist *= npeaks elif parlist == params: parlist[:] = [1, 0, 1, 1] * npeaks elif parlist == fixed or parlist == limitedmax: parlist[:] = [False, False, False, False] * npeaks elif parlist == limitedmin: parlist[:] = [False, False, True, True] * npeaks elif parlist == minpars or parlist == maxpars: parlist[:] = [0, 0, 0, 0] * npeaks def mpfitfun(x, y, err): if err is None: def f(p, fjac=None): return [0, (y - self.n_voigt(pars=p)(x))] else: def f(p, fjac=None): return [0, (y - self.n_voigt(pars=p)(x)) / err] return f if xax == None: xax = numpy.arange(len(data)) parnames = {0: "AMPLITUDE", 1: "SHIFT", 2: "GFWHM", 3: "LFWHM"} parinfo = [{ 'n': ii, 'value': params[ii], 'limits': [minpars[ii], maxpars[ii]], 'limited': [limitedmin[ii], limitedmax[ii]], 'fixed': fixed[ii], 'parname': parnames[ii % 4] + str(ii / 4), 'error': ii } for ii in xrange(len(params))] if veryverbose: print "GUESSES: " print "\n".join( ["%s: %s" % (p['parname'], p['value']) for p in parinfo]) mp = mpfit(mpfitfun(xax, data, err), parinfo=parinfo, quiet=quiet) mpp = mp.params if mp.perror is not None: mpperr = mp.perror else: mpperr = mpp * 0 chi2 = mp.fnorm if mp.status == 0: raise Exception(mp.errmsg) if not shh: print "Fit message ", mp.errmsg print "Final fit values: " for i, p in enumerate(mpp): parinfo[i]['value'] = p print parinfo[i]['parname'], p, " +/- ", mpperr[i] print "Chi2: ", mp.fnorm, " Reduced Chi2: ", mp.fnorm / len( data), " DOF:", len(data) - len(mpp) self.mp = mp self.mpp = mpp self.mpperr = mpperr self.model = self.n_voigt(pars=mpp)(xax) return mpp, self.n_voigt(pars=mpp)(xax), mpperr, chi2
def _baseline(self, spectrum, xarr=None, err=None, order=1, quiet=True, mask=None, powerlaw=False, xarr_fit_units='pixels', LoudDebug=False, renormalize='auto', zeroerr_is_OK=True, spline=False, **kwargs): """ Fit a baseline/continuum to a spectrum """ #if xmin == 'default': # if order <= 1 and mask is None: xmin = np.floor( spectrum.shape[-1]*0.1 ) # else: xmin = 0 #elif xmin is None: # xmin = 0 #if xmax == 'default': # if order <= 1 and mask is None: xmax = np.ceil( spectrum.shape[-1]*0.9 ) # else: xmax = spectrum.shape[-1] #elif xmax is None: # xmax = spectrum.shape[-1] if xarr is None: xarr = np.indices(spectrum.shape).squeeze() # A good alternate implementation of masking is to only pass mpfit the data # that is unmasked. That would require some manipulation above... if err is None: err = np.ones(spectrum.shape) else: # don't overwrite error err = err.copy() # assume anything with 0 error is GOOD if zeroerr_is_OK: err[err == 0] = 1. else: # flag it out! err[err == 0] = 1e10 #err[:xmin] = 1e10 #err[xmax:] = 1e10 if mask is not None: if mask.dtype.name != 'bool': mask = mask.astype('bool') err[mask] = 1e10 if LoudDebug: print "In _baseline: %i points masked out" % mask.sum() if (spectrum!=spectrum).sum() > 0: print "There is an error in baseline: some values are NaN" import pdb; pdb.set_trace() #xarrconv = xarr[xmin:xmax].as_unit(xarr_fit_units) OK = True-mask xarrconv = xarr.as_unit(xarr_fit_units) if powerlaw: # for powerlaw fitting, only consider positive data OK *= spectrum > 0 pguess = [np.median(spectrum[OK]),2.0] if LoudDebug: print "_baseline powerlaw Guesses: ",pguess def mpfitfun(data,err): #def f(p,fjac=None): return [0,np.ravel(((p[0] * (xarrconv[OK]-p[2])**(-p[1]))-data)/err)] # Logarithmic fitting: def f(p,fjac=None): #return [0, # np.ravel( (np.log10(data) - np.log10(p[0]) + p[1]*np.log10(xarrconv[OK]/p[2])) / (err/data) ) # ] return [0, np.ravel( (data - self.get_model(xarr=xarrconv[OK],baselinepars=p)) / (err/data) )] return f else: pguess = [0]*(order+1) if LoudDebug: print "_baseline Guesses: ",pguess def mpfitfun(data,err): def f(p,fjac=None): return [0,np.ravel((np.poly1d(p)(xarrconv[OK])-data)/err)] return f #scalefactor = 1.0 #if renormalize in ('auto',True): # datarange = spectrum.max() - spectrum.min() # if abs(datarange) < 1e-9 or abs(datarange) > 1e9: # scalefactor = np.median(np.abs(self.spectrum)) # print "BASELINE: Renormalizing data by factor %e to improve fitting procedure" % scalefactor # spectrum /= scalefactor # err /= scalefactor import pyspeckit.mpfit as mpfit mp = mpfit.mpfit(mpfitfun(spectrum[OK],err[OK]),xall=pguess,quiet=quiet) # mpfit doesn't need to take kwargs, I think ,**kwargs) if np.isnan(mp.fnorm): raise ValueError("chi^2 is NAN in baseline fitting") fitp = mp.params if powerlaw: #bestfit = (fitp[0]*(xarrconv)**(-fitp[1])).squeeze() bestfit = (fitp[0]*(xarrconv)**(-fitp[1])).squeeze() else: bestfit = np.poly1d(fitp)(xarrconv).squeeze() return bestfit,fitp
def fitter(self, xax, data, err=None, quiet=True, veryverbose=False, debug=False, parinfo=None, **kwargs): """ Run the fitter using mpfit. kwargs will be passed to _make_parinfo and mpfit. Parameters ---------- xax : SpectroscopicAxis The X-axis of the spectrum data : ndarray The data to fit err : ndarray (optional) The error on the data. If unspecified, will be uniform unity parinfo : ParinfoList The guesses, parameter limits, etc. See `pyspeckit.spectrum.parinfo` for details quiet : bool pass to mpfit. If False, will print out the parameter values for each iteration of the fitter veryverbose : bool print out a variety of mpfit output parameters debug : bool raise an exception (rather than a warning) if chi^2 is nan """ if parinfo is None: parinfo, kwargs = self._make_parinfo(debug=debug, **kwargs) else: if debug: print "Using user-specified parinfo dict" # clean out disallowed kwargs (don't want to pass them to mpfit) #throwaway, kwargs = self._make_parinfo(debug=debug, **kwargs) self.xax = xax # the 'stored' xax is just a link to the original if hasattr(xax,'convert_to_unit') and self.fitunits is not None: # some models will depend on the input units. For these, pass in an X-axis in those units # (gaussian, voigt, lorentz profiles should not depend on units. Ammonia, formaldehyde, # H-alpha, etc. should) xax = copy.copy(xax) xax.convert_to_unit(self.fitunits, quiet=quiet) elif self.fitunits is not None: raise TypeError("X axis does not have a convert method") if np.any(np.isnan(data)) or np.any(np.isinf(data)): err[np.isnan(data) + np.isinf(data)] = np.inf data[np.isnan(data) + np.isinf(data)] = 0 if debug: for p in parinfo: print p print "\n".join(["%s %i: tied: %s value: %s" % (p['parname'],p['n'],p['tied'],p['value']) for p in parinfo]) mp = mpfit(self.mpfitfun(xax,data,err),parinfo=parinfo,quiet=quiet,**kwargs) mpp = mp.params if mp.perror is not None: mpperr = mp.perror else: mpperr = mpp*0 chi2 = mp.fnorm if mp.status == 0: if "parameters are not within PARINFO limits" in mp.errmsg: print parinfo raise mpfitException(mp.errmsg) for i,(p,e) in enumerate(zip(mpp,mpperr)): self.parinfo[i]['value'] = p self.parinfo[i]['error'] = e if veryverbose: print "Fit status: ",mp.status print "Fit error message: ",mp.errmsg print "Fit message: ",mpfit_messages[mp.status] for i,p in enumerate(mpp): print self.parinfo[i]['parname'],p," +/- ",mpperr[i] print "Chi2: ",mp.fnorm," Reduced Chi2: ",mp.fnorm/len(data)," DOF:",len(data)-len(mpp) self.mp = mp self.mpp = self.parinfo.values self.mpperr = self.parinfo.errors self.mppnames = self.parinfo.names self.model = self.n_modelfunc(self.parinfo,**self.modelfunc_kwargs)(xax) if debug: print "Modelpars: ",self.mpp if np.isnan(chi2): if debug: raise ValueError("Error: chi^2 is nan") else: print "Warning: chi^2 is nan" return mpp,self.model,mpperr,chi2
def multigaussfit(self, xax, data, npeaks=1, err=None, params=[1,0,1], fixed=[False,False,False], limitedmin=[False,False,True], limitedmax=[False,False,False], minpars=[0,0,0], maxpars=[0,0,0], quiet=True, shh=True, veryverbose=False, negamp=None, tied = ['', '', ''], parinfo=None, debug=False, **kwargs): """ An improvement on onepeakgaussfit. Lets you fit multiple gaussians. Inputs: xax - x axis data - y axis npeaks - How many gaussians to fit? Default 1 (this could supersede onepeakgaussfit) err - error corresponding to data These parameters need to have length = 3*npeaks. If npeaks > 1 and length = 3, they will be replicated npeaks times, otherwise they will be reset to defaults: params - Fit parameters: [amplitude, offset, width] * npeaks If len(params) % 3 == 0, npeaks 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 tied - link parameters together quiet - should MPFIT output each iteration? shh - output final parameters? kwargs are passed to mpfit Returns: Fit parameters Model Fit errors chi2 """ if len(params) != npeaks and (len(params) / 3) > npeaks: self.npeaks = len(params) / 3 else: self.npeaks = npeaks if isinstance(params,numpy.ndarray): params=params.tolist() # make sure all various things are the right length; if they're not, fix them using the defaults # multiformaldehydefit should process negamp directly if kwargs.has_key('negamp') is False: kwargs['negamp'] = None pardict = {"params":params,"fixed":fixed,"limitedmin":limitedmin,"limitedmax":limitedmax,"minpars":minpars,"maxpars":maxpars,"tied":tied} for parlistname in pardict: parlist = pardict[parlistname] if len(parlist) != 3*self.npeaks: # if you leave the defaults, or enter something that can be multiplied by 3 to get to the # right number of formaldehydeians, it will just replicate if veryverbose: print "Correcting length of parameter %s" % parlistname if len(parlist) == 3: parlist *= self.npeaks elif parlistname=="params": parlist[:] = [1,0,1] * self.npeaks elif parlistname=="fixed": parlist[:] = [False,False,False] * self.npeaks elif parlistname=="limitedmax": if negamp is None: parlist[:] = [False,False,False] * self.npeaks elif negamp is False: parlist[:] = [False,False,False] * self.npeaks else: parlist[:] = [True,False,False] * self.npeaks elif parlistname=="limitedmin": if negamp is None: parlist[:] = [False,False,True] * self.npeaks # Lines can't have negative width! elif negamp is False: parlist[:] = [True,False,True] * self.npeaks else: parlist[:] = [False,False,True] * self.npeaks elif parlistname=="minpars" or parlistname=="maxpars": parlist[:] = [0,0,0] * self.npeaks elif parlistname=="tied": parlist[:] = ['','',''] * self.npeaks # mpfit doesn't recognize negamp, so get rid of it now that we're done setting limitedmin/max and min/maxpars #if kwargs.has_key('negamp'): kwargs.pop('negamp') def mpfitfun(x,y,err): if err is None: def f(p,fjac=None): return [0,(y-self.n_gaussian(pars=p)(x))] else: def f(p,fjac=None): return [0,(y-self.n_gaussian(pars=p)(x))/err] return f if xax is None: xax = numpy.arange(len(data)) parnames = {0:"AMPLITUDE",1:"SHIFT",2:"WIDTH"} if parinfo is None: 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), 'error':ii, 'tied':tied[ii]} for ii in xrange(len(params)) ] if veryverbose: print "GUESSES: " print "\n".join(["%s: %s" % (p['parname'],p['value']) for p in parinfo]) if debug: for p in parinfo: print p mp = mpfit(mpfitfun(xax,data,err),parinfo=parinfo,quiet=quiet,**kwargs) mpp = mp.params if mp.perror is not None: mpperr = mp.perror else: mpperr = mpp*0 chi2 = mp.fnorm if mp.status == 0: raise Exception(mp.errmsg) if not shh: print "Fit status: ",mp.status print "Fit error message: ",mp.errmsg print "Fit message: ",mpfit_messages[mp.status] print "Final fit values: " for i,p in enumerate(mpp): parinfo[i]['value'] = p print parinfo[i]['parname'],p," +/- ",mpperr[i] print "Chi2: ",mp.fnorm," Reduced Chi2: ",mp.fnorm/len(data)," DOF:",len(data)-len(mpp) self.mp = mp self.mpp = mpp self.mpperr = mpperr self.model = self.n_gaussian(pars=mpp)(xax) return mpp,self.n_gaussian(pars=mpp)(xax),mpperr,chi2
# Generate model data for a Gaussian with param mu and sigma and add noise import warnings warnings.filterwarnings('once') x= SlambdaWave preal=[1,0.22,2] y_true=peval(x,preal) mu,sigma=0,1 y = y_true + 0.01 * np.random.normal(mu,sigma, len(y_true)) err = 0.1 * np.random.normal(mu,sigma, len(y_true) ) # Initial estimates for MPFIT p0 = [1.2, 0.2,1.8] fa = {'x':x, 'y':y, 'err':err} # Call MPFIT with user defined function 'myfunct' m = mpfit.mpfit(myfunct, p0, functkw=fa ) print("status: ", m.status) if (m.status <= 0): print('error message = ', m.errmsg) else: print("Iterations: ", m.niter) print("Fitted pars: ", m.params) print("Uncertainties: ", m.perror ) # In[119]: p = preal SpectrumFlux = f11Fun(x) + SlambdaFun(x)*(p[0]-1.1)
def emtau(freq, flux, err=None, EMguess=1e7 * emu, Te=default_te, normfac=5e-6, quiet=1, dust=False, dustT=False, alpha=3.5, normfac2=1e-6, beta=1.75, use_mpfit=False, maxiter=500, **kwargs): """ Returns emission measure & optical depth given radio continuum data points at frequency freq with flux density flux. return bestEM,nu(tau=1),chi^2 """ EMguess = with_unit(EMguess, emu) Te = with_unit(Te, u.K) flux = with_unit(flux, u.Jy) err = with_unit(err, u.Jy) ok = np.isfinite(freq) & np.isfinite(flux) & np.isfinite(err) guesses = [(EMguess / emu).decompose().value, normfac] if dust: guesses += [alpha, normfac2] elif dustT: guesses += [beta, normfac2, dustT.to(u.K).value] if use_mpfit: mp = mpfit.mpfit(mpfitfun(freq[ok], flux[ok], err[ok], dust=dust, dustT=bool(dustT)), xall=guesses, maxiter=maxiter, quiet=quiet) mpp = mp.params mpperr = mp.perror chi2 = mp.fnorm bestEM = mpp[0] normfac = mpp[1] else: fitter = LevMarLSQFitter() model = (inufit_dustT if bool(dustT) else inufit_dust if dust else inufit) m_init = model(*guesses) m_init.Te.fixed = True m_init.nu0.fixed = True m_init.em.min = 1 # cannot be less than 1 cm^-6 pc m_init.normfac.min = 0 if hasattr(m_init, 'beta'): m_init.beta.min = 1 m_init.beta.max = 10 m_init.normfac2.min = 0 elif hasattr(m_init, 'alpha'): m_init.alpha.min = 0 m_init.alpha.max = 12 fitted = fitter(m_init, freq[ok].to(u.GHz).value, flux[ok].to(u.mJy).value, weights=1 / err[ok].to(u.mJy).value, maxiter=maxiter) mp = fitter mp.params = fitted.parameters mpp = fitted.parameters try: assert 'cov_x' in fitter.fit_info and fitter.fit_info[ 'cov_x'] is not None mpperr = fitter.fit_info['cov_x'].diagonal()**0.5 except AssertionError: mpperr = [np.nan] * len(guesses) chi2 = (((u.Quantity(fitted(freq[ok]), u.mJy) - flux[ok]) / err[ok])** 2).sum() / (ok.sum() - len(guesses)) bestEM = mpp[0] normfac = mpp[1] log.info(fitter.fit_info['message']) # bestEM is unitless w/ emu units nu_tau = (((Te / u.K)**1.35 / (bestEM) / 8.235e-2)**(-1 / 2.1)).decompose() return with_unit(bestEM, emu), nu_tau, normfac, chi2, mp
def onedvoigtfit(self,xax, data, err=None, params=[0,1,0,1,1],fixed=[False,False,False,False,False], limitedmin=[False,False,False,True,True], limitedmax=[False,False,False,False,False], minpars=[0,0,0,0,0], maxpars=[0,0,0,0,0], quiet=True, shh=True, veryverbose=False, vheight=True, negamp=False, usemoments=False, **kwargs): """ Inputs: xax - x axis data - y axis err - error corresponding to data params - Fit parameters: Height of background, Amplitude, Shift, Width fixed - Is parameter fixed? limitedmin/minpars - set lower limits on each parameter (default: width>0) limitedmax/maxpars - set upper limits on each parameter quiet - should MPFIT output each iteration? shh - output final parameters? usemoments - replace default parameters with moments Returns: Fit parameters Model Fit errors chi2 """ def mpfitfun(x,y,err): if err is None: def f(p,fjac=None): return [0,(y-self.n_voigt(pars=p[1:])(x)+p[0])] else: def f(p,fjac=None): return [0,(y-self.n_voigt(pars=p[1:])(x)+p[0])/err] return f if xax == None: xax = numpy.arange(len(data)) if vheight is False: height = params[0] fixed[0] = True if usemoments: params = moments(xax, data, vheight=vheight, negamp=negamp, veryverbose=veryverbose) if vheight is False: params = [height]+params if veryverbose: print "OneD moments: h: %g a: %g c: %g w: %g" % tuple(params) parinfo = [ {'n':0,'value':params[0],'limits':[minpars[0],maxpars[0]],'limited':[limitedmin[0],limitedmax[0]],'fixed':fixed[0],'parname':"HEIGHT",'error':0} , {'n':1,'value':params[1],'limits':[minpars[1],maxpars[1]],'limited':[limitedmin[1],limitedmax[1]],'fixed':fixed[1],'parname':"AMPLITUDE",'error':0}, {'n':2,'value':params[2],'limits':[minpars[2],maxpars[2]],'limited':[limitedmin[2],limitedmax[2]],'fixed':fixed[2],'parname':"SHIFT",'error':0}, {'n':3,'value':params[3],'limits':[minpars[3],maxpars[3]],'limited':[limitedmin[3],limitedmax[3]],'fixed':fixed[3],'parname':"GWIDTH",'error':0}, {'n':4,'value':params[4],'limits':[minpars[4],maxpars[4]],'limited':[limitedmin[4],limitedmax[4]],'fixed':fixed[4],'parname':"LWIDTH",'error':0}] mp = mpfit(mpfitfun(xax,data,err),parinfo=parinfo,quiet=quiet) mpp = mp.params if mp.perror is not None: mpperr = mp.perror else: mpperr = mpp*0 chi2 = mp.fnorm if mp.status == 0: raise Exception(mp.errmsg) if (not shh) or veryverbose: print "Fit message ",mp.errmsg print "Fit status: ",mp.status for i,p in enumerate(mpp): parinfo[i]['value'] = p print parinfo[i]['parname'],p," +/- ",mpperr[i] print "Chi2: ",mp.fnorm," Reduced Chi2: ",mp.fnorm/len(data)," DOF:",len(data)-len(mpp) self.mp = mp self.mpp = mpp[1:] self.mpperr = mpperr self.model = self.n_voigt(pars=mpp[1:])(xax) return mpp,self.n_voigt(pars=mpp[1:])(xax),mpperr,chi2
def multinh3fit(self, xax, data, npeaks=1, err=None, params=(20,20,14,1.0,0.0,0.5), parnames=None, fixed=(False,False,False,False,False,False), limitedmin=(True,True,True,True,False,True), limitedmax=(False,False,False,False,False,True), minpars=(2.73,2.73,0,0,0,0), parinfo=None, maxpars=(0,0,0,0,0,1), quiet=True, shh=True, veryverbose=False, **kwargs): """ Fit multiple nh3 profiles (multiple can be 1) Inputs: xax - x axis data - y axis npeaks - How many nh3 profiles to fit? Default 1 (this could supersede onedgaussfit) err - error corresponding to data These parameters need to have length = 6*npeaks. If npeaks > 1 and length = 6, they will be replicated npeaks times, otherwise they will be reset to defaults: params - Fit parameters: [tkin, tex, ntot (or tau), width, offset, ortho fraction] * npeaks If len(params) % 6 == 0, npeaks will be set to len(params) / 6 fixed - Is parameter fixed? limitedmin/minpars - set lower limits on each parameter (default: width>0, Tex and Tkin > Tcmb) limitedmax/maxpars - set upper limits on each parameter parnames - default parameter names, important for setting kwargs in model ['tkin','tex','ntot','width','xoff_v','fortho'] quiet - should MPFIT output each iteration? shh - output final parameters? Returns: Fit parameters Model Fit errors chi2 """ if parinfo is None: self.npars = len(params) / npeaks if len(params) != npeaks and (len(params) / self.npars) > npeaks: npeaks = len(params) / self.npars self.npeaks = npeaks if isinstance(params,np.ndarray): params=params.tolist() # this is actually a hack, even though it's decently elegant # somehow, parnames was being changed WITHOUT being passed as a variable # this doesn't make sense - at all - but it happened. # (it is possible for self.parnames to have npars*npeaks elements where # npeaks > 1 coming into this function even though only 6 pars are specified; # _default_parnames is the workaround) if parnames is None: parnames = copy.copy(self._default_parnames) partype_dict = dict(zip(['params','parnames','fixed','limitedmin','limitedmax','minpars','maxpars'], [params,parnames,fixed,limitedmin,limitedmax,minpars,maxpars])) # make sure all various things are the right length; if they're not, fix them using the defaults for partype,parlist in partype_dict.iteritems(): if len(parlist) != self.npars*self.npeaks: # if you leave the defaults, or enter something that can be multiplied by npars to get to the # right number of gaussians, it will just replicate if len(parlist) == self.npars: partype_dict[partype] *= npeaks elif len(parlist) > self.npars: # DANGER: THIS SHOULD NOT HAPPEN! print "WARNING! Input parameters were longer than allowed for variable ",parlist partype_dict[partype] = partype_dict[partype][:self.npars] elif parlist==params: # this instance shouldn't really be possible partype_dict[partype] = [20,20,1e10,1.0,0.0,0.5] * npeaks elif parlist==fixed: partype_dict[partype] = [False] * len(params) elif parlist==limitedmax: # only fortho, fillingfraction have upper limits partype_dict[partype] = (np.array(parnames) == 'fortho') + (np.array(parnames) == 'fillingfraction') elif parlist==limitedmin: # no physical values can be negative except velocity partype_dict[partype] = (np.array(parnames) != 'xoff_v') elif parlist==minpars: # all have minima of zero except kinetic temperature, which can't be below CMB. Excitation temperature technically can be, but not in this model partype_dict[partype] = ((np.array(parnames) == 'tkin') + (np.array(parnames) == 'tex')) * 2.73 elif parlist==maxpars: # fractions have upper limits of 1.0 partype_dict[partype] = ((np.array(parnames) == 'fortho') + (np.array(parnames) == 'fillingfraction')).astype('float') elif parlist==parnames: # assumes the right number of parnames (essential) partype_dict[partype] = list(parnames) * self.npeaks if len(parnames) != len(partype_dict['params']): raise ValueError("Wrong array lengths AFTER fixing them") # used in components. Is this just a hack? self.parnames = partype_dict['parnames'] parinfo = [ {'n':ii, 'value':partype_dict['params'][ii], 'limits':[partype_dict['minpars'][ii],partype_dict['maxpars'][ii]], 'limited':[partype_dict['limitedmin'][ii],partype_dict['limitedmax'][ii]], 'fixed':partype_dict['fixed'][ii], 'parname':partype_dict['parnames'][ii]+str(ii/self.npars), 'mpmaxstep':float(partype_dict['parnames'][ii] in ('tex','tkin')), # must force small steps in temperature (True = 1.0) 'error': 0} for ii in xrange(len(partype_dict['params'])) ] # hack: remove 'fixed' pars parinfo_with_fixed = parinfo parinfo = [p for p in parinfo_with_fixed if not p['fixed']] fixed_kwargs = dict((p['parname'].strip("0123456789").lower(),p['value']) for p in parinfo_with_fixed if p['fixed']) # don't do this - it breaks the NEXT call because npars != len(parnames) self.parnames = [p['parname'] for p in parinfo] # this is OK - not a permanent change parnames = [p['parname'] for p in parinfo] # not OK self.npars = len(parinfo)/self.npeaks parinfo = ParinfoList([Parinfo(p) for p in parinfo], preserve_order=True) #import pdb; pdb.set_trace() else: self.parinfo = ParinfoList([Parinfo(p) for p in parinfo], preserve_order=True) parinfo_with_fixed = None fixed_kwargs = {} fitfun_kwargs = dict(kwargs.items()+fixed_kwargs.items()) npars = len(parinfo)/self.npeaks # (fortho0 is not fortho) # this doesn't work if parinfo_with_fixed is not None: # this doesn't work for p in parinfo_with_fixed: # this doesn't work # users can change the defaults while holding them fixed # this doesn't work if p['fixed']: # this doesn't work kwargs.update({p['parname']:p['value']}) def mpfitfun(x,y,err): if err is None: def f(p,fjac=None): return [0,(y-self.n_ammonia(pars=p, parnames=parinfo.parnames, **fitfun_kwargs)(x))] else: def f(p,fjac=None): return [0,(y-self.n_ammonia(pars=p, parnames=parinfo.parnames, **fitfun_kwargs)(x))/err] return f if veryverbose: print "GUESSES: " print "\n".join(["%s: %s" % (p['parname'],p['value']) for p in parinfo]) mp = mpfit(mpfitfun(xax,data,err),parinfo=parinfo,quiet=quiet) mpp = mp.params if mp.perror is not None: mpperr = mp.perror else: mpperr = mpp*0 chi2 = mp.fnorm if mp.status == 0: raise Exception(mp.errmsg) for i,p in enumerate(mpp): parinfo[i]['value'] = p parinfo[i]['error'] = mpperr[i] if not shh: print "Fit status: ",mp.status print "Fit message: ",mp.errmsg print "Final fit values: " for i,p in enumerate(mpp): print parinfo[i]['parname'],p," +/- ",mpperr[i] print "Chi2: ",mp.fnorm," Reduced Chi2: ",mp.fnorm/len(data)," DOF:",len(data)-len(mpp) if any(['tex' in s for s in parnames]) and any(['tkin' in s for s in parnames]): texnum = (i for i,s in enumerate(parnames) if 'tex' in s) tkinnum = (i for i,s in enumerate(parnames) if 'tkin' in s) for txn,tkn in zip(texnum,tkinnum): if mpp[txn] > mpp[tkn]: mpp[txn] = mpp[tkn] # force Tex>Tkin to Tex=Tkin (already done in n_ammonia) self.mp = mp if parinfo_with_fixed is not None: # self self.parinfo preserving the 'fixed' parameters # ORDER MATTERS! for p in parinfo: parinfo_with_fixed[p['n']] = p self.parinfo = ParinfoList([Parinfo(p) for p in parinfo_with_fixed], preserve_order=True) else: self.parinfo = parinfo self.parinfo = ParinfoList([Parinfo(p) for p in parinfo], preserve_order=True) # I don't THINK these are necessary? #self.parinfo = parinfo #self.parinfo = ParinfoList([Parinfo(p) for p in self.parinfo]) # need to restore the fixed parameters.... # though the above commented out section indicates that I've done and undone this dozens of times now # (a test has been added to test_nh3.py) # this was NEVER included or tested because it breaks the order #for par in parinfo_with_fixed: # if par.parname not in self.parinfo.keys(): # self.parinfo.append(par) self.mpp = self.parinfo.values self.mpperr = self.parinfo.errors self.mppnames = self.parinfo.names self.model = self.n_ammonia(pars=self.mpp, parnames=self.mppnames, **kwargs)(xax) #if self.model.sum() == 0: # print "DON'T FORGET TO REMOVE THIS ERROR!" # raise ValueError("Model is zeros.") indiv_parinfo = [self.parinfo[jj*self.npars:(jj+1)*self.npars] for jj in xrange(len(self.parinfo)/self.npars)] modelkwargs = [ dict([(p['parname'].strip("0123456789").lower(),p['value']) for p in pi]) for pi in indiv_parinfo] self.tau_list = [ammonia(xax,return_tau=True,**mk) for mk in modelkwargs] return self.mpp,self.model,self.mpperr,chi2
def multinh3fit(self, xax, data, err=None, parinfo=None, quiet=True, shh=True, debug=False, maxiter=200, use_lmfit=False, veryverbose=False, **kwargs): """ Fit multiple nh3 profiles (multiple can be 1) Inputs: xax - x axis data - y axis npeaks - How many nh3 profiles to fit? Default 1 (this could supersede onedgaussfit) err - error corresponding to data These parameters need to have length = 6*npeaks. If npeaks > 1 and length = 6, they will be replicated npeaks times, otherwise they will be reset to defaults: params - Fit parameters: [tkin, tex, ntot (or tau), width, offset, ortho fraction] * npeaks If len(params) % 6 == 0, npeaks will be set to len(params) / 6 fixed - Is parameter fixed? limitedmin/minpars - set lower limits on each parameter (default: width>0, Tex and Tkin > Tcmb) limitedmax/maxpars - set upper limits on each parameter parnames - default parameter names, important for setting kwargs in model ['tkin','tex','ntot','width','xoff_v','fortho'] quiet - should MPFIT output each iteration? shh - output final parameters? Returns: Fit parameters Model Fit errors chi2 """ if parinfo is None: parinfo = self.parinfo = self.make_parinfo(**kwargs) else: if isinstance(parinfo, ParinfoList): if not quiet: log.info("Using user-specified parinfo.") self.parinfo = parinfo else: if not quiet: log.info( "Using something like a user-specified parinfo, but not." ) self.parinfo = ParinfoList([ p if isinstance(p, Parinfo) else Parinfo(p) for p in parinfo ], preserve_order=True) fitfun_kwargs = dict( (x, y) for (x, y) in kwargs.items() if x not in ('npeaks', 'params', 'parnames', 'fixed', 'limitedmin', 'limitedmax', 'minpars', 'maxpars', 'tied', 'max_tem_step')) if 'use_lmfit' in fitfun_kwargs: raise KeyError("use_lmfit was specified in a location where it " "is unacceptable") npars = len(parinfo) / self.npeaks def mpfitfun(x, y, err): if err is None: def f(p, fjac=None): return [ 0, (y - self.n_ammonia(pars=p, parnames=parinfo.parnames, **fitfun_kwargs)(x)) ] else: def f(p, fjac=None): return [ 0, (y - self.n_ammonia( pars=p, parnames=parinfo.parnames, **fitfun_kwargs) (x)) / err ] return f if veryverbose: log.info("GUESSES: ") log.info(str(parinfo)) #log.info "\n".join(["%s: %s" % (p['parname'],p['value']) for p in parinfo]) if use_lmfit: return self.lmfitter(xax, data, err=err, parinfo=parinfo, quiet=quiet, debug=debug) else: mp = mpfit(mpfitfun(xax, data, err), parinfo=parinfo, maxiter=maxiter, quiet=quiet, debug=debug) mpp = mp.params if mp.perror is not None: mpperr = mp.perror else: mpperr = mpp * 0 chi2 = mp.fnorm if mp.status == 0: raise Exception(mp.errmsg) for i, p in enumerate(mpp): parinfo[i]['value'] = p parinfo[i]['error'] = mpperr[i] if not shh: log.info("Fit status: {0}".format(mp.status)) log.info("Fit message: {0}".format(mpfit_messages[mp.status])) log.info("Fit error message: {0}".format(mp.errmsg)) log.info("Final fit values: ") for i, p in enumerate(mpp): log.info(" ".join( (parinfo[i]['parname'], str(p), " +/- ", str(mpperr[i])))) log.info(" ".join(("Chi2: ", str(mp.fnorm), " Reduced Chi2: ", str(mp.fnorm / len(data)), " DOF:", str(len(data) - len(mpp))))) self.mp = mp self.parinfo = parinfo self.mpp = self.parinfo.values self.mpperr = self.parinfo.errors self.mppnames = self.parinfo.names self.model = self.n_ammonia(pars=self.mpp, parnames=self.mppnames, **fitfun_kwargs)(xax) indiv_parinfo = [ self.parinfo[jj * self.npars:(jj + 1) * self.npars] for jj in xrange(len(self.parinfo) / self.npars) ] modelkwargs = [ dict([(p['parname'].strip("0123456789").lower(), p['value']) for p in pi]) for pi in indiv_parinfo ] self.tau_list = [ ammonia(xax, return_tau=True, **mk) for mk in modelkwargs ] return self.mpp, self.model, self.mpperr, chi2
def fitter(self, xax, data, err=None, quiet=True, veryverbose=False, debug=False, parinfo=None, **kwargs): """ Run the fitter using mpfit. kwargs will be passed to _make_parinfo and mpfit. Parameters ---------- xax : SpectroscopicAxis The X-axis of the spectrum data : ndarray The data to fit err : ndarray (optional) The error on the data. If unspecified, will be uniform unity parinfo : ParinfoList The guesses, parameter limits, etc. See `pyspeckit.spectrum.parinfo` for details quiet : bool pass to mpfit. If False, will print out the parameter values for each iteration of the fitter veryverbose : bool print out a variety of mpfit output parameters debug : bool raise an exception (rather than a warning) if chi^2 is nan """ if parinfo is None: parinfo, kwargs = self._make_parinfo(debug=debug, **kwargs) else: log.debug("Using user-specified parinfo dict") # clean out disallowed kwargs (don't want to pass them to mpfit) #throwaway, kwargs = self._make_parinfo(debug=debug, **kwargs) self.xax = xax # the 'stored' xax is just a link to the original if hasattr(xax,'as_unit') and self.fitunits is not None: # some models will depend on the input units. For these, pass in an X-axis in those units # (gaussian, voigt, lorentz profiles should not depend on units. Ammonia, formaldehyde, # H-alpha, etc. should) xax = copy.copy(xax) # xax.convert_to_unit(self.fitunits, quiet=quiet) xax = xax.as_unit(self.fitunits, quiet=quiet, **kwargs) elif self.fitunits is not None: raise TypeError("X axis does not have a convert method") if np.any(np.isnan(data)) or np.any(np.isinf(data)): err[np.isnan(data) + np.isinf(data)] = np.inf data[np.isnan(data) + np.isinf(data)] = 0 if np.any(np.isnan(err)): raise ValueError("One or more of the error values is NaN." " This is not allowed. Errors can be infinite " "(which is equivalent to giving zero weight to " "a data point), but otherwise they must be positive " "floats.") elif np.any(err<0): raise ValueError("At least one error value is negative, which is " "not allowed as negative errors are not " "meaningful in the optimization process.") for p in parinfo: log.debug( p ) log.debug( "\n".join(["%s %i: tied: %s value: %s" % (p['parname'],p['n'],p['tied'],p['value']) for p in parinfo]) ) mp = mpfit(self.mpfitfun(xax,data,err),parinfo=parinfo,quiet=quiet,debug=debug,**kwargs) mpp = mp.params if mp.perror is not None: mpperr = mp.perror else: mpperr = mpp*0 chi2 = mp.fnorm if mp.status == 0: if "parameters are not within PARINFO limits" in mp.errmsg: log.warning( parinfo ) raise mpfitException(mp.errmsg) for i,(p,e) in enumerate(zip(mpp,mpperr)): self.parinfo[i]['value'] = p self.parinfo[i]['error'] = e if veryverbose: log.info("Fit status: {0}".format(mp.status)) log.info("Fit error message: {0}".format(mp.errmsg)) log.info("Fit message: {0}".format(mpfit_messages[mp.status])) for i,p in enumerate(mpp): log.info("{0}: {1} +/- {2}".format(self.parinfo[i]['parname'], p,mpperr[i])) log.info("Chi2: {0} Reduced Chi2: {1} DOF:{2}".format(mp.fnorm, mp.fnorm/(len(data)-len(mpp)), len(data)-len(mpp))) self.mp = mp self.mpp = self.parinfo.values self.mpperr = self.parinfo.errors self.mppnames = self.parinfo.names self.model = self.n_modelfunc(self.parinfo,**self.modelfunc_kwargs)(xax) log.debug("Modelpars: {0}".format(self.mpp)) if np.isnan(chi2): if debug: raise ValueError("Error: chi^2 is nan") else: log.warning("Warning: chi^2 is nan") return mpp,self.model,mpperr,chi2
def multigaussfit(self, xax, data, npeaks=1, err=None, params=[1, 0, 1], fixed=[False, False, False], limitedmin=[False, False, True], limitedmax=[False, False, False], minpars=[0, 0, 0], maxpars=[0, 0, 0], quiet=True, shh=True, veryverbose=False, negamp=None, tied=['', '', ''], parinfo=None, debug=False, **kwargs): """ An improvement on onepeakgaussfit. Lets you fit multiple gaussians. Inputs: xax - x axis data - y axis npeaks - How many gaussians to fit? Default 1 (this could supersede onepeakgaussfit) err - error corresponding to data These parameters need to have length = 3*npeaks. If npeaks > 1 and length = 3, they will be replicated npeaks times, otherwise they will be reset to defaults: params - Fit parameters: [amplitude, offset, width] * npeaks If len(params) % 3 == 0, npeaks 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 tied - link parameters together quiet - should MPFIT output each iteration? shh - output final parameters? kwargs are passed to mpfit Returns: Fit parameters Model Fit errors chi2 """ if len(params) != npeaks and (len(params) / 3) > npeaks: self.npeaks = len(params) / 3 else: self.npeaks = npeaks if isinstance(params, numpy.ndarray): params = params.tolist() # make sure all various things are the right length; if they're not, fix them using the defaults # multiformaldehydefit should process negamp directly if kwargs.has_key('negamp') is False: kwargs['negamp'] = None pardict = { "params": params, "fixed": fixed, "limitedmin": limitedmin, "limitedmax": limitedmax, "minpars": minpars, "maxpars": maxpars, "tied": tied } for parlistname in pardict: parlist = pardict[parlistname] if len(parlist) != 3 * self.npeaks: # if you leave the defaults, or enter something that can be multiplied by 3 to get to the # right number of formaldehydeians, it will just replicate if veryverbose: print("Correcting length of parameter %s" % parlistname) if len(parlist) == 3: parlist *= self.npeaks elif parlistname == "params": parlist[:] = [1, 0, 1] * self.npeaks elif parlistname == "fixed": parlist[:] = [False, False, False] * self.npeaks elif parlistname == "limitedmax": if negamp is None: parlist[:] = [False, False, False] * self.npeaks elif negamp is False: parlist[:] = [False, False, False] * self.npeaks else: parlist[:] = [True, False, False] * self.npeaks elif parlistname == "limitedmin": if negamp is None: parlist[:] = [ False, False, True ] * self.npeaks # Lines can't have negative width! elif negamp is False: parlist[:] = [True, False, True] * self.npeaks else: parlist[:] = [False, False, True] * self.npeaks elif parlistname == "minpars" or parlistname == "maxpars": parlist[:] = [0, 0, 0] * self.npeaks elif parlistname == "tied": parlist[:] = ['', '', ''] * self.npeaks # mpfit doesn't recognize negamp, so get rid of it now that we're done setting limitedmin/max and min/maxpars #if kwargs.has_key('negamp'): kwargs.pop('negamp') def mpfitfun(x, y, err): if err is None: def f(p, fjac=None): return [0, (y - self.n_gaussian(pars=p)(x))] else: def f(p, fjac=None): return [0, (y - self.n_gaussian(pars=p)(x)) / err] return f if xax is None: xax = numpy.arange(len(data)) parnames = {0: "AMPLITUDE", 1: "SHIFT", 2: "WIDTH"} if parinfo is None: 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), 'error': ii, 'tied': tied[ii] } for ii in xrange(len(params))] if veryverbose: print("GUESSES: ") print("\n".join( ["%s: %s" % (p['parname'], p['value']) for p in parinfo])) if debug: for p in parinfo: print(p) mp = mpfit(mpfitfun(xax, data, err), parinfo=parinfo, quiet=quiet, **kwargs) mpp = mp.params if mp.perror is not None: mpperr = mp.perror else: mpperr = mpp * 0 chi2 = mp.fnorm if mp.status == 0: raise Exception(mp.errmsg) if not shh: print("Fit status: ", mp.status) print("Fit error message: ", mp.errmsg) print("Fit message: ", mpfit_messages[mp.status]) print("Final fit values: ") for i, p in enumerate(mpp): parinfo[i]['value'] = p print(parinfo[i]['parname'], p, " +/- ", mpperr[i]) print("Chi2: ", mp.fnorm, " Reduced Chi2: ", mp.fnorm / len(data), " DOF:", len(data) - len(mpp)) self.mp = mp self.mpp = mpp self.mpperr = mpperr self.model = self.n_gaussian(pars=mpp)(xax) return mpp, self.n_gaussian(pars=mpp)(xax), mpperr, chi2
def onedvoigtfit(self, xax, data, err=None, params=[0, 1, 0, 1, 1], fixed=[False, False, False, False, False], limitedmin=[False, False, False, True, True], limitedmax=[False, False, False, False, False], minpars=[0, 0, 0, 0, 0], maxpars=[0, 0, 0, 0, 0], quiet=True, shh=True, veryverbose=False, vheight=True, negamp=False, usemoments=False, **kwargs): """ Inputs: xax - x axis data - y axis err - error corresponding to data params - Fit parameters: Height of background, Amplitude, Shift, Width fixed - Is parameter fixed? limitedmin/minpars - set lower limits on each parameter (default: width>0) limitedmax/maxpars - set upper limits on each parameter quiet - should MPFIT output each iteration? shh - output final parameters? usemoments - replace default parameters with moments Returns: Fit parameters Model Fit errors chi2 """ def mpfitfun(x, y, err): if err is None: def f(p, fjac=None): return [0, (y - self.n_voigt(pars=p[1:])(x) + p[0])] else: def f(p, fjac=None): return [0, (y - self.n_voigt(pars=p[1:])(x) + p[0]) / err] return f if xax == None: xax = numpy.arange(len(data)) if vheight is False: height = params[0] fixed[0] = True if usemoments: params = moments(xax, data, vheight=vheight, negamp=negamp, veryverbose=veryverbose) if vheight is False: params = [height] + params if veryverbose: print "OneD moments: h: %g a: %g c: %g w: %g" % tuple( params) parinfo = [{ 'n': 0, 'value': params[0], 'limits': [minpars[0], maxpars[0]], 'limited': [limitedmin[0], limitedmax[0]], 'fixed': fixed[0], 'parname': "HEIGHT", 'error': 0 }, { 'n': 1, 'value': params[1], 'limits': [minpars[1], maxpars[1]], 'limited': [limitedmin[1], limitedmax[1]], 'fixed': fixed[1], 'parname': "AMPLITUDE", 'error': 0 }, { 'n': 2, 'value': params[2], 'limits': [minpars[2], maxpars[2]], 'limited': [limitedmin[2], limitedmax[2]], 'fixed': fixed[2], 'parname': "SHIFT", 'error': 0 }, { 'n': 3, 'value': params[3], 'limits': [minpars[3], maxpars[3]], 'limited': [limitedmin[3], limitedmax[3]], 'fixed': fixed[3], 'parname': "GWIDTH", 'error': 0 }, { 'n': 4, 'value': params[4], 'limits': [minpars[4], maxpars[4]], 'limited': [limitedmin[4], limitedmax[4]], 'fixed': fixed[4], 'parname': "LWIDTH", 'error': 0 }] mp = mpfit(mpfitfun(xax, data, err), parinfo=parinfo, quiet=quiet) mpp = mp.params if mp.perror is not None: mpperr = mp.perror else: mpperr = mpp * 0 chi2 = mp.fnorm if mp.status == 0: raise Exception(mp.errmsg) if (not shh) or veryverbose: print "Fit message ", mp.errmsg print "Fit status: ", mp.status for i, p in enumerate(mpp): parinfo[i]['value'] = p print parinfo[i]['parname'], p, " +/- ", mpperr[i] print "Chi2: ", mp.fnorm, " Reduced Chi2: ", mp.fnorm / len( data), " DOF:", len(data) - len(mpp) self.mp = mp self.mpp = mpp[1:] self.mpperr = mpperr self.model = self.n_voigt(pars=mpp[1:])(xax) return mpp, self.n_voigt(pars=mpp[1:])(xax), mpperr, chi2
for i in xrange( 0, nparams): # To initialize all the individual dictionaries separately dictionary['data'] = p3[ i] # We give to each dictionary the initial values for each parameter parinf.append(dictionary.copy()) parinf[6]['tied'] = p3[ 3] # Put constraint that the sigma should be the same for both gaussians parinf[5]['tied'] = p3[2] * ( l_SII_1 / l_SII_2 ) # Put a constraint on the differences between wavelengths of the lines # Make the fit using mpfit for one gaussian and for the combination of as many gaussians as wanted with # a linear fit for the continuum m = mpfit.mpfit(usefunctions.gausfunct, p0, functkw=fa) m3 = mpfit.mpfit(usefunctions.gaus3funct, p3, functkw=fa3, parinfo=parinf) # Calculate the residuals of the data resid = newy1 - usefunctions.gaussian(newx1, m.params) # In order to determine if the lines need one more gaussian to be fit correctly, we apply the condition # that the std dev of the continuum should be higher than 3 times the std dev of the residuals of the # fit of the line. We have to calculate the stddev of the continuum in a place where there are no # lines (True for all AGNs spectra in this range). # Calculate the standard deviation of a part of the continuum without lines nor contribution of them std0 = np.where(l > 6450.)[0][0] std1 = np.where(l < 6500.)[0][-1] stadev = np.std(data_cor[std0:std1]) #############################################################################################################