Beispiel #1
0
def fit_tex(eupper, nupperoverg, verbose=False, plot=False):
    """
    Fit the Boltzmann diagram
    """
    model = modeling.models.Linear1D()
    #fitter = modeling.fitting.LevMarLSQFitter()
    fitter = modeling.fitting.LinearLSQFitter()
    result = fitter(model, eupper, np.log(nupperoverg))
    tex = -1. / result.slope * u.K

    partition_func = specmodel.calculate_partitionfunction(
        hnco.data['States'], temperature=tex.value)
    assert len(partition_func) == 1
    Q_rot = tuple(partition_func.values())[0]

    Ntot = np.exp(result.intercept + np.log(Q_rot)) * u.cm**-2

    if verbose:
        print(("Tex={0}, Ntot={1}, Q_rot={2}".format(tex, Ntot, Q_rot)))

    if plot:
        import pylab as pl
        L, = pl.plot(eupper, np.log10(nupperoverg), 'o')
        xax = np.array([0, eupper.max().value])
        line = (xax * result.slope.value + result.intercept.value)
        pl.plot(xax,
                np.log10(np.exp(line)),
                '-',
                color=L.get_color(),
                label='$T={0:0.1f} \log(N)={1:0.1f}$'.format(
                    tex, np.log10(Ntot.value)))

    return Ntot, tex, result.slope, result.intercept
Beispiel #2
0
def sio_model(xarr, vcen, width, tex, column, background=None, tbg=2.73):

    if hasattr(tex, 'unit'):
        tex = tex.value
    if hasattr(tbg, 'unit'):
        tbg = tbg.value
    if hasattr(column, 'unit'):
        column = column.value
    if column < 25:
        column = 10**column
    if hasattr(vcen, 'unit'):
        vcen = vcen.value
    if hasattr(width, 'unit'):
        width = width.value

    if background is not None:
        tbg = background

    # assume equal-width channels
    kwargs = dict(rest=ref_freq)
    equiv = u.doppler_radio(**kwargs)
    channelwidth = np.abs(xarr[1].to(u.Hz, equiv) -
                          xarr[0].to(u.Hz, equiv)).value
    velo = xarr.to(u.km / u.s, equiv).value
    mol_model = np.zeros_like(xarr).value

    freqs_ = freqs.to(u.Hz).value

    Q = m.calculate_partitionfunction(result.data['States'],
                                      temperature=tex)[sio.Id]

    jnu_bg = lte_molecule.Jnu_cgs(xarr.to(u.Hz).value, tbg)
    bg_model = np.ones_like(xarr).value * jnu_bg

    for voff, A, g, nu, eu in zip(vdiff, aij, deg, freqs_, EU):
        tau_per_dnu = lte_molecule.line_tau_cgs(tex, column, Q, g, nu, eu,
                                                10**A)
        s = np.exp(-(velo - vcen - voff)**2 /
                   (2 * width**2)) * tau_per_dnu / channelwidth
        jnu_mol = lte_molecule.Jnu_cgs(nu, tex)

        # the "emission" model is generally zero everywhere, so we can just
        # add to it as we move along
        mol_model = mol_model + jnu_mol * (1 - np.exp(-s))

        # background is assumed to start as a uniform value, then each
        # absorption line multiplies to reduce it.  s is zero for most velocities,
        # so this is mostly bg_model *= 1
        bg_model *= np.exp(-s)

    if background:
        # subtract jnu_bg because we *must* rezero for the case of
        # having multiple components, otherwise the backgrounds add,
        # which is nonsense
        model = bg_model + mol_model - jnu_bg
    else:
        model = mol_model

    return model
def pyspeckitfit(eupper, kkms, frequencies, degeneracies, einsteinAs, verbose=False, plot=False, guess=(150, 1e19)):
    """
    Fit the Boltzmann diagram (but do it right, with no approximations, just
    direct forward modeling)

    This doesn't seem to work, though: it can't reproduce its own output
    """

    bandwidth = (1 * u.km / u.s / constants.c) * (frequencies)

    def model(tex, col, einsteinAs=einsteinAs, eupper=eupper):
        tex = u.Quantity(tex, u.K)
        col = u.Quantity(col, u.cm ** -2)
        eupper = eupper.to(u.erg, u.temperature_energy())
        einsteinAs = u.Quantity(einsteinAs, u.Hz)

        partition_func = specmodel.calculate_partitionfunction(ch3oh.data["States"], temperature=tex.value)
        assert len(partition_func) == 1
        Q_rot = tuple(partition_func.values())[0]
        return lte_molecule.line_brightness(
            tex,
            bandwidth,
            frequencies,
            total_column=col,
            partition_function=Q_rot,
            degeneracy=degeneracies,
            energy_upper=eupper,
            einstein_A=einsteinAs,
        )

    def minmdl(args):
        tex, col = args
        return ((model(tex, col).value - kkms) ** 2).sum()

    from scipy import optimize

    result = optimize.minimize(minmdl, guess, method="Nelder-Mead")
    tex, Ntot = result.x

    if plot:
        import pylab as pl

        pl.subplot(2, 1, 1)
        pl.plot(eupper, kkms, "o")
        order = np.argsort(eupper)
        pl.plot(eupper[order], model(tex, Ntot)[order])
        pl.subplot(2, 1, 2)
        xax = np.array([0, eupper.max().value])
        pl.plot(eupper, np.log(nupper_of_kkms(kkms, frequencies, einsteinAs, degeneracies).value), "o")
        partition_func = specmodel.calculate_partitionfunction(ch3oh.data["States"], temperature=tex)
        assert len(partition_func) == 1
        Q_rot = tuple(partition_func.values())[0]
        intercept = np.log(Ntot) - np.log(Q_rot)
        pl.plot(
            xax, np.log(xax * tex + intercept), "-", label="$T={0:0.1f} \log(N)={1:0.1f}$".format(tex, np.log10(Ntot))
        )

    return Ntot, tex, result
Beispiel #4
0
def ntot_of_nupper(nupper, eupper, tex, degen=1):

    partition_func = specmodel.calculate_partitionfunction(
        vamdc_result.data['States'], temperature=tex.value)[sio.Id]
    Q_rot = partition_func

    Ntot = nupper * (Q_rot / degen) * np.exp(eupper / (constants.k_B * tex))

    return Ntot
