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
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
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