Source code for uvex_transients.models.lfbots.lfbots

r"""Luminous Fast Blue Optical Transient (LFBOT) SED model."""

from typing import ClassVar

import numpy as np
from astropy import units as u

from uvex_transients.models._typing import CGSParameterValue, FloatArray
from uvex_transients.models._util_functions import cooling_temperature_cgs
from uvex_transients.models._utils import to_cgs_value
from uvex_transients.models.core.base import SpectralModel
from uvex_transients.models.core.parameters import Parameter
from uvex_transients.models.core.priors import LogNormalPrior, NormalPrior, UniformPrior
from uvex_transients.models.lightcurves.generic import GaussianRisePowerLawLightcurve
from uvex_transients.models.spectra.thermal import BlackbodySpectrum

__all__ = ["LFBOTCoolingBlackbodySED"]


[docs] class LFBOTCoolingBlackbodySED(SpectralModel): r""" A Gaussian-rise/power-law-decline light curve with a Villar-type cooling blackbody photosphere. This model pairs :class:`~uvex_transients.models.lightcurves.generic.GaussianRisePowerLawLightcurve` with a time-dependent blackbody spectral shape, .. math:: L_\nu(\nu, t) = L_\mathrm{bol}(t)\, S_\mathrm{BB}\!\left[\nu, T(t)\right], where :math:`S_\mathrm{BB}` is the normalized blackbody spectrum provided by :class:`~uvex_transients.models.spectra.thermal.BlackbodySpectrum`. The photosphere follows the same cooling-law form as :class:`~uvex_transients.models.supernovae.villar.VillarCoolingBlackbodySED`, but with the cooling timescale fixed to the light curve's own :math:`t_\mathrm{peak}` rather than a separate free parameter, .. math:: T(t) = T_\mathrm{floor} + (T_0 - T_\mathrm{floor})(1 + t/t_\mathrm{peak})^{-\alpha_T}, which has the correct :math:`T \to T_\mathrm{floor} + (T_0 - T_\mathrm{floor})(t/t_\mathrm{peak})^{-\alpha_T}` power-law asymptote at late times. .. rubric:: Parameters The model parameters are summarized below. .. list-table:: :header-rows: 1 :widths: 18 18 64 * - Parameter - Symbol - Description * - ``amplitude`` - :math:`A` - Peak bolometric luminosity. * - ``t_peak`` - :math:`t_\mathrm{peak}` - Time of peak luminosity since explosion. * - ``decline_index`` - :math:`\alpha_\mathrm{decline}` - Positive post-peak power-law decline index of the bolometric light curve. * - ``T0`` - :math:`T_0` - Photospheric temperature at t=0 (the T(t) -> T0 limit, not literally T at peak). * - ``T_floor`` - :math:`T_\mathrm{floor}` - Asymptotic late-time photospheric temperature (T(t) -> T_floor as t -> infinity). * - ``alpha_T`` - :math:`\alpha_T` - Late-time photospheric cooling power-law index. """ _DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = { "amplitude": Parameter( prior=NormalPrior(mean=44.5, sigma=0.5), scale=1.0 * u.erg / u.s, transform="log10", description="Peak bolometric luminosity. log10(L_0/[erg/s]) ~ N(44.5, 0.1^2), " "spanning the ~1e44 (CSS161010) to a few 1e45 erg/s range reported by Ho & Lu et al. 2026.", latex=r"L_0", ), "t_peak": Parameter( prior=NormalPrior(mean=np.log10(3), sigma=0.1), scale=1.0 * u.day, transform="log10", description="Time of peak bolometric luminosity since explosion. Log-normal around " "2 d with fairly small scatter (Ho & Lu et al. 2026).", latex=r"t_\mathrm{peak}", ), "decline_index": Parameter( prior=UniformPrior(lower=1.5, upper=4), scale=1 * u.dimensionless_unscaled, description="Positive post-peak power-law decline index; L_bol ~ t^-decline_index. " "Late-time light curves are broadly consistent with a t^-3 decline (Ho & Lu et al. 2026).", latex=r"\alpha_\mathrm{decline}", ), "T0": Parameter( prior=LogNormalPrior(mean=0.0, sigma=0.15), scale=4e4 * u.K, description="Photospheric temperature at t=0 (the T(t) -> T0 limit, not literally T at peak).", latex=r"T_0", ), "T_floor": Parameter( prior=LogNormalPrior(mean=0.0, sigma=0.01), scale=1e4 * u.K, description="Asymptotic late-time photospheric temperature (T(t) -> T_floor as t -> infinity). " "Log-normal, tightly centered on 1e4 K.", latex=r"T_\mathrm{floor}", ), "alpha_T": Parameter( prior=UniformPrior(lower=1 / 3, upper=1), scale=1, description="Late-time photospheric cooling power-law index; T ~ t^-alpha_T for t >> t_peak. " "~1/3 is a reasonable fit for most events (e.g. AT2018cow); CSS161010 was closer to " "constant temperature (Ho & Lu et al. 2026).", latex=r"\alpha_T", ), } # -------------------------------------- # # Cooling Law: T(t) # # -------------------------------------- # @classmethod def _temperature_cgs( cls, t: FloatArray, *, T0: CGSParameterValue, T_floor: CGSParameterValue, t_peak: CGSParameterValue, alpha_T: CGSParameterValue, **_ignored: CGSParameterValue, ) -> FloatArray: r""" :math:`T(t) = T_\mathrm{floor} + (T_0 - T_\mathrm{floor})(1 + t/t_\mathrm{peak})^{-\alpha_T}`. Parameters ---------- t : numpy.ndarray Time since explosion, in seconds. T0, T_floor, t_peak, alpha_T : float or numpy.ndarray This model's parameter values, in cgs units; see the class docstring. **_ignored Any other model parameter values, ignored. Returns ------- numpy.ndarray :math:`T(t)`, in Kelvin. """ return cooling_temperature_cgs(t, T0=T0, T_floor=T_floor, timescale=t_peak, alpha=alpha_T)
[docs] @classmethod def temperature(cls, t: FloatArray, **parameters: CGSParameterValue) -> FloatArray: r""" :math:`T(t)` in Kelvin. Parameters ---------- t : numpy.ndarray Time since explosion, in seconds. **parameters This model's parameter values, in cgs units. Returns ------- numpy.ndarray :math:`T(t)`, in Kelvin. """ cgs_parameters: dict[str, CGSParameterValue] = {name: to_cgs_value(value) for name, value in parameters.items()} return cls._temperature_cgs(t.cgs.value, **cgs_parameters) * u.K
# -------------------------------------- # # Bolometric Luminosity: L_bol(t) # # -------------------------------------- # @classmethod def _eval_bolometric(cls, t: FloatArray, **parameters: CGSParameterValue) -> FloatArray: r""" :math:`\log L_\mathrm{bol}(t)`, delegated directly to :class:`GaussianRisePowerLawLightcurve`. Exact -- no integration needed. Parameters ---------- t : numpy.ndarray Time since explosion, in seconds. **parameters This model's parameter values, in cgs units. Returns ------- numpy.ndarray The natural log of :math:`L_\mathrm{bol}(t)`, in erg/s. """ lightcurve_parameters = {name: parameters[name] for name in GaussianRisePowerLawLightcurve._DEFAULT_PARAMETERS} return GaussianRisePowerLawLightcurve._eval(t, **lightcurve_parameters) # -------------------------------------- # # Normalized Spectral Shape: S(nu, t) # # -------------------------------------- # @classmethod def _eval_spectrum(cls, nu: FloatArray, t: FloatArray, **parameters: CGSParameterValue) -> FloatArray: r""" :math:`\log S(\nu, T(t))`, delegated to :class:`BlackbodySpectrum`. Evaluated at this ``t``'s own cooling-law temperature. Parameters ---------- nu : numpy.ndarray Frequency, in Hz. t : numpy.ndarray Time since explosion, in seconds. **parameters This model's parameter values, in cgs units. Returns ------- numpy.ndarray The natural log of the normalized spectral shape, in 1/Hz. """ temperature = cls._temperature_cgs(t, **parameters) return BlackbodySpectrum._eval(nu, temperature=temperature) # -------------------------------------- # # Spectral Luminosity: L_nu(nu, t) # # -------------------------------------- # @classmethod def _eval(cls, nu: FloatArray, t: FloatArray, **parameters: CGSParameterValue) -> FloatArray: r""" :math:`\log L_\nu(\nu, t) = \log L_\mathrm{bol}(t) + \log S(\nu, T(t))`. Parameters ---------- nu : numpy.ndarray Frequency, in Hz. t : numpy.ndarray Time since explosion, in seconds. **parameters This model's parameter values, in cgs units. Returns ------- numpy.ndarray The natural log of :math:`L_\nu(\nu, t)`, in erg/s/Hz. """ return cls._eval_bolometric(t, **parameters) + cls._eval_spectrum(nu, t, **parameters)