def fit_tex(eupper, nupperoverg, errors=None, verbose=False, plot=False):
    """
    Fit the Boltzmann diagram
    """
    model = modeling.models.Linear1D()
    #fitter = modeling.fitting.LevMarLSQFitter()
    fitter = modeling.fitting.LinearLSQFitter()
    # ignore negatives
    good = nupperoverg > 0
    if good.sum() < len(nupperoverg) / 2.:
        return 0 * u.cm**-2, 0 * u.K, 0, 0
    if errors is not None:
        weights = 1 / errors**2
    else:
        weights = None
    result = fitter(model,
                    eupper[good],
                    np.log(nupperoverg[good]),
                    weights=weights[good])
    tex = -1. / result.slope * u.K

    assert not np.isnan(tex)

    partition_func = specmodel.calculate_partitionfunction(
        vamdc_result.data['States'], temperature=tex.value)[ch3cn.Id]
    Q_rot = partition_func

    Ntot = np.exp(result.intercept + np.log(Q_rot)) * u.cm**-2

    if verbose:
        print(("Tex={0}, Ntot={1}, Q_rot={2}".format(tex, Ntot, Q_rot)))

    if plot:
        import pylab as pl
        if errors is not None:
            L, _, _ = pl.errorbar(
                x=eupper.value,
                y=np.log10(nupperoverg),
                marker='o',
                linestyle='none',
                yerr=np.array([
                    np.log10(nupperoverg) - np.log10(nupperoverg - errors),
                    np.log10(nupperoverg + errors) - np.log10(nupperoverg)
                ]))
        else:
            L, = pl.plot(eupper, np.log10(nupperoverg), 'o')
        xax = np.array([0, eupper.max().value])
        line = (xax * result.slope.value + result.intercept.value)
        pl.plot(xax,
                np.log10(np.exp(line)),
                '-',
                color=L.get_color(),
                label='$T={0:0.1f} \log(N)={1:0.1f}$'.format(
                    tex, np.log10(Ntot.value)))

    return Ntot, tex, result.slope, result.intercept
def pyspeckitfit(eupper, kkms, frequencies, degeneracies, einsteinAs,
                 verbose=False, plot=False, guess=(150,1e19)):
    """
    Fit the Boltzmann diagram (but do it right, with no approximations, just
    direct forward modeling)

    This doesn't seem to work, though: it can't reproduce its own output
    """

    bandwidth = (1*u.km/u.s/constants.c)*(frequencies)

    def model(tex, col, einsteinAs=einsteinAs, eupper=eupper):
        tex = u.Quantity(tex, u.K)
        col = u.Quantity(col, u.cm**-2)
        eupper = eupper.to(u.erg, u.temperature_energy())
        einsteinAs = u.Quantity(einsteinAs, u.Hz)

        partition_func = specmodel.calculate_partitionfunction(ch3oh.data['States'],
                                                               temperature=tex.value)
        assert len(partition_func) == 1
        Q_rot = tuple(partition_func.values())[0]
        return lte_molecule.line_brightness(tex, bandwidth, frequencies,
                                            total_column=col,
                                            partition_function=Q_rot,
                                            degeneracy=degeneracies,
                                            energy_upper=eupper,
                                            einstein_A=einsteinAs)

    def minmdl(args):
        tex, col = args
        return ((model(tex, col).value - kkms)**2).sum()

    from scipy import optimize
    result = optimize.minimize(minmdl, guess, method='Nelder-Mead')
    tex, Ntot = result.x

    if plot:
        import pylab as pl
        pl.subplot(2,1,1)
        pl.plot(eupper, kkms, 'o')
        order = np.argsort(eupper)
        pl.plot(eupper[order], model(tex, Ntot)[order])
        pl.subplot(2,1,2)
        xax = np.array([0, eupper.max().value])
        pl.plot(eupper, np.log(nupper_of_kkms(kkms, frequencies, einsteinAs, degeneracies).value), 'o')
        partition_func = specmodel.calculate_partitionfunction(ch3oh.data['States'],
                                                               temperature=tex)
        assert len(partition_func) == 1
        Q_rot = tuple(partition_func.values())[0]
        intercept = np.log(Ntot) - np.log(Q_rot)
        pl.plot(xax, np.log(xax*tex + intercept), '-',
                label='$T={0:0.1f} \log(N)={1:0.1f}$'.format(tex, np.log10(Ntot)))

    return Ntot, tex, result
def ch3oh_model(xarr, vcen, width, tex, column, background=None, tbg=2.73):

    if hasattr(tex,'unit'):
        tex = tex.value
    if hasattr(tbg,'unit'):
        tbg = tbg.value
    if hasattr(column, 'unit'):
        column = column.value
    if column < 25:
        column = 10**column
    if hasattr(vcen, 'unit'):
        vcen = vcen.value
    if hasattr(width, 'unit'):
        width = width.value

    ckms = constants.c.to(u.km/u.s).value

    # assume equal-width channels
    #kwargs = dict(rest=ref_freq)
    #equiv = u.doppler_radio(**kwargs)
    #channelwidth = np.abs(xarr[1].to(u.Hz, ) - xarr[0].to(u.Hz, )).value
    #channelwidth = xarr.as_unit(u.Hz).dxarr
    #velo = xarr.to(u.km/u.s, equiv).value
    freq = xarr.to(u.Hz).value # same unit as nu below
    model = np.zeros_like(xarr).value

    freqs_ = freqs.to(u.Hz).value

    Q = specmodel.calculate_partitionfunction(result.data['States'],
                                              temperature=tex)[ch3oh.Id]

    for A, g, nu, eu in zip(aij, deg, freqs_, EU):
        taudnu = lte_molecule.line_tau_cgs(tex,
                                           column,
                                           Q,
                                           g,
                                           nu,
                                           eu,
                                           10**A)
        width_dnu = width / ckms * nu
        effective_linewidth_dnu = (2 * np.pi)**0.5 * width_dnu
        fcen = (1 - vcen/ckms) * nu
        tauspec = (np.exp(-(freq - fcen)**2 / (2 * width_dnu**2)) *
                   taudnu/effective_linewidth_dnu)
        jnu = (lte_molecule.Jnu_cgs(nu, tex)-lte_molecule.Jnu_cgs(nu, tbg))

        model = model + jnu*(1-np.exp(-tauspec))

    if background is not None:
        return background-model
    return model
    def model(tex, col, einsteinAs=einsteinAs, eupper=eupper):
        tex = u.Quantity(tex, u.K)
        col = u.Quantity(col, u.cm**-2)
        eupper = eupper.to(u.erg, u.temperature_energy())
        einsteinAs = u.Quantity(einsteinAs, u.Hz)

        partition_func = specmodel.calculate_partitionfunction(ch3oh.data['States'],
                                                               temperature=tex.value)
        assert len(partition_func) == 1
        Q_rot = tuple(partition_func.values())[0]
        return lte_molecule.line_brightness(tex, bandwidth, frequencies,
                                            total_column=col,
                                            partition_function=Q_rot,
                                            degeneracy=degeneracies,
                                            energy_upper=eupper,
                                            einstein_A=einsteinAs)
    def model(tex, col, einsteinAs=einsteinAs, eupper=eupper):
        tex = u.Quantity(tex, u.K)
        col = u.Quantity(col, u.cm**-2)
        eupper = eupper.to(u.erg, u.temperature_energy())
        einsteinAs = u.Quantity(einsteinAs, u.Hz)

        partition_func = specmodel.calculate_partitionfunction(hnco.data['States'],
                                                               temperature=tex.value)
        assert len(partition_func) == 1
        Q_rot = tuple(partition_func.values())[0]
        return lte_molecule.line_brightness(tex, bandwidth, frequencies,
                                            total_column=col,
                                            partition_function=Q_rot,
                                            degeneracy=degeneracies,
                                            energy_upper=eupper,
                                            einstein_A=einsteinAs)
