Exemplo n.º 1
0
    def from_jplspec(cls, temp_estimate, transition_freq, mol_tag):
        """Returns relevant constants from JPLSpec catalog and energy
        calculations

        Parameters
        ----------
        temp_estimate : `~astropy.units.Quantity`
            Estimated temperature in Kelvins

        transition_freq : `~astropy.units.Quantity`
            Transition frequency in MHz

        mol_tag : int or str
            Molecule identifier. Make sure it is an exclusive identifier,
            although this function can take a regex as your molecule tag,
            it will return an error if there is ambiguity on what the
            molecule of interest is. The function
            `~astroquery.jplspec.JPLSpec.query_lines_async`
            with the option `parse_name_locally=True` can be used to parse
            for the exclusive identifier of a molecule you might be
            interested in. For more information, visit
            `astroquery.jplspec` documentation.

        Returns
        -------
        Molecular data : `~sbpy.data.Phys` instance
            Quantities in the following order from JPL Spectral Molecular Catalog:
                | Transition frequency
                | Temperature
                | Integrated line intensity at 300 K
                | Partition function at 300 K
                | Partition function at designated temperature
                | Upper state degeneracy
                | Upper level energy in Joules
                | Lower level energy in Joules
                | Degrees of freedom

        """

        if isinstance(mol_tag, str):
            query = JPLSpec.query_lines_async(
                min_frequency=(transition_freq - (1 * u.GHz)),
                max_frequency=(transition_freq + (1 * u.GHz)),
                molecule=mol_tag,
                parse_name_locally=True,
                get_query_payload=True)

            res = dict(query)
            # python request payloads aren't stable (could be
            # dictionary or list)
            # depending on the version, so make
            # sure to check back from time to time
            if len(res['Mol']) > 1:
                raise JPLSpecQueryFailed(("Ambiguous choice for molecule,\
                    more than one molecule was found for \
                    the given mol_tag. Please refine \
                    your search to one of the following tags\
                    {} by using JPLSpec.get_species_table()\
                    (as shown in JPLSpec documentation)\
                    to parse their names and choose your \
                    molecule of interest, or refine your\
                    regex to be more specific (hint '^name$'\
                    will match 'name' exactly with no\
                    ambiguity).").format(res['Mol']))
            else:
                mol_tag = res['Mol'][0]

        query = JPLSpec.query_lines(
            min_frequency=(transition_freq - (1 * u.GHz)),
            max_frequency=(transition_freq + (1 * u.GHz)),
            molecule=mol_tag)

        freq_list = query['FREQ']

        if freq_list[0] == 'Zero lines we':
            raise JPLSpecQueryFailed(
                ("Zero lines were found by JPLSpec in a +/- 1 GHz "
                 "range from your provided transition frequency for "
                 "molecule tag {}.").format(mol_tag))

        t_freq = min(list(freq_list.quantity),
                     key=lambda x: abs(x - transition_freq))

        data = query[query['FREQ'] == t_freq.value]

        df = int(data['DR'].data)

        lgint = float(data['LGINT'].data)

        lgint = 10**lgint * u.nm * u.nm * u.MHz

        elo = float(data['ELO'].data) / u.cm

        gu = float(data['GUP'].data)

        cat = JPLSpec.get_species_table()

        mol = cat[cat['TAG'] == mol_tag]

        temp_list = cat.meta['Temperature (K)'] * u.K

        part = list(mol['QLOG1', 'QLOG2', 'QLOG3', 'QLOG4', 'QLOG5', 'QLOG6',
                        'QLOG7'][0])

        temp = temp_estimate

        f = interp(log(temp.value), log(temp_list.value[::-1]),
                   log(part[::-1]))

        f = exp(f)

        partition = 10**(f)

        part300 = 10**(float(mol['QLOG1'].data))

        # yields in 1/cm
        energy = elo + (t_freq.to(1 / u.cm, equivalencies=u.spectral()))

        energy_J = energy.to(u.J, equivalencies=u.spectral())
        elo_J = elo.to(u.J, equivalencies=u.spectral())

        quantities = [
            t_freq, temp, lgint, part300, partition, gu, energy_J, elo_J, df,
            mol_tag
        ]

        names = [
            't_freq', 'temp', 'lgint300', 'partfn300', 'partfn', 'dgup',
            'eup_J', 'elo_J', 'degfreedom', 'mol_tag'
        ]

        # names = ('Transition frequency',
        #          'Temperature',
        #          'Integrated line intensity at 300 K',
        #          'Partition function at 300 K',
        #          'Partition function at designated temperature',
        #          'Upper state degeneracy',
        #          'Upper level energy in Joules',
        #          'Lower level energy in Joules',
        #          'Degrees of freedom', 'Molecule Identifier')

        result = cls.from_dict(dict(zip(names, quantities)))

        return result
