Source code for pdrtpy.molecule

import warnings
from pathlib import Path
from typing import Union

import astropy.units as u
import numpy as np
from astropy.table import Table  # , QTable
from astropy.units.quantity import Quantity

from . import pdrutils as utils


[docs] class BaseMolecule(object): def __init__(self, name: str, path: Union[Path, str], opr: float = 1, opr_can_vary: bool = False, **kwargs) -> None: """ The base class for holding molecular transition data. Parameters ---------- name : str The descriptive name of the molecule, which will be used in displays. Can include LaTeX (without $ signs) e.g. 'H_2', 'CO', '^{13}CO'. path : Union[Path, str] Path to the table of molecular transition data. opr : float The canonical ortho-to-para ratio for molecules containing hydrogen. Default 1. opr_can_vary : bool Can the ortho-to-para ratio vary in this molecule under different physical conditions? Default:False **kwargs: dict Additional keywords to pass to Table.read """ self._constants_file = path self._canonical_opr = opr self._opr_can_vary = opr_can_vary self._name = name self._transition_data = utils.get_table(path, **kwargs) # @todo QTable self._transition_data.add_index("Line") self._transition_data.add_index("Ju")
[docs] def partition_function(self, temperature: Quantity) -> np.ndarray: r""" Calculate the partition function given an excitation temperature(s) The default calculation is :math:`Q(T) = \sum g_j~e^{-E_j/kT}` where :math:`g_j` is the statistical weight, :math:`E_j` is the transition energy, :math:`T` is the excitation temperature, and :math:`k` is Boltzmann's constant. Sub-classes should override this method for custom (and likely more accurate) partition function calculation. Parameters ---------- temperature : :class:`astropy.units.quantity.Quantity` The excitation temperature(s) Returns ------- :class:`~numpy.ndarray` The partition function evaluated at the given excitation temperatures """ gu = self._transition_data["gu"] eu = self._transition_data["Tu"] return np.array([np.sum(gu * np.exp(-eu / t)) for t in temperature])
@property def name(self): """ The descriptive name of the molecule Returns ------- str Descriptive name of the moelcule """ return self._name @property def canonical_opr(self) -> float: """ The canonical ortho-to-para ratio of the molecule. Only used for molecules containing hydrogen (where canonical OPR=3). Returns ------- float The ortho-to-para ratio """ return self._canonical_opr @property def transition_data(self) -> Table: """ The table fo transition data, containing columns such as transition energy, statistical weight, Einstein A coefficient, wavelength etc. Returns ------- `~astropy.table.Table` The table of transition data, """ return self._transition_data @property def opr_can_vary(self): """ Does the ortho-to-para ratio vary in this molecule under different physical conditions? Returns ------- bool True if OPR can vary, False otherwise. """ return self._opr_can_vary @property def line_ids(self) -> list: """ The identifiers (names) of the spectral line transitions in the data. These should be used to identify your Measurement data for excitation fits. Returns ------- list The spectral line IDs """ return list(self._transition_data["Line"]) @property def line_wavelengths(self) -> Quantity: """ The wavelengths of the spectral line transitions in the data. These should be used to identify your Measurement data for excitation fits. Returns ------- `~astropy.units.quantity.Quantity` The spectral line wavelengths """ return Quantity(self._transition_data["lambda"])
[docs] class H2(BaseMolecule): def __init__(self, name="H_2", path="RoueffEtAl.tab", opr=3.0, opr_can_vary=True): """Molecular hydrogen. This uses the `H_2` line calculations from Roueff et al 2019, A&A, 630, 58, Table 2.""" super().__init__(name=name, path=path, opr=opr, opr_can_vary=True, format="ascii.ipac")
[docs] def partition_function(self, temperature: Quantity) -> np.ndarray: r""" Calculate the :math:`H_2` partition function given an excitation temperature(s). This function uses the expression from Herbst et al 1996 http://articles.adsabs.harvard.edu/pdf/1996AJ....111.2403H :math:`Q(T) = 0.247 ~T / [1 - exp(6000 / T)]` where :math:`T` is the excitation temperature. Parameters ---------- temperature :class:`astropy.units.quantity.Quantity` The excitation temperature(s) at which to evaluate the partition function Returns ------- Q: :class:`~numpy.ndarray` The partition function evaluated at the given excitation temperature(s) """ # See Herbst et al 1996 # http://articles.adsabs.harvard.edu/pdf/1996AJ....111.2403H # Z(T) = = 0.0247T * [1 - \exp(-6000/T)]^-1 # This is just being defensive. I know the temperatures used internally are in K. t = np.ma.masked_invalid( (temperature.value * u.Unit(temperature.unit)).to("K", equivalencies=u.temperature()).value ) t.mask = np.logical_or(t.mask, np.logical_not(np.isfinite(t))) Q = 0.0247 * t / (1.0 - np.exp(-6000.0 / t)) return Q
[docs] class CO(BaseMolecule): # 12C16O def __init__(self, name="^{12}CO", path="12co_transition.tab.gz", opr=1.0, opr_can_vary=False): r"""Carbon Monoxide isotopologue :math:`^{12}C^{16}O`. This uses the molecular Lines and Levels data from the Meudon PDR7 code.""" super().__init__(name, path, opr, opr_can_vary, format="ascii.ecsv") self._partfun_data = utils.get_table("PartFun_12C16O.tab", format="ascii.ecsv") self._maxQtemp = np.max(self._partfun_data["T"])
[docs] def partition_function(self, temperature: Quantity) -> np.ndarray: """ Calculate the partition function for CO at the given temperature usin the HITRAN partition function. https://hitran.org/data/Q/q26.txt The HITRAN function is evaluated at 1K intervals; this function performas a linear interpolation on those data. Parameters ---------- temperature :class:`astropy.units.quantity.Quantity` The excitation temperature(s) at which to evaluate the partition function Returns ------- Q: :class:`~numpy.ndarray` The partition function evaluated at the given excitation temperature(s) """ t = np.ma.masked_invalid( (temperature.value * u.Unit(temperature.unit)).to("K", equivalencies=u.temperature()).value ) if np.nanmax(t) > self._maxQtemp: warnings.warn(f"Input temperature exceeds maximum partition function temperature: {self._maxQtemp} K") return np.interp(t, self._partfun_data["T"], self._partfun_data["Q"])
[docs] class C13O(BaseMolecule): # 13CO16O def __init__(self, name="^{13}CO", path="13co_transition.tab", opr=1.0, opr_can_vary=False): """Carbon Monoxide isotopologue :math:`^{13}C^{16}O`. This uses the molecular Lines and Levels data from the Meudon PDR7 code.""" super().__init__(name, path, opr, opr_can_vary, format="ascii.ecsv") self._partfun_data = utils.get_table("PartFun_13C16O.tab", format="ascii.ecsv") self._maxQtemp = np.max(self._partfun_data["T"]) # @todo refactor partition_function since we use same table format for all
[docs] def partition_function(self, temperature: Quantity) -> np.ndarray: """Calculate the partition function for 13CO at the given temperature usin the HITRAN partition function. https://hitran.org/data/Q/q27.txt The HITRAN function is evaluated at 1K intervals; this function performas a linear interpolation on those data. Parameters ---------- temperature :class:`astropy.units.quantity.Quantity` The excitation temperature(s) at which to evaluate the partition function Returns ------- Q: :class:`~numpy.ndarray` The partition function evaluated at the given excitation temperature(s) """ t = np.ma.masked_invalid( (temperature.value * u.Unit(temperature.unit)).to("K", equivalencies=u.temperature()).value ) if np.nanmax(t) > self._maxQtemp: warnings.warn(f"Input temperature exceeds maximum partition function temperature: {self._maxQtemp} K") return np.interp(t, self._partfun_data["T"], self._partfun_data["Q"])
# class CHplus(BaseMolecule): # CH+ # def __init__(self, name="CH^+", path="CHp_transition.tab", opr=3.0, opr_can_vary=True): # super().__init__(name, path, opr, opr_can_vary) # self._partfun_data = utils.get_table("PartFun_CH+.tab", format="ascii.ecsv") # self._maxQtemp = np.max(self._partfun_data["T"])