Beispiel #10
0
def hnco_model(xarr, vcen, width, tex, column, background=None, tbg=2.73):

    if hasattr(tex,'unit'):
        tex = tex.value
    if hasattr(tbg,'unit'):
        tbg = tbg.value
    if hasattr(column, 'unit'):
        column = column.value
    if column < 25:
        column = 10**column
    if hasattr(vcen, 'unit'):
        vcen = vcen.value
    if hasattr(width, 'unit'):
        width = width.value

    ckms = constants.c.to(u.km/u.s).value

    # assume equal-width channels
    #kwargs = dict(rest=ref_freq)
    #equiv = u.doppler_radio(**kwargs)
    channelwidth = np.abs(xarr[1].to(u.Hz, ) - xarr[0].to(u.Hz, )).value
    #velo = xarr.to(u.km/u.s, equiv).value
    freq = xarr.to(u.Hz).value # same unit as nu below
    model = np.zeros_like(xarr).value

    freqs_ = freqs.to(u.Hz).value

    Q = specmodel.calculate_partitionfunction(result.data['States'],
                                              temperature=tex)[hnco.Id]

    for A, g, nu, eu in zip(aij, deg, freqs_, EU):
        tau_per_dnu = lte_molecule.line_tau_cgs(tex,
                                                column,
                                                Q,
                                                g,
                                                nu,
                                                eu,
                                                10**A)
        width_dnu = width / ckms * nu
        s = np.exp(-(freq-(1-vcen/ckms)*nu)**2/(2*width_dnu**2))*tau_per_dnu/channelwidth
        jnu = (lte_molecule.Jnu_cgs(nu, tex)-lte_molecule.Jnu_cgs(nu, tbg))

        model = model + jnu*(1-np.exp(-s))

    if background is not None:
        return background-model
    return model
def fit_tex(eupper, nupperoverg, errors=None, verbose=False, plot=False):
    """
    Fit the Boltzmann diagram
    """
    model = modeling.models.Linear1D()
    #fitter = modeling.fitting.LevMarLSQFitter()
    fitter = modeling.fitting.LinearLSQFitter()
    # ignore negatives
    good = nupperoverg > 0
    if good.sum() < len(nupperoverg)/2.:
        return 0*u.cm**-2, 0*u.K, 0, 0
    if errors is not None:
        weights = 1/errors**2
    else:
        weights=None
    result = fitter(model, eupper[good], np.log(nupperoverg[good]),
                    weights=weights[good])
    tex = -1./result.slope*u.K

    assert not np.isnan(tex)

    partition_func = specmodel.calculate_partitionfunction(vamdc_result.data['States'],
                                                           temperature=tex.value)[ch3cn.Id]
    Q_rot = partition_func

    Ntot = np.exp(result.intercept + np.log(Q_rot)) * u.cm**-2

    if verbose:
        print(("Tex={0}, Ntot={1}, Q_rot={2}".format(tex, Ntot, Q_rot)))

    if plot:
        import pylab as pl
        if errors is not None:
            L,_,_ = pl.errorbar(x=eupper.value, y=np.log10(nupperoverg),
                                marker='o', linestyle='none',
                                yerr=np.array([np.log10(nupperoverg)-np.log10(nupperoverg-errors),
                                               np.log10(nupperoverg+errors)-np.log10(nupperoverg)]))
        else:
            L, = pl.plot(eupper, np.log10(nupperoverg), 'o')
        xax = np.array([0, eupper.max().value])
        line = (xax*result.slope.value +
                result.intercept.value)
        pl.plot(xax, np.log10(np.exp(line)), '-', color=L.get_color(),
                label='$T={0:0.1f} \log(N)={1:0.1f}$'.format(tex, np.log10(Ntot.value)))

    return Ntot, tex, result.slope, result.intercept
def ch3cn_model(xarr, vcen, width, tex, column, background=None, tbg=2.73):

    if hasattr(tex,'unit'):
        tex = tex.value
    if hasattr(tbg,'unit'):
        tbg = tbg.value
    if hasattr(column, 'unit'):
        column = column.value
    if column < 25:
        column = 10**column
    if hasattr(vcen, 'unit'):
        vcen = vcen.value
    if hasattr(width, 'unit'):
        width = width.value

    # assume equal-width channels
    kwargs = dict(rest=ref_freq)
    equiv = u.doppler_radio(**kwargs)
    channelwidth = np.abs(xarr[1].to(u.Hz, equiv) - xarr[0].to(u.Hz, equiv)).value
    velo = xarr.to(u.km/u.s, equiv).value
    model = np.zeros_like(xarr).value

    freqs_ = freqs.to(u.Hz).value

    Q = m.calculate_partitionfunction(result.data['States'],
                                      temperature=tex)[ch3cn.Id]

    for voff, A, g, nu, eu in zip(vdiff, aij, deg, freqs_, EU):
        tau_per_dnu = lte_molecule.line_tau_cgs(tex,
                                                column,
                                                Q,
                                                g,
                                                nu,
                                                eu,
                                                10**A)
        s = np.exp(-(velo-vcen-voff)**2/(2*width**2))*tau_per_dnu/channelwidth
        jnu = (lte_molecule.Jnu_cgs(nu, tex)-lte_molecule.Jnu_cgs(nu, tbg))

        model = model + jnu*(1-np.exp(-s))

    if background is not None:
        return background-model
    return model
def hnco_model(xarr, vcen, width, tex, column, background=None, tbg=2.73):

    if hasattr(tex, "unit"):
        tex = tex.value
    if hasattr(tbg, "unit"):
        tbg = tbg.value
    if hasattr(column, "unit"):
        column = column.value
    if column < 25:
        column = 10 ** column
    if hasattr(vcen, "unit"):
        vcen = vcen.value
    if hasattr(width, "unit"):
        width = width.value

    ckms = constants.c.to(u.km / u.s).value

    # assume equal-width channels
    # kwargs = dict(rest=ref_freq)
    # equiv = u.doppler_radio(**kwargs)
    channelwidth = np.abs(xarr[1].to(u.Hz) - xarr[0].to(u.Hz)).value
    # velo = xarr.to(u.km/u.s, equiv).value
    freq = xarr.to(u.Hz).value  # same unit as nu below
    model = np.zeros_like(xarr).value

    freqs_ = freqs.to(u.Hz).value

    Q = specmodel.calculate_partitionfunction(result.data["States"], temperature=tex)[hnco.Id]

    for A, g, nu, eu in zip(aij, deg, freqs_, EU):
        tau_per_dnu = lte_molecule.line_tau_cgs(tex, column, Q, g, nu, eu, 10 ** A)
        width_dnu = width / ckms * nu
        s = np.exp(-(freq - (1 - vcen / ckms) * nu) ** 2 / (2 * width_dnu ** 2)) * tau_per_dnu / channelwidth
        jnu = lte_molecule.Jnu_cgs(nu, tex) - lte_molecule.Jnu_cgs(nu, tbg)

        model = model + jnu * (1 - np.exp(-s))

    if background is not None:
        return background - model
    return model
