Source code for uvex_transients.models.spectra.thermal
r"""
Blackbody spectral shape.
A :class:`BlackbodySpectrum` is shaped entirely by a single sampled
``temperature`` -- unlike a power law, which is scale-free and needs an
arbitrary reference frequency to anchor it, a blackbody's shape has no free
normalization choice: the Planck function, normalized by the
Stefan-Boltzmann law, already integrates to exactly 1 over all frequencies
for any temperature.
"""
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.core.base import Spectrum
from uvex_transients.models.core.parameters import Parameter
from uvex_transients.models.core.priors import LogNormalPrior
from .._util_functions import planck_shape_log_cgs
__all__ = ["BlackbodySpectrum"]
[docs]
class BlackbodySpectrum(Spectrum):
r"""
Blackbody spectral shape, shaped entirely by a sampled ``temperature``.
Represents :math:`S(\nu, T) = \pi B_\nu(\nu, T) / (\sigma T^4)`, the
Lambertian-emergent Planck function normalized by the Stefan-Boltzmann
constant so that :math:`\int_0^\infty S(\nu, T)\,d\nu = 1` for any
``temperature`` -- see the module docstring for why this needs no
reference-frequency pivot the way a power law does.
By default ``temperature`` uses a
:class:`~uvex_transients.models.core.priors.LogNormalPrior`, a physically motivated
prior for a strictly positive scale parameter; like any other
:class:`~uvex_transients.models.core.parameters.Parameter`, it can be overridden
per instance.
.. rubric:: Parameters
The spectral shape parameters are summarized below.
.. list-table::
:header-rows: 1
:widths: 18 18 64
* - Parameter
- Symbol
- Description
* - ``temperature``
- :math:`T`
- Blackbody temperature.
"""
_DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = {
"temperature": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e4 * u.K,
description="Blackbody temperature.",
latex=r"T",
),
}
# ----------------------------------- #
# Shape: S(nu) #
# ----------------------------------- #
# Narrowing `**parameters` to this model's own named parameters (rather
# than matching the base class's generic `**parameters` exactly) is the
# intended pattern for every `_eval` override -- see `Spectrum._eval`.
@classmethod
def _eval( # type: ignore[override]
cls, nu: FloatArray, *, temperature: CGSParameterValue
) -> FloatArray:
return planck_shape_log_cgs(nu, temperature)
# ----------------------------------- #
# Normalization: integral of S(nu) dnu #
# ----------------------------------- #
@classmethod
def _eval_normalization(cls, **parameters: CGSParameterValue) -> FloatArray:
r"""
Natural log of the shape integral.
Exactly 0 (i.e. the integral itself is 1) for any ``temperature``, by
construction -- see the class docstring.
"""
return np.zeros_like(np.asarray(parameters["temperature"], dtype=np.float64))