def _make_parinfo(self, model=None): self.parinfo = ParinfoList([ Parinfo(parname=name,value=value) for name,value in zip(model.param_names,model.parameters)]) return self.parinfo, {}
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 _make_parinfo(self, params=None, parnames=None, parvalues=None, parlimits=None, parlimited=None, parfixed=None, parerror=None, partied=None, fitunits=None, parsteps=None, npeaks=1, parinfo=None, names=None, values=None, limits=None, limited=None, fixed=None, error=None, tied=None, steps=None, negamp=None, limitedmin=None, limitedmax=None, minpars=None, maxpars=None, vheight=False, debug=False, **kwargs): """ Generate a `ParinfoList` that matches the inputs This code is complicated - it can take inputs in a variety of different forms with different priority. It will return a `ParinfoList` (and therefore must have values within parameter ranges) """ # for backwards compatibility - partied = tied, etc. locals_dict = locals() for varname in str.split( "parnames,parvalues,parsteps,parlimits,parlimited,parfixed,parerror,partied", ","): shortvarname = varname.replace("par", "") if locals_dict.get(shortvarname) is not None and locals_dict.get( varname) is not None: raise ValueError("Cannot specify both {0} and {1}".format( varname, shortvarname)) input_pardict = { k: locals_dict.get(k) for k in str.split( "parnames,parvalues,parsteps,parlimits,parlimited,parfixed,parerror,partied", ",") } _tip = { 'par' + k: locals_dict.get(k) for k in str.split( "names,values,steps,limits,limited,fixed,error,tied", ",") if locals_dict.get(k) } input_pardict.update(_tip) if params is not None and parvalues is not None: raise ValueError( "parvalues and params both specified; they're redundant so that's not allowed." ) elif params is not None and parvalues is None: input_pardict['parvalues'] = params log.debug("Parvalues = {0}, npeaks = {1}".format( input_pardict['parvalues'], npeaks)) # this is used too many damned times to keep referencing a dict. parnames = input_pardict['parnames'] parlimited = input_pardict['parlimited'] parlimits = input_pardict['parlimits'] parvalues = input_pardict['parvalues'] if parnames is not None: self.parnames = parnames elif parnames is None and hasattr( self, 'parnames') and self.parnames is not None: parnames = self.parnames elif self.default_parinfo is not None and parnames is None: parnames = [p['parname'] for p in self.default_parinfo] input_pardict['parnames'] = parnames assert input_pardict['parnames'] is not None if limitedmin is not None: if limitedmax is not None: parlimited = list(zip(limitedmin, limitedmax)) else: parlimited = list(zip(limitedmin, (False, ) * len(parnames))) elif limitedmax is not None: parlimited = list(zip((False, ) * len(parnames), limitedmax)) elif self.default_parinfo is not None and parlimited is None: parlimited = [p['limited'] for p in self.default_parinfo] input_pardict['parlimited'] = parlimited if minpars is not None: if maxpars is not None: parlimits = list(zip(minpars, maxpars)) else: parlimits = list(zip(minpars, (False, ) * len(parnames))) elif maxpars is not None: parlimits = list(zip((False, ) * len(parnames), maxpars)) elif limits is not None: parlimits = limits elif self.default_parinfo is not None and parlimits is None: parlimits = [p['limits'] for p in self.default_parinfo] input_pardict['parlimits'] = parlimits self.npeaks = int(npeaks) # the height / parvalue popping needs to be done before the temp_pardict is set in order to make sure # that the height guess isn't assigned to the amplitude self.vheight = vheight if ((vheight and len(self.parinfo) == self.default_npars and len(parvalues) == self.default_npars + 1)): # if the right number of parameters are passed, the first is the height self.parinfo = [{ 'n': 0, 'value': parvalues.pop(0), 'limits': (0, 0), 'limited': (False, False), 'fixed': False, 'parname': 'HEIGHT', 'error': 0, 'tied': "" }] elif vheight and len(self.parinfo) == self.default_npars and len( parvalues) == self.default_npars: # if you're one par short, guess zero self.parinfo = [{ 'n': 0, 'value': 0, 'limits': (0, 0), 'limited': (False, False), 'fixed': False, 'parname': 'HEIGHT', 'error': 0, 'tied': "" }] elif vheight and len(self.parinfo) == self.default_npars + 1 and len( parvalues) == self.default_npars + 1: # the right numbers are passed *AND* there is already a height param self.parinfo = [{ 'n': 0, 'value': parvalues.pop(0), 'limits': (0, 0), 'limited': (False, False), 'fixed': False, 'parname': 'HEIGHT', 'error': 0, 'tied': "" }] #heightparnum = (i for i,s in self.parinfo if 'HEIGHT' in s['parname']) #for hpn in heightparnum: # self.parinfo[hpn]['value'] = parvalues[0] elif vheight: raise ValueError( 'VHEIGHT is specified but a case was found that did not allow it to be included.' ) else: self.parinfo = [] log.debug("After VHEIGHT parse len(parinfo): %i vheight: %s" % (len(self.parinfo), vheight)) # this is a clever way to turn the parameter lists into a dict of lists # clever = hard to read temp_pardict = OrderedDict([( varname, np.zeros(self.npars * self.npeaks, dtype='bool') ) if input_pardict.get(varname) is None else ( varname, list(input_pardict.get(varname)) ) for varname in str.split( "parnames,parvalues,parsteps,parlimits,parlimited,parfixed,parerror,partied", ",")]) temp_pardict['parlimits'] = parlimits if parlimits is not None else [ (0, 0) ] * (self.npars * self.npeaks) temp_pardict[ 'parlimited'] = parlimited if parlimited is not None else [ (False, False) ] * (self.npars * self.npeaks) for k, v in temp_pardict.items(): if (self.npars * self.npeaks) / len(v) > 1: n_components = ((self.npars * self.npeaks) / len(v)) if n_components != int(n_components): raise ValueError("The number of parameter values is not a " "multiple of the number of allowed " "parameters.") temp_pardict[k] = list(v) * int(n_components) # generate the parinfo dict # note that 'tied' must be a blank string (i.e. ""), not False, if it is not set # parlimited, parfixed, and parlimits are all two-element items (tuples or lists) self.parinfo += [{ 'n': ii + self.npars * jj + vheight, 'value': float(temp_pardict['parvalues'][ii + self.npars * jj]), 'step': temp_pardict['parsteps'][ii + self.npars * jj], 'limits': temp_pardict['parlimits'][ii + self.npars * jj], 'limited': temp_pardict['parlimited'][ii + self.npars * jj], 'fixed': temp_pardict['parfixed'][ii + self.npars * jj], 'parname': temp_pardict['parnames'][ii].upper() + "%0i" % int(jj), 'error': float(temp_pardict['parerror'][ii + self.npars * jj]), 'tied': temp_pardict['partied'][ii + self.npars * jj] if temp_pardict['partied'][ii + self.npars * jj] else "" } for jj in range(self.npeaks) for ii in range(self.npars)] # order matters! log.debug("After Generation step len(parinfo): %i vheight: %s " "parinfo: %s" % (len(self.parinfo), vheight, self.parinfo)) if debug > True: import pdb pdb.set_trace() # special keyword to specify emission/absorption lines if negamp is not None: if negamp: for p in self.parinfo: if 'AMP' in p['parname']: p['limited'] = (p['limited'][0], True) p['limits'] = (p['limits'][0], 0) else: for p in self.parinfo: if 'AMP' in p['parname']: p['limited'] = (True, p['limited'][1]) p['limits'] = (0, p['limits'][1]) # This is effectively an override of all that junk above (3/11/2012) # Much of it is probably unnecessary, but it was easier to do this than # rewrite the above self.parinfo = ParinfoList([Parinfo(p) for p in self.parinfo]) # New feature: scaleability for par in self.parinfo: if par.parname.lower().strip('0123456789') in ('amplitude', 'amp'): par.scaleable = True log.debug("Parinfo has been set: {0}".format(self.parinfo)) log.debug("kwargs {0} were passed.".format(kwargs)) assert self.parinfo != [] return self.parinfo, kwargs
def make_parinfo(self, quiet=True, npeaks=1, 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), maxpars=(0, 0, 0, 0, 0, 1), tied=('', ) * 6, max_tem_step=1., **kwargs): if not quiet: log.info("Creating a 'parinfo' from guesses.") 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', 'tied' ], [ params, parnames, fixed, limitedmin, limitedmax, minpars, maxpars, tied ])) # make sure all various things are the right length; if they're # not, fix them using the defaults # (you can put in guesses of length 12 but leave the rest length 6; # this code then doubles the length of everything else) 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! log.warn( "WARNING! Input parameters were longer than allowed for variable {0}" .format(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 elif parlist == tied: partype_dict[partype] = [ _increment_string_number(t, ii * self.npars) for t in tied for ii in range(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), 'tied': partype_dict['tied'][ii], 'mpmaxstep': max_tem_step * 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() return parinfo
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