Exemplo n.º 2
0
def get_molecular_parameters(molecule_name,
                             tex=50,
                             fmin=1 * u.GHz,
                             fmax=1 * u.THz,
                             catalog='JPL',
                             **kwargs):
    """
    Get the molecular parameters for a molecule from the JPL or CDMS catalog

    (this version should, in principle, be entirely self-consistent)

    Parameters
    ----------
    molecule_name : string
        The string name of the molecule (normal name, like CH3OH or CH3CH2OH,
        but it has to match the JPL catalog spec)
    tex : float
        Optional excitation temperature (basically checks if the partition
        function calculator works)
    catalog : 'JPL' or 'CDMS'
        Which catalog to pull from
    fmin : quantity with frequency units
    fmax : quantity with frequency units
        The minimum and maximum frequency to search over

    Examples
    --------
    >>> from pyspeckit.spectrum.models.lte_molecule import get_molecular_parameters
    >>> freqs, aij, deg, EU, partfunc = get_molecular_parameters('CH2CHCN',
    ...                                                          fmin=220*u.GHz,
    ...                                                          fmax=222*u.GHz,
                                                                )
    >>> freqs, aij, deg, EU, partfunc = get_molecular_parameters('CH3OH',
    ...                                                          fmin=90*u.GHz,
    ...                                                          fmax=100*u.GHz)
    """
    if catalog == 'JPL':
        from astroquery.jplspec import JPLSpec as QueryTool
    elif catalog == 'CDMS':
        from astroquery.linelists.cdms import CDMS as QueryTool
    else:
        raise ValueError("Invalid catalog specification")

    speciestab = QueryTool.get_species_table()
    jpltable = speciestab[speciestab['NAME'] == molecule_name]
    if len(jpltable) != 1:
        raise ValueError(f"Too many or too few matches to {molecule_name}")

    jpltbl = QueryTool.query_lines(fmin,
                                   fmax,
                                   molecule=molecule_name,
                                   parse_name_locally=True)

    def partfunc(tem):
        """
        interpolate the partition function
        WARNING: this can be very wrong
        """
        tem = u.Quantity(tem, u.K).value
        tems = np.array(jpltable.meta['Temperature (K)'])
        keys = [k for k in jpltable.keys() if 'q' in k.lower()]
        logQs = jpltable[keys]
        logQs = np.array(list(logQs[0]))
        inds = np.argsort(tems)
        #logQ = np.interp(tem, tems[inds], logQs[inds])
        # linear interpolation is appropriate; Q is linear with T... for some cases...
        # it's a safer interpolation, anyway.
        # to get a better solution, you can fit a functional form as shown in the
        # JPLSpec docs, but that is... left as an exercise.
        # (we can test the degree of deviation there)
        linQ = np.interp(tem, tems[inds], 10**logQs[inds])
        return linQ

    freqs = jpltbl['FREQ'].quantity
    freq_MHz = freqs.to(u.MHz).value
    deg = np.array(jpltbl['GUP'])
    EL = jpltbl['ELO'].quantity.to(u.erg, u.spectral())
    dE = freqs.to(u.erg, u.spectral())
    EU = EL + dE

    # need elower, eupper in inverse centimeter units
    elower_icm = jpltbl['ELO'].quantity.to(u.cm**-1).value
    eupper_icm = elower_icm + (freqs.to(u.cm**-1, u.spectral()).value)

    # from Brett McGuire https://github.com/bmcguir2/simulate_lte/blob/1f3f7c666946bc88c8d83c53389556a4c75c2bbd/simulate_lte.py#L2580-L2587

    # LGINT: Base 10 logarithm of the integrated intensity in units of nm2 ·MHz at 300 K.
    # (See Section 3 for conversions to other units.)
    # see also https://cdms.astro.uni-koeln.de/classic/predictions/description.html#description
    CT = 300
    logint = np.array(jpltbl['LGINT'])  # this should just be a number
    #from CDMS website
    sijmu = (np.exp(np.float64(-(elower_icm / 0.695) / CT)) -
             np.exp(np.float64(-(eupper_icm / 0.695) / CT)))**(-1) * (
                 (10**logint) / freq_MHz) * (24025.120666) * partfunc(CT)

    #aij formula from CDMS.  Verfied it matched spalatalogue's values
    aij = 1.16395 * 10**(-20) * freq_MHz**3 * sijmu / deg

    # we want logA for consistency with use in generate_model below
    aij = np.log10(aij)
    EU = EU.to(u.erg).value

    ok = np.isfinite(aij) & np.isfinite(EU) & np.isfinite(deg) & np.isfinite(
        freqs)

    return freqs[ok], aij[ok], deg[ok], EU[ok], partfunc