def fit_tex(eupper, nupperoverg, verbose=False, plot=False, uplims=None,
            errors=None, min_nupper=1,
            replace_errors_with_uplims=False,
            max_uplims='half'):
    """
    Fit the Boltzmann diagram

    Parameters
    ----------
    max_uplims: str or number
        The maximum number of upper limits before the fit is ignored completely
        and instead zeros are returned
    """
    model = modeling.models.Linear1D()
    #fitter = modeling.fitting.LevMarLSQFitter()
    fitter = modeling.fitting.LinearLSQFitter()

    nupperoverg_tofit = nupperoverg.copy()

    if uplims is not None:
        upperlim_mask = nupperoverg < uplims

        # allow this magical keyword 'half'
        max_uplims = len(nupperoverg)/2. if max_uplims == 'half' else max_uplims

        if upperlim_mask.sum() > max_uplims:
            # too many upper limits = bad idea to fit.
            return 0*u.cm**-2, 0*u.K, 0, 0
        
        if errors is None:
            # if errors are not specified, we set the upper limits as actual values
            # (which gives a somewhat useful upper limit on the temperature)
            nupperoverg_tofit[upperlim_mask] = uplims[upperlim_mask]
        else:
            # otherwise, we set the values to zero-column and set the errors to
            # be whatever the upper limits are (hopefully a 1-sigma upper
            # limit)
            # 1.0 here becomes 0.0 in log and makes the relative errors meaningful
            nupperoverg_tofit[upperlim_mask] = 1.0
            if replace_errors_with_uplims:
                errors[upperlim_mask] = uplims[upperlim_mask]

    # always ignore negatives & really low values
    good = nupperoverg_tofit > min_nupper
    # skip any fits that have fewer than 50% good values
    if good.sum() < len(nupperoverg_tofit)/2.:
        return 0*u.cm**-2, 0*u.K, 0, 0

    if errors is not None:
        rel_errors = errors / nupperoverg_tofit
        weights = 1. / rel_errors**2
        log.debug("Fitting with data = {0}, weights = {1}, errors = {2},"
                  "relative_errors = {3}"
                  .format(np.log(nupperoverg_tofit[good]),
                          np.log(weights[good]),
                          errors[good],
                          rel_errors[good],
                         ))
    else:
        # want log(weight) = 1
        weights = np.exp(np.ones_like(nupperoverg_tofit))

    result = fitter(model, eupper[good], np.log(nupperoverg_tofit[good]),
                    weights=np.log(weights[good]))
    tex = -1./result.slope*u.K

    partition_func = specmodel.calculate_partitionfunction(hnco.data['States'],
                                                           temperature=tex.value)
    assert len(partition_func) == 1
    Q_rot = tuple(partition_func.values())[0]

    Ntot = np.exp(result.intercept + np.log(Q_rot)) * u.cm**-2

    if verbose:
        print(("Tex={0}, Ntot={1}, Q_rot={2}, nuplim={3}".format(tex, Ntot, Q_rot, upperlim_mask.sum())))

    if plot:
        import pylab as pl
        L, = pl.plot(eupper, np.log10(nupperoverg_tofit), 'ro',
                     markeredgecolor='none', alpha=0.5)
        L, = pl.plot(eupper, np.log10(nupperoverg), 'bo', alpha=0.2)
        if errors is not None:
            yerr = np.array([np.log10(nupperoverg_tofit)-np.log10(nupperoverg_tofit-errors),
                             np.log10(nupperoverg_tofit+errors)-np.log10(nupperoverg_tofit)])
            # if lower limit is nan, set to zero
            yerr[0,:] = np.nan_to_num(yerr[0,:])
            if np.any(np.isnan(yerr[1,:])):
                print("*** Some upper limits are NAN")
            pl.errorbar(eupper.value,
                        np.log10(nupperoverg_tofit),
                        yerr=yerr,
                        linestyle='none',
                        linewidth=0.5,
                        marker='.', zorder=-5)
        xax = np.array([0, eupper.max().value])
        line = (xax*result.slope.value +
                result.intercept.value)
        pl.plot(xax, np.log10(np.exp(line)), '-', color=L.get_color(),
                alpha=0.3,
                label='$T={0:0.1f}$ $\log(N)={1:0.1f}$'.format(tex, np.log10(Ntot.value)))
        pl.ylabel("log N$_u$ (cm$^{-2}$)")
        pl.xlabel("E$_u$ (K)")

        if (uplims is not None) and ((errors is None) or replace_errors_with_uplims):
            # if errors are specified, their errorbars will be better
            # representations of what's actually being fit
            pl.plot(eupper, np.log10(uplims), marker='_', alpha=0.5,
                    linestyle='none', color='k')

    return Ntot, tex, result.slope, result.intercept
