Source code for mufasa.spec_models.HyperfineModel

"""
Defines the HyperfineModel for spectral modeling with hyperfine structure.
"""
import numpy as np
from .BaseModel import BaseModel

[docs] class HyperfineModel(BaseModel): """ Spectral model for multi-component fitting with hyperfine structure. Inherits from `BaseModel` and applies hyperfine-specific molecular constants. """ def __init__(self, line_names=None): """ Initialize HyperfineModel. """ super().__init__(line_names=line_names)
[docs] def multi_v_spectrum(self, xarr, *args): """ Generate a multi-component spectrum with hyperfine structure. Parameters ---------- xarr : array-like Frequency array in GHz. args : list Model parameters (velocity, width, excitation temperature, optical depth) for each component, provided in sequence. Returns ------- spectrum : array-like Computed spectrum with hyperfine components. """ cls = self.__class__ if xarr.unit.to_string() != 'GHz': xarr = xarr.as_unit('GHz') background_ta = cls.T_antenna(cls._TCMB, xarr.value) tau_dict = {} for vel, width, tex, tau in zip(args[::4], args[1::4], args[2::4], args[3::4]): for linename in self.line_names: tau_dict[linename] = tau model_spectrum = self._single_spectrum_hf( xarr, tex, tau_dict, width, vel, background_ta=background_ta ) # Update background for the next component background_ta = model_spectrum return model_spectrum - cls.T_antenna(cls._TCMB, xarr.value)
def _single_spectrum_hf(self, xarr, tex, tau_dict, width, xoff_v, background_ta=0.0): """ Compute a single-component spectrum with hyperfine structure. Parameters ---------- xarr : array-like Frequency array in GHz. tex : float Excitation temperature (K). tau_dict : dict Optical depth for each hyperfine component. width : float Line width (km/s). xoff_v : float Velocity offset (km/s). background_ta : float or array-like, optional Background antenna temperature (default: 0.0). Returns ------- spectrum : array-like Computed spectrum with hyperfine components. """ cls = self.__class__ molecular_constants = cls._molecular_constants freq_dict = molecular_constants['freq_dict'] voff_lines_dict = molecular_constants['voff_lines_dict'] tau_wts_dict = molecular_constants['tau_wts_dict'] runspec = np.zeros(len(xarr)) for linename in self.line_names: voff_lines = np.array(voff_lines_dict[linename]) tau_wts = np.array(tau_wts_dict[linename]) lines = (1 - voff_lines / cls._ckms) * freq_dict[linename] / 1e9 tau_wts /= tau_wts.sum() nuwidth = np.abs(width / cls._ckms * lines) nuoff = xoff_v / cls._ckms * lines tauprof = np.zeros(len(xarr)) for kk, nuo in enumerate(nuoff): tauprof += (tau_dict[linename] * tau_wts[kk] * np.exp(-(xarr.value + nuo - lines[kk]) ** 2 / (2.0 * nuwidth[kk] ** 2))) T0 = (cls._h * xarr.value * 1e9 / cls._kb) runspec += ((T0 / (np.exp(T0 / tex) - 1) * (1 - np.exp(-tauprof)) + background_ta * np.exp(-tauprof))) return runspec
[docs] def deblend(self, xarr, tex, tau, xoff_v, width): """ Compute a spectrum without hyperfine structures. Parameters ---------- xarr : array-like Frequency array in GHz. tex : float Excitation temperature (K). tau : float Optical depth. xoff_v : float Velocity offset (km/s). width : float Line width (km/s). Returns ------- spectrum : array-like Computed spectrum without hyperfine structure. """ cls = self.__class__ tau_dict = {} for linename in self.line_names: print(f"linename: {linename}") tau_dict[linename] = tau background_ta = cls.T_antenna(cls._TCMB, xarr.value) spec = self._single_spectrum(xarr, tex, tau_dict, width, xoff_v, background_ta=background_ta) return spec - cls.T_antenna(cls._TCMB, xarr.value)