Esempio n. 1
0
    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, {}
Esempio n. 2
0
    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
Esempio n. 3
0
    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
Esempio n. 4
0
    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
Esempio n. 5
0
    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