def fit_tex(eupper, nupperoverg, verbose=False, plot=False, uplims=None,
            errors=None, min_nupper=1,
            replace_errors_with_uplims=False,
            max_uplims='half'):
    """
    Fit the Boltzmann diagram

    Parameters
    ----------
    max_uplims: str or number
        The maximum number of upper limits before the fit is ignored completely
        and instead zeros are returned
    """
    model = modeling.models.Linear1D()
    #fitter = modeling.fitting.LevMarLSQFitter()
    fitter = modeling.fitting.LinearLSQFitter()

    nupperoverg_tofit = nupperoverg.copy()

    if uplims is not None:
        upperlim_mask = nupperoverg < uplims

        # allow this magical keyword 'half'
        max_uplims = len(nupperoverg)/2. if max_uplims == 'half' else max_uplims

        if upperlim_mask.sum() > max_uplims:
            # too many upper limits = bad idea to fit.
            return 0*u.cm**-2, 0*u.K, 0, 0
        
        if errors is None:
            # if errors are not specified, we set the upper limits as actual values
            # (which gives a somewhat useful upper limit on the temperature)
            nupperoverg_tofit[upperlim_mask] = uplims[upperlim_mask]
        else:
            # otherwise, we set the values to zero-column and set the errors to
            # be whatever the upper limits are (hopefully a 1-sigma upper
            # limit)
            # 1.0 here becomes 0.0 in log and makes the relative errors meaningful
            nupperoverg_tofit[upperlim_mask] = 1.0
            if replace_errors_with_uplims:
                errors[upperlim_mask] = uplims[upperlim_mask]

    # always ignore negatives & really low values
    good = nupperoverg_tofit > min_nupper
    # skip any fits that have fewer than 50% good values
    if good.sum() < len(nupperoverg_tofit)/2.:
        return 0*u.cm**-2, 0*u.K, 0, 0

    if errors is not None:
        rel_errors = errors / nupperoverg_tofit
        weights = 1. / rel_errors**2
        log.debug("Fitting with data = {0}, weights = {1}, errors = {2},"
                  "relative_errors = {3}"
                  .format(np.log(nupperoverg_tofit[good]),
                          np.log(weights[good]),
                          errors[good],
                          rel_errors[good],
                         ))
    else:
        # want log(weight) = 1
        weights = np.exp(np.ones_like(nupperoverg_tofit))

    result = fitter(model, eupper[good], np.log(nupperoverg_tofit[good]),
                    weights=np.log(weights[good]))
    tex = -1./result.slope*u.K

    partition_func = specmodel.calculate_partitionfunction(ch3oh.data['States'],
                                                           temperature=tex.value)
    assert len(partition_func) == 1
    Q_rot = tuple(partition_func.values())[0]

    Ntot = np.exp(result.intercept + np.log(Q_rot)) * u.cm**-2

    if verbose:
        print(("Tex={0}, Ntot={1}, Q_rot={2}, nuplim={3}".format(tex, Ntot, Q_rot, upperlim_mask.sum())))

    if plot:
        import pylab as pl
        L, = pl.plot(eupper, np.log10(nupperoverg_tofit), 'ro',
                     markeredgecolor='none', alpha=0.5)
        L, = pl.plot(eupper, np.log10(nupperoverg), 'bo', alpha=0.2)
        if errors is not None:
            yerr = np.array([np.log10(nupperoverg_tofit)-np.log10(nupperoverg_tofit-errors),
                             np.log10(nupperoverg_tofit+errors)-np.log10(nupperoverg_tofit)])
            # if lower limit is nan, set to zero
            yerr[0,:] = np.nan_to_num(yerr[0,:])
            if np.any(np.isnan(yerr[1,:])):
                print("*** Some upper limits are NAN")
            pl.errorbar(eupper.value,
                        np.log10(nupperoverg_tofit),
                        yerr=yerr,
                        linestyle='none',
                        linewidth=0.5,
                        marker='.', zorder=-5)
        xax = np.array([0, eupper.max().value])
        line = (xax*result.slope.value +
                result.intercept.value)
        pl.plot(xax, np.log10(np.exp(line)), '-', color=L.get_color(),
                alpha=0.3,
                label='$T={0:0.1f}$ $\log(N)={1:0.1f}$'.format(tex, np.log10(Ntot.value)))
        pl.ylabel("log N$_u$ (cm$^{-2}$)")
        pl.xlabel("E$_u$ (K)")

        if (uplims is not None) and ((errors is None) or replace_errors_with_uplims):
            # if errors are specified, their errorbars will be better
            # representations of what's actually being fit
            pl.plot(eupper, np.log10(uplims), marker='_', alpha=0.5,
                    linestyle='none', color='k')

    return Ntot, tex, result.slope, result.intercept
 def partfunc(tem):
     Q = list(
         specmodel.calculate_partitionfunction(result.data['States'],
                                               temperature=tem).values())[0]
     return Q
def get_molecular_parameters(molecule_name,
                             molecule_name_vamdc=None,
                             tex=50,
                             fmin=1 * u.GHz,
                             fmax=1 * u.THz,
                             line_lists=['SLAIM'],
                             chem_re_flags=0,
                             **kwargs):
    """
    Get the molecular parameters for a molecule from the CDMS database using
    vamdclib

    Parameters
    ----------
    molecule_name : string
        The string name of the molecule (normal name, like CH3OH or CH3CH2OH)
    molecule_name_vamdc : string or None
        If specified, gives this name to vamdc instead of the normal name.
        Needed for some molecules, like CH3CH2OH -> C2H5OH.
    tex : float
        Optional excitation temperature (basically checks if the partition
        function calculator works)
    fmin : quantity with frequency units
    fmax : quantity with frequency units
        The minimum and maximum frequency to search over
    line_lists : list
        A list of Splatalogue line list catalogs to search.  Valid options
        include SLAIM, CDMS, JPL.  Only a single catalog should be used to
        avoid repetition of transitions and species
    chem_re_flags : int
        An integer flag to be passed to splatalogue's chemical name matching
        tool

    Examples
    --------
    >>> freqs, aij, deg, EU, partfunc = get_molecular_parameters(molecule_name='CH2CHCN',
    ...                                                          fmin=220*u.GHz,
    ...                                                          fmax=222*u.GHz,
    ...                                                          molecule_name_vamdc='C2H3CN')
    >>> freqs, aij, deg, EU, partfunc = get_molecular_parameters('CH3OH',
    ...                                                          fmin=90*u.GHz,
    ...                                                          fmax=100*u.GHz)
    """
    from astroquery.vamdc import load_species_table
    from astroquery.splatalogue import Splatalogue

    from vamdclib import nodes
    from vamdclib import request
    from vamdclib import specmodel

    lut = load_species_table.species_lookuptable()
    species_id_dict = lut.find(molecule_name_vamdc or molecule_name,
                               flags=chem_re_flags)
    if len(species_id_dict) == 1:
        species_id = list(species_id_dict.values())[0]
    elif len(species_id_dict) == 0:
        raise ValueError("No matches for {0}".format(molecule_name))
    else:
        raise ValueError(
            "Too many species matched: {0}".format(species_id_dict))

    # do this here, before trying to compute the partition function, because
    # this query could fail
    tbl = Splatalogue.query_lines(fmin,
                                  fmax,
                                  chemical_name=molecule_name,
                                  line_lists=line_lists,
                                  show_upper_degeneracy=True,
                                  **kwargs)

    nl = nodes.Nodelist()
    nl.findnode('cdms')
    cdms = nl.findnode('cdms')

    request = request.Request(node=cdms)
    query_string = "SELECT ALL WHERE VAMDCSpeciesID='%s'" % species_id
    request.setquery(query_string)
    result = request.dorequest()
    Q = list(
        specmodel.calculate_partitionfunction(result.data['States'],
                                              temperature=tex).values())[0]

    def partfunc(tem):
        Q = list(
            specmodel.calculate_partitionfunction(result.data['States'],
                                                  temperature=tem).values())[0]
        return Q

    freqs = np.array(tbl['Freq-GHz']) * u.GHz
    aij = tbl['Log<sub>10</sub> (A<sub>ij</sub>)']
    deg = tbl['Upper State Degeneracy']
    EU = (np.array(tbl['E_U (K)']) * u.K * constants.k_B).to(u.erg).value

    return freqs, aij, deg, EU, partfunc
Beispiel #18
0
import numpy as np
import paths
import pylab as pl
from astroquery.vamdc import Vamdc
from vamdclib import specmodel

ch3oh = Vamdc.query_molecule('CH3OH')

temperatures = np.linspace(10, 300)

