Source code for elisa.models.mul

"""Multiplicative models."""

from __future__ import annotations

import warnings
from abc import abstractmethod
from pathlib import Path
from typing import TYPE_CHECKING

import h5py
import jax
import jax.numpy as jnp
import numpy as np

from elisa.models.model import (
    AnaIntMultiplicative,
    NumIntMultiplicative,
    ParamConfig,
)

if TYPE_CHECKING:
    from elisa.util.typing import CompEval, JAXArray, NameValMapping

__all__ = [
    'Constant',
    'Edge',
    'ExpAbs',
    'ExpFac',
    'GAbs',
    'HighECut',
    'PLAbs',
    'PhAbs',
    'TBAbs',
    'WAbs',
]


[docs] class Constant(AnaIntMultiplicative): r"""Energy-independent multiplicative factor. .. math:: M(E) = f. Parameters ---------- f : Parameter, optional The multiplicative factor :math:`f`, dimensionless. latex : str, optional :math:`\LaTeX` format of the component. Defaults to class name. """ _config = (ParamConfig('f', 'f', '', 1.0, 1e-5, 1e5),)
[docs] @staticmethod def integral(egrid: JAXArray, params: NameValMapping) -> JAXArray: return jnp.full(egrid.size - 1, params['f'])
[docs] class Edge(NumIntMultiplicative): r"""Absorption edge. .. math:: M(E) = \begin{cases} \exp\left[-D \bigl(\frac{E}{E_\mathrm{c}}\bigr)^3\right], &\text{if } E \ge E_\mathrm{c}, \\\\ 1, &\text{otherwise.} \end{cases} Parameters ---------- Ec : Parameter, optional The threshold energy :math:`E_\mathrm{c}`, in units of keV. D : Parameter, optional The absorption depth :math:`D` at the threshold energy, dimensionless. latex : str, optional :math:`\LaTeX` format of the component. Defaults to class name. method : {'trapz', 'simpson'}, optional Numerical integration method. Defaults to 'trapz'. """ _config = ( ParamConfig('Ec', r'E_\mathrm{c}', 'keV', 7.0, 0.0, 100.0), ParamConfig('D', 'D', '', 1.0, 0.0, 10), )
[docs] @staticmethod def continuum(egrid: JAXArray, params: NameValMapping) -> JAXArray: Ec = params['Ec'] D = params['D'] return jnp.where( egrid >= Ec, jnp.exp(-D * jnp.power(egrid / Ec, 3.0)), 1.0 )
[docs] class ExpAbs(NumIntMultiplicative): r"""Low-energy exponential rolloff. .. math:: M(E) = \exp\left(-\frac{E_\mathrm{c}}{E}\right). Parameters ---------- Ec : Parameter, optional The e-folding energy :math:`E_\mathrm{c}` for the absorption, in units of keV. latex : str, optional :math:`\LaTeX` format of the component. Defaults to class name. method : {'trapz', 'simpson'}, optional Numerical integration method. Defaults to 'trapz'. """ _config = (ParamConfig('Ec', r'E_\mathrm{c}', 'keV', 2.0, 0.0, 200.0),)
[docs] @staticmethod def continuum(egrid: JAXArray, params: NameValMapping) -> JAXArray: return jnp.exp(-params['Ec'] / egrid)
[docs] class ExpFac(NumIntMultiplicative): r"""Exponential modification. .. math:: M(E) = \begin{cases} 1 + A \exp\bigl(-\frac{f E}{E_0}\bigr), &\text{if } E \ge E_\mathrm{c}, \\\\ 1, &\text{otherwise,} \end{cases} where :math:`E_0` is the pivot energy fixed at 1 keV. Parameters ---------- A : Parameter, optional The amplitude of effect :math:`A`, dimensionless. f : Parameter, optional The exponential factor :math:`f`, dimensionless. Ec : Parameter, optional The start energy of modification :math:`E_\mathrm{c}`, in units of keV. latex : str, optional :math:`\LaTeX` format of the component. Defaults to class name. method : {'trapz', 'simpson'}, optional Numerical integration method. Defaults to 'trapz'. """ _config = ( ParamConfig('A', 'A', '', 1.0, 0.0, 1e6), ParamConfig('f', 'f', '', 1.0, 0.0, 1e6), ParamConfig('Ec', r'E_\mathrm{c}', 'keV', 0.5, 0.0, 1e6), )
[docs] @staticmethod def continuum(egrid: JAXArray, params: NameValMapping) -> JAXArray: A = params['A'] f = params['f'] Ec = params['Ec'] return jnp.where(egrid >= Ec, 1.0 + A * jnp.exp(-f * egrid), 1.0)
[docs] class GAbs(NumIntMultiplicative): r"""Gaussian absorption line. .. math:: M(E) = \exp\left[ -\frac{\tau}{\sqrt{2\pi} \sigma} \exp\left[ -\frac{\left(E - E_\mathrm{l}\right)^2}{2 \sigma^2} \right] \right]. The optical depth at line center is :math:`\frac{\tau}{\sqrt{2\pi}\sigma}`. Parameters ---------- El : Parameter, optional The line energy :math:`E_\mathrm{l}`, in units of keV. sigma : Parameter, optional The line width :math:`\sigma`, in units of keV. tau : Parameter, optional The line depth :math:`\tau`, in units of keV. latex : str, optional :math:`\LaTeX` format of the component. Defaults to class name. method : {'trapz', 'simpson'}, optional Numerical integration method. Defaults to 'trapz'. """ _config = ( ParamConfig('El', r'E_\mathrm{l}', 'keV', 1, 0.0, 1e6), ParamConfig('sigma', r'\sigma', 'keV', 0.01, 0.0, 20), ParamConfig('tau', r'\tau', '', 1.0, 0.0, 1e6), )
[docs] @staticmethod def continuum(egrid: JAXArray, params: NameValMapping) -> JAXArray: El = params['El'] sigma = params['sigma'] tau = params['tau'] return jnp.exp( -tau / (jnp.sqrt(2 * jnp.pi) * sigma) * jnp.exp(-0.5 * jnp.power((egrid - El) / sigma, 2)) )
[docs] class HighECut(NumIntMultiplicative): r"""High-energy cutoff. .. math:: M(E) = \begin{cases} \exp\bigl(\frac{E_\mathrm{c}-E}{E_\mathrm{f}}\bigr), &\text{if } E \ge E_\mathrm{c}, \\\\ 1, &\text{otherwise.} \end{cases} Parameters ---------- Ec : Parameter, optional The cutoff energy :math:`E_\mathrm{c}`, in units of keV. Ef : Parameter, optional The e-folding energy :math:`E_\mathrm{f}`, in units of keV. latex : str, optional :math:`\LaTeX` format of the component. Defaults to class name. method : {'trapz', 'simpson'}, optional Numerical integration method. Defaults to 'trapz'. """ _config = ( ParamConfig('Ec', r'E_\mathrm{c}', 'keV', 10.0, 1e-4, 1e6), ParamConfig('Ef', r'E_\mathrm{f}', 'keV', 15.0, 1e-4, 1e6), )
[docs] @staticmethod def continuum(egrid: JAXArray, params: NameValMapping) -> JAXArray: Ec = params['Ec'] Ef = params['Ef'] return jnp.where(egrid >= Ec, jnp.exp((Ec - egrid) / Ef), 1.0)
[docs] class PLAbs(AnaIntMultiplicative): r"""Absorption as a power-law in energy. Useful for things like dust. .. math:: M(E) = K \left(\frac{E}{E_0}\right)^{-\alpha}, where :math:`E_0` is the pivot energy fixed at 1 keV. Parameters ---------- alpha : Parameter, optional The power law index :math:`\alpha`, dimensionless. K : Parameter, optional The coefficient :math:`K`, dimensionless. latex : str, optional :math:`\LaTeX` format of the component. Defaults to class name. """ _config = ( ParamConfig('alpha', r'\alpha', '', 2.0, 0.0, 5.0), ParamConfig('K', 'K', '', 1.0, 0.0, 100.0), )
[docs] @staticmethod def integral(egrid: JAXArray, params: NameValMapping) -> JAXArray: # ignore the case of alpha = 1.0 one_minus_alpha = 1.0 - params['alpha'] f = params['K'] / one_minus_alpha * jnp.power(egrid, one_minus_alpha) return (f[1:] - f[:-1]) / (egrid[1:] - egrid[:-1])
def _make_interp(egrid, xsect): xp = np.log(egrid) fp = np.log(xsect) return jax.jit( lambda e: jnp.exp( jnp.interp( jnp.log(e), xp, fp, left='extrapolate', right='extrapolate' ) ) ) with h5py.File(Path(__file__).parent / 'tables' / 'xsect.hdf5') as f: _XSECT_INTERP = { mabs: { xsect: { abund: _make_interp( f['energy'][:], f[f'{mabs}/{xsect}/{abund}'][:] ) for abund in f[f'{mabs}/{xsect}'].keys() } for xsect in f[mabs].keys() } for mabs in [k for k in f.keys() if k != 'energy'] } del f class PhotonAbsorption(NumIntMultiplicative): r"""Photon absorption model. .. note:: The photon cross-sections are obtained by interpolating cross-sections table, which is the same as threeml does, except that if input energy is outside the table energy range, extrapolated value is used. """ _kwargs = ('abund', 'xsect', 'method') _default_abund: str _default_xsect: str def __init__( self, params: dict, latex: str | None, method: str | None, abund: str | None, xsect: str | None, ): if self.__class__.__name__ == 'WAbs': warnings.warn( 'The WAbs model is obsolete and is only included for ' 'comparison with historical results. The TBAbs model ' 'should be used for the ISM or PhAbs for general ' 'photoelectric absorption.', DeprecationWarning, stacklevel=4, ) if abund is None: abund = self._default_abund self.abund = abund if xsect is None: xsect = self._default_xsect self.xsect = xsect super().__init__(params, latex, method) @property def eval(self) -> CompEval: """Get photon absorption model function.""" if self._continuum_jit is None: self._continuum_jit = jax.jit( self.continuum, static_argnums=(2, 3, 4) ) abs_model = self.__class__.__name__.lower() abund = self.abund xsect = self.xsect continuum = jax.jit( lambda egrid, params: self._continuum_jit( egrid, params, abs_model, abund, xsect ) ) return self._make_integral(continuum) @staticmethod def continuum( egrid: JAXArray, params: NameValMapping, abs_model: str, abund: str, xsect: str, ) -> JAXArray: """Photon absorption model. Parameters ---------- egrid : ndarray Photon energy grid in units of keV. params : dict Parameter dict for the convolution model. abs_model : str Photon absorption model name. abund : str Abundance table name. xsect : str Photon cross-sections. Returns ------- jax.Array The model value at `egrid`, dimensionless. """ sigma = _XSECT_INTERP[abs_model][xsect][abund](egrid) return jnp.exp(-params['nH'] * sigma) @property def abund(self) -> str: """Current abundance table.""" return self._abund @abund.setter def abund(self, abund: str): abund = str(abund) if abund not in self.abund_list(): available = ', '.join(self.abund_list()) raise ValueError(f'available abundance: {available}') self._abund = abund @staticmethod @abstractmethod def abund_list() -> list[str]: """Get available abundance list.""" pass @property def xsect(self) -> str: """Current photon cross-section.""" return self._xsect @xsect.setter def xsect(self, xsect: str | None): xsect = str(xsect) if xsect not in self.xsect_list(): available = ', '.join(self.xsect_list()) raise ValueError(f'available photon cross-section: {available}') self._xsect = xsect @staticmethod @abstractmethod def xsect_list() -> list[str]: """Get available photon cross-section list.""" pass
[docs] class PhAbs(PhotonAbsorption): r"""Photoelectric absorption. .. math :: M(E) = \exp \left[ -\eta_\mathrm{H}\ \sigma(E) \right], where :math:`\sigma(E)` is the photo-electric cross-section, **NOT** including Thomson scattering. Parameters ---------- nH : Parameter, optional The equivalent hydrogen column density :math:`\eta_\mathrm{H}`, in units of 10²² cm⁻². latex : str, optional :math:`\LaTeX` format of the component. Defaults to class name. abund : str, optional Abundance table to use. Available options are: * ``'angr'`` [1]_ (Photospheric, using Table 2) * ``'aspl'`` [2]_ (Photospheric, using Table 1) * ``'feld'`` [3]_ * ``'aneb'`` [4]_ * ``'grsa'`` [5]_ * ``'wilm'`` [6]_ * ``'lodd'`` [7]_ (Photospheric, using Table 1) * ``'lpgp'`` [8]_ (Photospheric, using Table 4) * ``'lpgs'`` [8]_ (Proto-solar, using Table 10) The default is ``'angr'``. xsect : str, optional Photon cross-section to use. Available options are: * ``'bcmc'`` [9]_ with a new He cross-section [10]_ * ``'obcm'`` [9]_ * ``'vern'`` [11]_ The default is ``'vern'``. method : {'trapz', 'simpson'}, optional Numerical integration method. Defaults to 'trapz'. References ---------- .. [1] `Anders & Grevesse 1989, Geochimica et Cosmochimica Acta, 53, 1, 197-214 <https://doi.org/10.1016/0016-7037(89)90286-X>`__ .. [2] `Asplund et al. 2009 ARAA, 47, 481 <https://doi.org/10.1146/annurev.astro.46.060407.145222>`__ .. [3] `Feldman 1992, Phys. Scr. 46, 202 <https://doi.org/10.1088/0031-8949/46/3/002>`__ .. [4] `Anders & Ebihara 1982, Geochimica et Cosmochimica Acta, 46, 11, 2363-2380 <https://doi.org/10.1016/0016-7037(82)90208-3>`__ .. [5] `Grevesse & Sauval 1998, Space Science Reviews, 85, 161–174 <https://doi.org/10.1023/A:1005161325181>`__ .. [6] `Wilms et al 2000, ApJ, 542, 914 <https://doi.org/10.1086/317016>`__ .. [7] `Lodders 2003, ApJ, 591, 1220 <https://doi.org/10.1086/375492>`__ .. [8] `Lodders et al. 2009 <https://doi.org/10.1007/978-3-540-88055-4_34>`__ .. [9] `Balucinska-Church & McCammon 1992, ApJ, 400, 699 <https://doi.org/10.1086/172032>`__ .. [10] `Yan et al. 1998, ApJ, 496, 1044 <https://doi.org/10.1086/305420>`__ .. [11] `Verner et al. 1996, ApJ, 465, 487 <https://doi.org/10.1086/177435>`__ """ _default_abund: str = 'angr' _default_xsect: str = 'vern' _config = ( ParamConfig('nH', r'\eta_\mathrm{H}', '10^22 cm^-2', 1.0, 0.0, 1e6), )
[docs] @staticmethod def abund_list() -> list[str]: """Get available abundance list.""" return [ 'angr', 'aspl', 'feld', 'aneb', 'grsa', 'wilm', 'lodd', 'lpgp', 'lpgs', ]
[docs] @staticmethod def xsect_list() -> list[str]: """Get available photon cross-section list.""" return ['bcmc', 'obcm', 'vern']
[docs] class TBAbs(PhotonAbsorption): r"""The Tuebingen-Boulder ISM absorption model. This model calculates the cross-sections for X-ray absorption by the ISM as the sum of the cross-sections for X-ray absorption due to the gas-phase ISM, the grain-phase ISM, and the molecules in the ISM. Parameters ---------- nH : Parameter, optional The equivalent hydrogen column density :math:`\eta_\mathrm{H}`, in units of 10²² cm⁻². latex : str, optional :math:`\LaTeX` format of the component. Defaults to class name. abund : str, optional Abundance table to use. Available options are: * ``'angr'`` [1]_ (Photospheric, using Table 2) * ``'aspl'`` [2]_ (Photospheric, using Table 1) * ``'feld'`` [3]_ * ``'aneb'`` [4]_ * ``'grsa'`` [5]_ * ``'wilm'`` [6]_ * ``'lodd'`` [7]_ (Photospheric, using Table 1) * ``'lpgp'`` [8]_ (Photospheric, using Table 4) * ``'lpgs'`` [8]_ (Proto-solar, using Table 10) The default is ``'wilm'``. xsect : str, optional Always use cross-section ``'vern'`` [9]_ as baseline. method : {'trapz', 'simpson'}, optional Numerical integration method. Defaults to 'trapz'. References ---------- .. [1] `Anders & Grevesse 1989, Geochimica et Cosmochimica Acta, 53, 1, 197-214 <https://doi.org/10.1016/0016-7037(89)90286-X>`__ .. [2] `Asplund et al. 2009 ARAA, 47, 481 <https://doi.org/10.1146/annurev.astro.46.060407.145222>`__ .. [3] `Feldman 1992, Phys. Scr. 46, 202 <https://doi.org/10.1088/0031-8949/46/3/002>`__ .. [4] `Anders & Ebihara 1982, Geochimica et Cosmochimica Acta, 46, 11, 2363-2380 <https://doi.org/10.1016/0016-7037(82)90208-3>`__ .. [5] `Grevesse & Sauval 1998, Space Science Reviews, 85, 161–174 <https://doi.org/10.1023/A:1005161325181>`__ .. [6] `Wilms et al 2000, ApJ, 542, 914 <https://doi.org/10.1086/317016>`__ .. [7] `Lodders 2003, ApJ, 591, 1220 <https://doi.org/10.1086/375492>`__ .. [8] `Lodders et al. 2009 <https://doi.org/10.1007/978-3-540-88055-4_34>`__ .. [9] `Verner et al. 1996, ApJ, 465, 487 <https://doi.org/10.1086/177435>`__ """ _default_abund: str = 'wilm' _default_xsect: str = 'vern' _config = ( ParamConfig('nH', r'\eta_\mathrm{H}', '10^22 cm^-2', 1.0, 0.0, 1e6), )
[docs] @staticmethod def abund_list() -> list[str]: """Get available abundance list.""" return [ 'angr', 'aspl', 'feld', 'aneb', 'grsa', 'wilm', 'lodd', 'lpgp', 'lpgs', ]
[docs] @staticmethod def xsect_list() -> list[str]: return ['vern']
[docs] class WAbs(PhotonAbsorption): r"""A photo-electric absorption using Wisconsin cross-sections [1]_. .. math :: M(E) = \exp \left[ -\eta_\mathrm{H}\ \sigma(E) \right], where :math:`\sigma(E)` is the photo-electric cross-section, **NOT** including Thomson scattering. .. warning :: The :class:`WAbs` model is obsolete and is only included for comparison with historical results. The :class:`TBAbs` model should be used for the ISM or :class:`PhAbs` for general photoelectric absorption. Parameters ---------- nH : Parameter, optional The equivalent hydrogen column density :math:`\eta_\mathrm{H}`, in units of 10²² cm⁻². latex : str, optional :math:`\LaTeX` format of the component. Defaults to class name. abund : str, optional Always use abundance table ``'aneb'`` [2]_. xsect : str, optional Always use Wisconsin cross-sections [1]_. method : {'trapz', 'simpson'}, optional Numerical integration method. Defaults to 'trapz'. References ---------- .. [1] `Morrison & McCammon 1983, ApJ, 270, 119 <https://doi.org/10.1086/161102>`__ .. [2] `Anders & Ebihara 1982, Geochimica et Cosmochimica Acta, 46, 11, 2363-2380 <https://doi.org/10.1016/0016-7037(82)90208-3>`__ """ _default_abund: str = 'aneb' _default_xsect: str = 'wabs' _config = ( ParamConfig('nH', r'\eta_\mathrm{H}', '10^22 cm^-2', 1.0, 0.0, 1e6), )
[docs] @staticmethod def abund_list() -> list[str]: return ['aneb']
[docs] @staticmethod def xsect_list() -> list[str]: return ['wabs']