Exemplo n.º 3
0
def molecular_data(temp_estimate, transition_freq, mol_tag):
    """
    Returns relevant constants from JPLSpec catalog and energy calculations

    Parameters
    ----------
    transition_freq : `~astropy.units.Quantity`
        Transition frequency in MHz

    temp_estimate : `~astropy.units.Quantity`
        Estimated temperature in Kelvins

    mol_tag : int or str
        Molecule identifier. Make sure it is an exclusive identifier.

    Returns
    -------
    Molecular data : list
        List of constants in the following order:
            | Transtion frequency
            | Temperature
            | Integrated line intensity at 300 K
            | Partition function at 300 K
            | Partition function at designated temperature
            | Upper state degeneracy
            | Upper level energy in Joules
            | Lower level energy in Joules
            | Degrees of freedom

    """

    query = JPLSpec.query_lines(min_frequency=(transition_freq - (1 * u.MHz)),
                                max_frequency=(transition_freq + (1 * u.MHz)),
                                molecule=mol_tag)

    freq_list = query['FREQ']

    t_freq = min(list(freq_list.quantity),
                 key=lambda x: abs(x-transition_freq))

    data = query[query['FREQ'] == t_freq.value]

    df = int(data['DR'].data)

    lgint = float(data['LGINT'].data)

    lgint = 10**lgint * u.nm * u.nm * u.MHz

    elo = float(data['ELO'].data) / u.cm

    gu = float(data['GUP'].data)

    cat = JPLSpec.get_species_table()

    mol = cat[cat['TAG'] == mol_tag]

    temp_list = cat.meta['Temperature (K)'] * u.K

    part = list(mol['QLOG1', 'QLOG2', 'QLOG3', 'QLOG4', 'QLOG5', 'QLOG6',
                    'QLOG7'][0])

    temp = temp_estimate

    f = interpolate.interp1d(temp_list, part, 'linear')

    partition = 10**(f(temp_estimate.value))

    part300 = 10 ** (float(mol['QLOG1'].data))

    # yields in 1/cm
    energy = elo + (t_freq.to(1/u.cm, equivalencies=u.spectral()))

    energy_J = energy.to(u.J, equivalencies=u.spectral())
    elo_J = elo.to(u.J, equivalencies=u.spectral())

    result = []

    result.append(t_freq)

    result.extend((temp, lgint, part300, partition, gu, energy_J,
                   elo_J, df))

    return result