partition_func = [
    specmodel.calculate_partitionfunction(ch3oh.data['States'],
                                          temperature=tex)['XCDMS-149']
    for tex in temperatures
]

pl.matplotlib.rc_file('pubfiguresrc')
pl.figure(1).clf()
pl.plot(temperatures, partition_func)
pl.xlabel("T (K)")
pl.ylabel("$Q_{rot}$")
pl.savefig(paths.fpath("chemistry/ch3oh_partition_function.png"))
Beispiel #19
0
def fit_tex(
    eupper,
    nupperoverg,
    verbose=False,
    plot=False,
    uplims=None,
    errors=None,
    min_nupper=1,
    replace_errors_with_uplims=False,
    molecule=None,
    color='r',
    marker='o',
    max_uplims='half',
    label='',
):
    """
    Fit the Boltzmann diagram

    Parameters
    ----------
    max_uplims: str or number
        The maximum number of upper limits before the fit is ignored completely
        and instead zeros are returned
    """

    nupperoverg_tofit = nupperoverg.copy().to(u.cm**-2).value
    if errors is not None:
        errors = errors.to(u.cm**-2).value

    if uplims is not None:
        upperlim_mask = nupperoverg < uplims

        # allow this magical keyword 'half'
        max_uplims = len(
            nupperoverg) / 2. if max_uplims == 'half' else max_uplims

        if upperlim_mask.sum() > max_uplims:
            # too many upper limits = bad idea to fit.
            return 0 * u.cm**-2, 0 * u.K, 0, 0

        if errors is None:
            # if errors are not specified, we set the upper limits as values to
            # be fitted
            # (which gives a somewhat useful upper limit on the temperature)
            nupperoverg_tofit[upperlim_mask] = uplims[upperlim_mask]
        else:
            # otherwise, we set the values to zero-column and set the errors to
            # be whatever the upper limits are (hopefully a 1-sigma upper
            # limit)
            # 1.0 here becomes 0.0 in log and makes the relative errors meaningful
            nupperoverg_tofit[upperlim_mask] = 1.0
            if replace_errors_with_uplims:
                errors[upperlim_mask] = uplims[upperlim_mask]
    else:
        upperlim_mask = np.ones_like(nupperoverg_tofit, dtype='bool')

    # always ignore negatives & really low values
    good = nupperoverg_tofit > min_nupper
    # skip any fits that have fewer than 50% good values
    if good.sum() < len(nupperoverg_tofit) / 2.:
        return 0 * u.cm**-2, 0 * u.K, 0, 0

    if errors is not None:
        rel_errors = errors / nupperoverg_tofit
        weights = 1. / rel_errors**2
        log.debug("Fitting with data = {0}, weights = {1}, errors = {2},"
                  "relative_errors = {3}".format(
                      np.log(nupperoverg_tofit[good]),
                      np.log(weights[good]),
                      errors[good],
                      rel_errors[good],
                  ))
    else:
        # want log(weight) = 1
        weights = np.exp(np.ones_like(nupperoverg_tofit))

    #model = modeling.models.Linear1D()
    model = simple_lte_model_generator()
    fitter = modeling.fitting.LevMarLSQFitter()
    #fitter = modeling.fitting.LinearLSQFitter()
    #model.slope.bounds = [0, -1000]
    if good.sum() >= 2:
        result = fitter(model,
                        eupper[good].to(u.K).value,
                        np.log(nupperoverg_tofit[good]),
                        weights=np.log(weights[good]))
        #tex = u.Quantity(-1./result.slope, u.K)
        tex = u.Quantity(result.tem, u.K)
        if tex < 0 * u.K:
            tex = 100 * u.K

        partition_func = specmodel.calculate_partitionfunction(
            molecule.data['States'], temperature=tex.value)
        assert len(partition_func) == 1
        Q_rot = tuple(partition_func.values())[0]

        #Ntot = np.exp(result.intercept + np.log(Q_rot)) * u.cm**-2
        Ntot = np.exp(result.logcolumn) * Q_rot * u.cm**-2
    else:
        Ntot = 1e10 * u.cm**-2
        tex = 100 * u.K
        Q_rot = 1
        result = model

    if verbose:
        print(("Tex={0}, Ntot={1}, log(Ntot)={4}, Q_rot={2}, "
               "nuplim={3}".format(
                   tex,
                   Ntot,
                   Q_rot,
                   upperlim_mask.sum(),
                   np.log10(Ntot.value),
               )))

    if plot:
        import pylab as pl
        L, = pl.plot(
            eupper,
            np.log10(nupperoverg_tofit),
            marker=marker,
            color=color,
            markeredgecolor='none',
            alpha=0.5,
            linestyle='none',
            #markersize=2,
        )
        if uplims is not None:
            L, = pl.plot(eupper[upperlim_mask],
                         np.log10(uplims)[upperlim_mask],
                         'bv',
                         alpha=0.2,
                         markersize=2)
            #L, = pl.plot(eupper[upperlim_mask],
            #             np.log10(nupperoverg)[upperlim_mask], 'bv', alpha=0.2)
        if errors is not None:
            yerr = np.array([
                np.log10(nupperoverg_tofit) -
                np.log10(nupperoverg_tofit - errors),
                np.log10(nupperoverg_tofit + errors) -
                np.log10(nupperoverg_tofit)
            ])
            # if lower limit is nan, set to zero
            yerr[0, :] = np.nan_to_num(yerr[0, :])
            if np.any(np.isnan(yerr[1, :])):
                #raise ValueError("*** Some upper limits are NAN")
                print(ValueError("*** Some upper limits are NAN"))
            # use 'good' to exclude plotting errorbars for upper limits
            pl.errorbar(eupper.value[good],
                        np.log10(nupperoverg_tofit)[good],
                        yerr=yerr[:, good],
                        linestyle='none',
                        linewidth=0.5,
                        color='k',
                        marker='.',
                        zorder=-5,
                        markersize=2)
        xax = np.array([0, eupper.max().value + 500])
        line = (xax * (-1 / result.tem.value) + result.logcolumn.value)
        pl.plot(xax,
                np.log10(np.exp(line)),
                '--',
                color=color,
                alpha=0.6,
                linewidth=1.0,
                label='{2}$T={0:0.1f}$ $\log(N)={1:0.1f}$'.format(
                    tex, np.log10(Ntot.value), label))
        pl.ylabel("log N$_u$/g (cm$^{-2}$)")
        pl.xlabel("E$_u$ (K)")

        if (uplims is not None) and ((errors is None)
                                     or replace_errors_with_uplims):
            # if errors are specified, their errorbars will be better
            # representations of what's actually being fit
            pl.plot(eupper,
                    np.log10(uplims),
                    marker='_',
                    alpha=0.5,
                    linestyle='none',
                    color='k')

    return Ntot, tex, result.tem, result.logcolumn
Beispiel #20
0
def Qrot(temperature):
    Q = m.calculate_partitionfunction(result.data['States'],
                                      temperature=temperature)[h2co.Id]
    return Q
Beispiel #21
0
def get_molecular_parameters(molecule_name,
                             molecule_name_vamdc=None,
                             tex=50, fmin=1*u.GHz, fmax=1*u.THz,
                             line_lists=['SLAIM'],
                             chem_re_flags=0, **kwargs):
    """
    Get the molecular parameters for a molecule from the CDMS database using
    vamdclib

    Parameters
    ----------
    molecule_name : string
        The string name of the molecule (normal name, like CH3OH or CH3CH2OH)
    molecule_name_vamdc : string or None
        If specified, gives this name to vamdc instead of the normal name.
        Needed for some molecules, like CH3CH2OH -> C2H5OH.
    tex : float
        Optional excitation temperature (basically checks if the partition
        function calculator works)
    fmin : quantity with frequency units
    fmax : quantity with frequency units
        The minimum and maximum frequency to search over
    line_lists : list
        A list of Splatalogue line list catalogs to search.  Valid options
        include SLAIM, CDMS, JPL.  Only a single catalog should be used to
        avoid repetition of transitions and species
    chem_re_flags : int
        An integer flag to be passed to splatalogue's chemical name matching
        tool

    Examples
    --------
    >>> freqs, aij, deg, EU, partfunc = get_molecular_parameters(molecule_name='CH2CHCN',
    ...                                                          fmin=220*u.GHz,
    ...                                                          fmax=222*u.GHz,
    ...                                                          molecule_name_vamdc='C2H3CN')
    >>> freqs, aij, deg, EU, partfunc = get_molecular_parameters('CH3OH',
    ...                                                          fmin=90*u.GHz,
    ...                                                          fmax=100*u.GHz)
    """
    from astroquery.vamdc import load_species_table
    from astroquery.splatalogue import Splatalogue

    from vamdclib import nodes
    from vamdclib import request
    from vamdclib import specmodel

    lut = load_species_table.species_lookuptable()
    species_id_dict = lut.find(molecule_name_vamdc or molecule_name,
                               flags=chem_re_flags)
    if len(species_id_dict) == 1:
        species_id = list(species_id_dict.values())[0]
    elif len(species_id_dict) == 0:
        raise ValueError("No matches for {0}".format(molecule_name))
    else:
        raise ValueError("Too many species matched: {0}"
                         .format(species_id_dict))

    # do this here, before trying to compute the partition function, because
    # this query could fail
    tbl = Splatalogue.query_lines(fmin, fmax, chemical_name=molecule_name,
                                  line_lists=line_lists,
                                  show_upper_degeneracy=True, **kwargs)

    nl = nodes.Nodelist()
    nl.findnode('cdms')
    cdms = nl.findnode('cdms')

    request = request.Request(node=cdms)
    query_string = "SELECT ALL WHERE VAMDCSpeciesID='%s'" % species_id
    request.setquery(query_string)
    result = request.dorequest()
    # run it once to test that it works
    Q = list(specmodel.calculate_partitionfunction(result.data['States'],
                                                   temperature=tex).values())[0]

    def partfunc(tem):
        Q = list(specmodel.calculate_partitionfunction(result.data['States'],
                                                       temperature=tem).values())[0]
        return Q


    freqs = (np.array(tbl['Freq-GHz'])*u.GHz if 'Freq-GHz' in tbl else
             np.array(tbl['Freq-GHz(rest frame,redshifted)'])*u.GHz)
    aij = tbl['Log<sub>10</sub> (A<sub>ij</sub>)']
    deg = tbl['Upper State Degeneracy']
    EU = (np.array(tbl['E_U (K)'])*u.K*constants.k_B).to(u.erg).value

    return freqs, aij, deg, EU, partfunc
import numpy as np
import paths
import pylab as pl
from astroquery.vamdc import Vamdc
from vamdclib import specmodel

ch3oh = Vamdc.query_molecule("CH3OH")

temperatures = np.linspace(10, 300)

partition_func = [
    specmodel.calculate_partitionfunction(ch3oh.data["States"], temperature=tex)["XCDMS-149"] for tex in temperatures
]


pl.matplotlib.rc_file("pubfiguresrc")
pl.figure(1).clf()
pl.plot(temperatures, partition_func)
pl.xlabel("T (K)")
pl.ylabel("$Q_{rot}$")
pl.savefig(paths.fpath("chemistry/ch3oh_partition_function.png"))
Beispiel #23
0
 def partfunc(tem):
     Q = list(specmodel.calculate_partitionfunction(result.data['States'],
                                                    temperature=tem).values())[0]
     return Q
Beispiel #24
0
def fit_multi_tex(eupper,
                  nupperoverg,
                  vstate,
                  jstate,
                  vibenergies,
                  rotenergies,
                  verbose=False,
                  plot=False,
                  uplims=None,
                  errors=None,
                  min_nupper=1,
                  replace_errors_with_uplims=False,
                  molecule=None,
                  colors='rgbcmyk',
                  molname=None,
                  collims=(np.log(1e9), np.log(1e17)),
                  rottemlims=(10, 300),
                  vibtemlims=(500, 16000),
                  marker='o',
                  max_uplims='half'):
    """
    Fit the Boltzmann diagram with a vibrational and a rotational temperature

    Parameters
    ----------
    max_uplims: str or number
        The maximum number of upper limits before the fit is ignored completely
        and instead zeros are returned
    """

    nupperoverg_tofit = nupperoverg.copy().to(u.cm**-2).value
    if errors is not None:
        errors = errors.to(u.cm**-2).value

    if uplims is not None:
        upperlim_mask = nupperoverg < uplims

        # allow this magical keyword 'half'
        max_uplims = len(
            nupperoverg) / 2. if max_uplims == 'half' else max_uplims

        if upperlim_mask.sum() > max_uplims:
            # too many upper limits = bad idea to fit.
            return 0 * u.cm**-2, 0 * u.K, 0, 0

        if errors is None:
            # if errors are not specified, we set the upper limits as values to
            # be fitted
            # (which gives a somewhat useful upper limit on the temperature)
            nupperoverg_tofit[upperlim_mask] = uplims[upperlim_mask]
        else:
            # otherwise, we set the values to zero-column and set the errors to
            # be whatever the upper limits are (hopefully a 1-sigma upper
            # limit)
            # 1.0 here becomes 0.0 in log and makes the relative errors meaningful
            nupperoverg_tofit[upperlim_mask] = 1.0
            if replace_errors_with_uplims:
                errors[upperlim_mask] = uplims[upperlim_mask]
    else:
        upperlim_mask = np.ones_like(nupperoverg_tofit, dtype='bool')

    # always ignore negatives & really low values
    good = nupperoverg_tofit > min_nupper
    # skip any fits that have fewer than 50% good values
    if good.sum() < len(nupperoverg_tofit) / 2.:
        return 0 * u.cm**-2, 0 * u.K, 0, 0

    if errors is not None:
        rel_errors = errors / nupperoverg_tofit
        weights = 1. / rel_errors**2
        log.debug("Fitting with data = {0}, weights = {1}, errors = {2},"
                  "relative_errors = {3}".format(
                      np.log(nupperoverg_tofit[good]),
                      np.log(weights[good]),
                      errors[good],
                      rel_errors[good],
                  ))
    else:
        # want log(weight) = 1
        weights = np.exp(np.ones_like(nupperoverg_tofit))

    model = rovib_lte_model_generator(vibenergies, rotenergies)

    #print('vibenergies:',vibenergies, '\nrotenergies:', rotenergies)

    fitter = modeling.fitting.LevMarLSQFitter()
    #fitter = modeling.fitting.LinearLSQFitter()

    model_to_fit = model
    model_to_fit.logcolumn.bounds = collims
    model_to_fit.rottem.bounds = rottemlims
    model_to_fit.vibtem.bounds = vibtemlims
    #model_to_fit.rottem.fixed = True

    # check that the model works
    #first_check = model_to_fit(eupper[good].to(u.K).value)
    #print("Result of first check: ",first_check)

    result = fitter(model_to_fit,
                    jstate[good].astype('int'),
                    vstate[good].astype('int'),
                    np.log(nupperoverg_tofit[good]),
                    weights=np.log(weights[good]))
    print(result)
    tex = result.rottem  #u.Quantity(-1./result.slope, u.K)
    tvib = result.vibtem

    partition_func = specmodel.calculate_partitionfunction(
        molecule.data['States'], temperature=tex.value)
    assert len(partition_func) == 1
    Q_rot = tuple(partition_func.values())[0]
    print("Q_rot:", Q_rot)

    Ntot = np.exp(result.logcolumn + np.log(Q_rot)) * u.cm**-2

    if verbose:
        print(("Tex={0}, Ntot={1}, log(Ntot)={4}, Q_rot={2}, "
               "nuplim={3}".format(
                   tex,
                   Ntot,
                   Q_rot,
                   upperlim_mask.sum(),
                   np.log10(Ntot.value),
               )))

    if plot:
        import pylab as pl
        for vib, color in zip(np.arange(vstate.max() + 1), colors):
            mask = vstate == vib
            if mask.sum() == 0:
                continue
            L, = pl.plot(
                eupper[mask],
                np.log10(nupperoverg_tofit[mask]),
                marker=marker,
                color=color,
                markeredgecolor='none',
                alpha=0.5,
                linestyle='none',
                #markersize=2,
            )
            if uplims is not None:
                L, = pl.plot(eupper[upperlim_mask & mask],
                             np.log10(uplims)[upperlim_mask & mask],
                             'bv',
                             alpha=0.2,
                             markersize=2)
                #L, = pl.plot(eupper[upperlim_mask],
                #             np.log10(nupperoverg)[upperlim_mask], 'bv', alpha=0.2)
            if errors is not None:
                yerr = np.array([
                    np.log10(nupperoverg_tofit) -
                    np.log10(nupperoverg_tofit - errors),
                    np.log10(nupperoverg_tofit + errors) -
                    np.log10(nupperoverg_tofit)
                ])
                # if lower limit is nan, set to zero
                yerr[0, :] = np.nan_to_num(yerr[0, :])
                if np.any(np.isnan(yerr[1, :])):
                    print(ValueError("*** Some upper limits are NAN"))
                # use 'good' to exclude plotting errorbars for upper limits
                pl.errorbar(eupper.value[good & mask],
                            np.log10(nupperoverg_tofit)[good & mask],
                            yerr=yerr[:, good & mask],
                            linestyle='none',
                            linewidth=0.5,
                            color=L.get_color(),
                            marker='.',
                            zorder=-5,
                            markersize=2)
            #xax = np.array([0, eupper.max().value+500])

            jstates = np.arange(1, np.max(list(rotenergies.keys())) + 1)
            vstates = np.ones(np.max(list(rotenergies.keys()))) * vib
            line = result(jstates, vstates)
            xax = np.array(
                [rotenergies[ju] + vibenergies[vib] for ju in jstates])

            for marker, mask in (('.', (jstates > -1) & (jstates <= 10)),
                                 ('s', (jstates > 10) & (jstates <= 20)),
                                 ('o', (jstates > 20) & (jstates <= 30)),
                                 ('D', (jstates > 30) & (jstates <= 40)),
                                 ('p', (jstates > 40) & (jstates <= 50))):
                pl.plot(
                    xax[mask],
                    np.log10(np.exp(line))[mask],
                    marker=marker,
                    linestyle='none',
                    color=color,
                    alpha=0.3,
                    linewidth=1.0,
                    markersize=4,
                )
                #label='v={0}'.format(vib),
                #label=('v={2}$T_R={0:0.1f}$ $T_v={3:0.1f}$ $\log(N)={1:0.1f}$'
                #       .format(tex.value, np.log10(Ntot.value), vib,
                #               tvib.value
                #              ))

        for jj in np.unique(jstate):
            #jstates = np.arange(1,np.max(list(rotenergies.keys()))+1)
            #vstates = np.ones(np.max(list(rotenergies.keys())))*vib
            vstates = np.arange(9)
            jstates = np.ones(9) * jj
            line = result(jstates, vstates)
            xax = np.array(
                [rotenergies[jj] + vibenergies[vv] for vv in vstates])

            pl.plot(
                xax,
                np.log10(np.exp(line)),
                '--',
                color='k',
                alpha=0.1,
                linewidth=1.0,
            )
            #label="Ju={0}".format(jj),
            #label=('v={2}$T_R={0:0.1f}$ $T_v={3:0.1f}$ $\log(N)={1:0.1f}$'
            #       .format(tex.value, np.log10(Ntot.value), vib,
            #               tvib.value
            #              ))

        for eu, nu, jj, vv in zip(eupper, nupperoverg_tofit, jstate, vstate):
            model_nu = result(jj, vv)
            #print("POINT MATCHING: ",eu, model_nu, np.log(nu), jj, vv)
            pl.plot(u.Quantity([eu, eu]),
                    np.log10(np.exp([np.log(nu), model_nu])),
                    linestyle='-',
                    alpha=0.3,
                    color='k',
                    linewidth=0.5)

        pl.ylabel("log N$_u$/g (cm$^{-2}$)")
        pl.xlabel("E$_u$ (K)")

        pl.plot(
            [], [],
            linestyle='-',
            color='k',
            label=('$T_R={0:0.1f}$ $T_v={2:0.1f}$ $\log(N)={1:0.1f}$'.format(
                tex.value, np.log10(Ntot.value), tvib.value)))

        if (uplims is not None) and ((errors is None)
                                     or replace_errors_with_uplims):
            # if errors are specified, their errorbars will be better
            # representations of what's actually being fit
            pl.plot(eupper,
                    np.log10(uplims),
                    marker='_',
                    alpha=0.5,
                    linestyle='none',
                    color='k')

    return Ntot, tex, result.rottem, result.vibtem, result.logcolumn