"""Generic, reusable `~uvex_transients.models.core.Lightcurve` shapes."""
from typing import ClassVar
import numpy as np
from astropy import units as u
from numpy.typing import NDArray
from uvex_transients.models.core import Lightcurve, LogNormalPrior, NormalPrior, Parameter
from .._typing import CGSParameterValue
from .._utils import _BOL_LUM_UNIT
__all__ = [
"BazinLightcurve",
"BrokenPowerLawLightcurve",
"DelayedExponentialLightcurve",
"FREDLightcurve",
"GREDLightcurve",
"GaussianPulseLightcurve",
"GaussianRiseBrokenPowerLawLightcurve",
"GaussianRisePowerLawLightcurve",
"LogNormalPulseLightcurve",
"PlateauPowerLawLightcurve",
"PowerLawLightcurve",
"SmoothBrokenPowerLawLightcurve",
"TopHatLightcurve",
"TwoComponentBazinLightcurve",
"VillarLightcurve",
]
[docs]
class TopHatLightcurve(Lightcurve):
r"""
A constant luminosity for a fixed duration, zero before and after.
.. math::
L(t) = \begin{cases} A & 0 \le t \le \Delta t \\ 0 & t > \Delta t \end{cases}
The simplest possible pulse shape: a plateau at the peak amplitude :math:`A` for a
duration :math:`\Delta t`, with an instantaneous rise and cutoff.
.. rubric:: Parameters
The light curve parameters are summarized below.
.. list-table::
:header-rows: 1
:widths: 18 18 64
* - Parameter
- Symbol
- Description
* - ``amplitude``
- :math:`A`
- Plateau bolometric luminosity.
* - ``duration``
- :math:`\Delta t`
- Duration of the plateau.
"""
_LIGHTCURVE_TYPE = "bolometric"
_DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = {
"amplitude": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e43 * _BOL_LUM_UNIT,
description="Plateau bolometric luminosity.",
latex=r"A",
),
"duration": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1.0 * u.day,
description="Duration of the plateau.",
latex=r"\Delta t",
),
}
@classmethod
def _eval(cls, t: NDArray[np.float64], **parameters: CGSParameterValue) -> NDArray[np.float64]:
r"""
Evaluate the natural log of :math:`L_\mathrm{bol}(t)`; see the class docstring for the functional form.
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.
"""
amplitude, duration = parameters["amplitude"], parameters["duration"]
with np.errstate(divide="ignore"):
return np.where(t <= duration, np.log(amplitude), -np.inf)
[docs]
class GaussianPulseLightcurve(Lightcurve):
r"""
A Gaussian pulse in luminosity, peaked at ``t_peak``.
.. math::
L(t) = A \exp\left(-\frac{(t - t_\mathrm{peak})^2}{2\sigma^2}\right)
:math:`L(t_\mathrm{peak}) = A` exactly, by construction.
.. rubric:: Parameters
The light curve 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.
* - ``sigma``
- :math:`\sigma`
- Gaussian width of the pulse.
"""
_LIGHTCURVE_TYPE = "bolometric"
_DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = {
"amplitude": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e43 * _BOL_LUM_UNIT,
description="Peak bolometric luminosity.",
latex=r"A",
),
"t_peak": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1.0 * u.day,
description="Time of peak luminosity since explosion.",
latex=r"t_\mathrm{peak}",
),
"sigma": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1.0 * u.day,
description="Gaussian width of the pulse.",
latex=r"\sigma",
),
}
@classmethod
def _eval(cls, t: NDArray[np.float64], **parameters: CGSParameterValue) -> NDArray[np.float64]:
r"""
Evaluate the natural log of :math:`L_\mathrm{bol}(t)`; see the class docstring for the functional form.
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.
"""
amplitude, t_peak, sigma = (
parameters["amplitude"],
parameters["t_peak"],
parameters["sigma"],
)
return np.log(amplitude) - 0.5 * ((t - t_peak) / sigma) ** 2
[docs]
class FREDLightcurve(Lightcurve):
r"""
A fast-rise, exponential-decay pulse (the Norris et al. 1996 GRB pulse shape).
.. math::
L(t) = A \exp\left(2\sqrt{\tau_1/\tau_2} - \frac{\tau_1}{t} - \frac{t}{\tau_2}\right), \quad t > 0
with :math:`L(0) = 0`. Despite the apparent singularity at :math:`\tau_1/t`, this is
well-behaved: as :math:`t \to 0^+`, the :math:`-\tau_1/t` term dominates and drives
:math:`L \to 0`. The pulse peaks exactly at :math:`t_\mathrm{peak} = \sqrt{\tau_1 \tau_2}`,
where :math:`L(t_\mathrm{peak}) = A`; :math:`\tau_1 \ll \tau_2` gives the characteristic
fast rise / slow, exponential-looking decay.
.. rubric:: Parameters
The light curve parameters are summarized below.
.. list-table::
:header-rows: 1
:widths: 18 18 64
* - Parameter
- Symbol
- Description
* - ``amplitude``
- :math:`A`
- Peak bolometric luminosity.
* - ``rise``
- :math:`\tau_1`
- Rise timescale tau_1.
* - ``decay``
- :math:`\tau_2`
- Decay timescale tau_2.
"""
_LIGHTCURVE_TYPE = "bolometric"
_DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = {
"amplitude": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e43 * _BOL_LUM_UNIT,
description="Peak bolometric luminosity.",
latex=r"A",
),
"rise": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=0.5 * u.day,
description="Rise timescale tau_1.",
latex=r"\tau_1",
),
"decay": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=3.0 * u.day,
description="Decay timescale tau_2.",
latex=r"\tau_2",
),
}
@classmethod
def _eval(cls, t: NDArray[np.float64], **parameters: CGSParameterValue) -> NDArray[np.float64]:
r"""
Evaluate the natural log of :math:`L_\mathrm{bol}(t)`; see the class docstring for the functional form.
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.
"""
amplitude, rise, decay = (
parameters["amplitude"],
parameters["rise"],
parameters["decay"],
)
# `t` includes `t = 0` by convention (see `Lightcurve`'s class docstring), which
# divides by zero here. That's the correct limit, not an error -- `rise / t -> inf`
# drives the exponent to `-inf` and `L -> 0` -- so the warning is suppressed rather
# than worked around.
with np.errstate(divide="ignore"):
return np.log(amplitude) + 2 * np.sqrt(rise / decay) - rise / t - t / decay
[docs]
class GREDLightcurve(Lightcurve):
r"""
A Gaussian rise followed by an exponential decline, peaked at ``t_peak``.
.. math::
L(t) = A \times \begin{cases}
\exp\left(-\dfrac{(t - t_\mathrm{peak})^2}{2\sigma_\mathrm{rise}^2}\right)
& t \le t_\mathrm{peak} \\[4pt]
\exp\left(-\dfrac{t - t_\mathrm{peak}}{\tau_\mathrm{decline}}\right)
& t > t_\mathrm{peak}
\end{cases}
The two pieces agree at :math:`t = t_\mathrm{peak}`, where :math:`L(t_\mathrm{peak}) = A`
exactly, by construction. ``t_peak`` is not itself a free parameter --
it is fixed at :math:`t_\mathrm{peak} = 5\sigma_\mathrm{rise}`, five
Gaussian widths after explosion.
.. rubric:: Parameters
The light curve parameters are summarized below.
.. list-table::
:header-rows: 1
:widths: 18 18 64
* - Parameter
- Symbol
- Description
* - ``amplitude``
- :math:`A`
- Peak bolometric luminosity.
* - ``sigma_rise``
- :math:`\sigma_\mathrm{rise}`
- Gaussian width of the rise.
* - ``tau_decline``
- :math:`\tau_\mathrm{decline}`
- Exponential decline timescale.
"""
_LIGHTCURVE_TYPE = "bolometric"
_DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = {
"amplitude": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e43 * _BOL_LUM_UNIT,
description="Peak bolometric luminosity.",
latex=r"A",
),
"sigma_rise": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=0.5 * u.day,
description="Gaussian width of the rise.",
latex=r"\sigma_\mathrm{rise}",
),
"tau_decline": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=3.0 * u.day,
description="Exponential decline timescale.",
latex=r"\tau_\mathrm{decline}",
),
}
@classmethod
def _eval(cls, t: NDArray[np.float64], **parameters: CGSParameterValue) -> NDArray[np.float64]:
r"""
Evaluate the natural log of :math:`L_\mathrm{bol}(t)`; see the class docstring for the functional form.
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.
"""
amplitude = parameters["amplitude"]
sigma_rise = parameters["sigma_rise"]
t_peak = 5 * sigma_rise
tau_decline = parameters["tau_decline"]
is_rising = t <= t_peak
log_rise = -0.5 * ((t - t_peak) / sigma_rise) ** 2
log_decline = -(t - t_peak) / tau_decline
return np.log(amplitude) + np.where(is_rising, log_rise, log_decline)
[docs]
class GaussianRisePowerLawLightcurve(Lightcurve):
r"""
A Gaussian rise followed by a power-law decline, peaked at ``t_peak``.
.. math::
L(t) = A \times \begin{cases}
\exp\left(-\dfrac{(t - t_\mathrm{peak})^2}{2\sigma_\mathrm{rise}^2}\right)
& t \le t_\mathrm{peak} \\[4pt]
\left(\dfrac{t}{t_\mathrm{peak}}\right)^{-\alpha_\mathrm{decline}}
& t > t_\mathrm{peak}
\end{cases}
The two branches agree exactly at :math:`t = t_\mathrm{peak}`, where
:math:`L(t_\mathrm{peak}) = A` -- the power-law branch is referenced to
:math:`t_\mathrm{peak}` rather than :math:`t = 0` for exactly this
reason. Unlike `GREDLightcurve`'s exponential cutoff, the power-law tail
here (:math:`L \propto t^{-\alpha_\mathrm{decline}}` for :math:`t \gg
t_\mathrm{peak}`) fades slowly at late times -- the qualitative
late-time behavior of transients with an extended power source (e.g.
luminous fast blue optical transients) rather than a sharply cut-off
pulse.
``sigma_rise`` is not itself a free parameter -- it is fixed at
:math:`\sigma_\mathrm{rise} = t_\mathrm{peak}/5`, one fifth of the time
to peak, the same ratio `GREDLightcurve` fixes between the two
quantities.
.. rubric:: Parameters
The light curve 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.
"""
_LIGHTCURVE_TYPE = "bolometric"
_DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = {
"amplitude": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e43 * _BOL_LUM_UNIT,
description="Peak bolometric luminosity.",
latex=r"A",
),
"t_peak": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=5.0 * u.day,
description="Time of peak luminosity since explosion.",
latex=r"t_\mathrm{peak}",
),
"decline_index": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1.5 * u.dimensionless_unscaled,
description="Positive post-peak power-law decline index.",
latex=r"\alpha_\mathrm{decline}",
),
}
# 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 `Lightcurve._eval`.
@classmethod
def _eval( # type: ignore[override]
cls,
t: NDArray[np.float64],
*,
amplitude: CGSParameterValue,
t_peak: CGSParameterValue,
decline_index: CGSParameterValue,
) -> NDArray[np.float64]:
r"""
Evaluate the natural log of :math:`L_\mathrm{bol}(t)`; see the class docstring for the functional form.
Parameters
----------
t : numpy.ndarray
Time since explosion, in seconds.
amplitude, t_peak, decline_index : float or numpy.ndarray
This model's parameter values, in cgs units; see the class docstring.
Returns
-------
numpy.ndarray
The natural log of :math:`L_\mathrm{bol}(t)`, in erg/s.
"""
sigma_rise = t_peak / 5
log_rise = -0.5 * ((t - t_peak) / sigma_rise) ** 2
# `t_peak` is strictly positive (its prior's support excludes 0), so
# `t = 0` always falls on the rising branch -- the `log(0) = -inf`
# this produces on the discarded decline branch is a harmless
# masked-out value, not an error, exactly as in `FREDLightcurve._eval`.
with np.errstate(divide="ignore"):
log_decline = -decline_index * np.log(t / t_peak)
return np.log(amplitude) + np.where(t <= t_peak, log_rise, log_decline)
[docs]
class GaussianRiseBrokenPowerLawLightcurve(Lightcurve):
r"""
A Gaussian rise followed by a broken power-law decline, peaked at ``t_peak``.
.. math::
L(t) = A \times \begin{cases}
\exp\left(-\dfrac{(t - t_\mathrm{peak})^2}{2\sigma_\mathrm{rise}^2}\right)
& t \le t_\mathrm{peak} \\[4pt]
\left(\dfrac{t}{t_\mathrm{peak}}\right)^{-\alpha_1}
& t_\mathrm{peak} < t \le t_\mathrm{break} \\[4pt]
\left(\dfrac{t_\mathrm{break}}{t_\mathrm{peak}}\right)^{-\alpha_1}
\left(\dfrac{t}{t_\mathrm{break}}\right)^{-\alpha_2}
& t > t_\mathrm{break}
\end{cases}
Extends `GaussianRisePowerLawLightcurve` with a second, steeper power-law
segment past a break time ``t_break``. Every branch agrees exactly at its
boundary, by construction: :math:`L(t_\mathrm{peak}) = A`, and the two
decline branches meet at :math:`t_\mathrm{break}` without a jump. Useful
for transients whose late-time decline steepens relative to the
early-time behavior -- e.g. a kilonova's blue/early ejecta component
fading faster once it becomes optically thin.
``t_break`` is assumed to exceed ``t_peak``, although this ordering is
not enforced by the model itself.
``sigma_rise`` is not itself a free parameter -- it is fixed at
:math:`\sigma_\mathrm{rise} = t_\mathrm{peak}/5`, one fifth of the time
to peak, the same ratio `GREDLightcurve` fixes between the two
quantities.
.. rubric:: Parameters
The light curve 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_1``
- :math:`\alpha_1`
- Positive power-law decline index between ``t_peak`` and ``t_break``.
* - ``decline_index_2``
- :math:`\alpha_2`
- Positive power-law decline index past ``t_break``.
* - ``t_break``
- :math:`t_\mathrm{break}`
- Time at which the decline steepens from :math:`\alpha_1` to :math:`\alpha_2`.
"""
_LIGHTCURVE_TYPE = "bolometric"
_DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = {
"amplitude": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e43 * _BOL_LUM_UNIT,
description="Peak bolometric luminosity.",
latex=r"A",
),
"t_peak": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=5.0 * u.day,
description="Time of peak luminosity since explosion.",
latex=r"t_\mathrm{peak}",
),
"decline_index_1": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1.0 * u.dimensionless_unscaled,
description="Positive power-law decline index between t_peak and t_break.",
latex=r"\alpha_1",
),
"decline_index_2": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=3.0 * u.dimensionless_unscaled,
description="Positive power-law decline index past t_break.",
latex=r"\alpha_2",
),
"t_break": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=10.0 * u.day,
description="Time at which the decline steepens from decline_index_1 to decline_index_2.",
latex=r"t_\mathrm{break}",
),
}
# 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 `Lightcurve._eval`.
@classmethod
def _eval( # type: ignore[override]
cls,
t: NDArray[np.float64],
*,
amplitude: CGSParameterValue,
t_peak: CGSParameterValue,
decline_index_1: CGSParameterValue,
decline_index_2: CGSParameterValue,
t_break: CGSParameterValue,
) -> NDArray[np.float64]:
r"""
Evaluate the natural log of :math:`L_\mathrm{bol}(t)`; see the class docstring for the functional form.
Parameters
----------
t : numpy.ndarray
Time since explosion, in seconds.
amplitude, t_peak, decline_index_1, decline_index_2, t_break : float or numpy.ndarray
This model's parameter values, in cgs units; see the class docstring.
Returns
-------
numpy.ndarray
The natural log of :math:`L_\mathrm{bol}(t)`, in erg/s.
"""
sigma_rise = t_peak / 5
log_rise = -0.5 * ((t - t_peak) / sigma_rise) ** 2
# `t_peak`/`t_break` are strictly positive (their priors' support
# excludes 0), so `t = 0` always falls on the rising branch -- the
# `log(0) = -inf` this produces on the discarded decline branches is
# a harmless masked-out value, not an error, exactly as in
# `GaussianRisePowerLawLightcurve._eval`.
with np.errstate(divide="ignore"):
log_decline_early = -decline_index_1 * np.log(t / t_peak)
log_break_offset = -decline_index_1 * np.log(t_break / t_peak)
log_decline_late = log_break_offset - decline_index_2 * np.log(t / t_break)
log_shape = np.where(
t <= t_peak,
log_rise,
np.where(t <= t_break, log_decline_early, log_decline_late),
)
return np.log(amplitude) + log_shape
[docs]
class BazinLightcurve(Lightcurve):
r"""
A smooth asymmetric transient following the Bazin functional form.
.. math::
L(t) =
A\,
\frac{
\exp[-(t-t_0)/\tau_\mathrm{fall}]
}{
1 + \exp[-(t-t_0)/\tau_\mathrm{rise}]
}.
The model combines a logistic-like rise with an exponential decline and is
commonly useful as a generic phenomenological approximation to supernova-like
transients.
A finite maximum exists when
.. math::
\tau_\mathrm{fall} > \tau_\mathrm{rise}.
Unlike most of the other pulse models in this module, ``amplitude`` is the
normalization of the Bazin function and is not, in general, exactly the peak
luminosity.
.. rubric:: Parameters
The light curve parameters are summarized below.
.. list-table::
:header-rows: 1
:widths: 18 18 64
* - Parameter
- Symbol
- Description
* - ``amplitude``
- :math:`A`
- Luminosity normalization.
* - ``t0``
- :math:`t_0`
- Characteristic transition time.
* - ``rise``
- :math:`\tau_\mathrm{rise}`
- Logistic rise timescale.
* - ``fall``
- :math:`\tau_\mathrm{fall}`
- Exponential decline timescale.
"""
_LIGHTCURVE_TYPE = "bolometric"
_DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = {
"amplitude": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e43 * _BOL_LUM_UNIT,
description="Luminosity normalization.",
latex=r"A",
),
"t0": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=10.0 * u.day,
description="Characteristic transition time.",
latex=r"t_0",
),
"rise": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=5.0 * u.day,
description="Logistic rise timescale.",
latex=r"\tau_\mathrm{rise}",
),
"fall": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.3),
scale=30.0 * u.day,
description="Exponential decline timescale.",
latex=r"\tau_\mathrm{fall}",
),
}
@classmethod
def _eval(
cls,
t: NDArray[np.float64],
**parameters: CGSParameterValue,
) -> NDArray[np.float64]:
r"""
Evaluate the natural log of :math:`L_\mathrm{bol}(t)`; see the class docstring for the functional form.
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.
"""
amplitude = parameters["amplitude"]
t0 = parameters["t0"]
rise = parameters["rise"]
fall = parameters["fall"]
x = t - t0
return np.log(amplitude) - x / fall - np.logaddexp(0.0, -x / rise)
[docs]
class PowerLawLightcurve(Lightcurve):
r"""
A power-law decline beginning at a reference time.
.. math::
L(t) =
\begin{cases}
0, & t < t_\mathrm{ref}, \\[4pt]
A \left(\dfrac{t}{t_\mathrm{ref}}\right)^{-\alpha},
& t \ge t_\mathrm{ref}.
\end{cases}
The luminosity is therefore exactly :math:`A` at ``t_ref``. This form is
useful for generic afterglows and other transients whose post-onset emission
is approximately scale free.
Restricting the model to ``t >= t_ref`` avoids the formal divergence of a
declining power law as :math:`t \rightarrow 0`.
.. rubric:: Parameters
The light curve parameters are summarized below.
.. list-table::
:header-rows: 1
:widths: 18 18 64
* - Parameter
- Symbol
- Description
* - ``amplitude``
- :math:`A`
- Luminosity at the reference time.
* - ``t_ref``
- :math:`t_\mathrm{ref}`
- Reference time at which the power law begins.
* - ``index``
- :math:`\alpha`
- Positive power-law decline index.
"""
_LIGHTCURVE_TYPE = "bolometric"
_DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = {
"amplitude": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e43 * _BOL_LUM_UNIT,
description="Luminosity at the reference time.",
latex=r"A",
),
"t_ref": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1.0 * u.day,
description="Reference time at which the power law begins.",
latex=r"t_\mathrm{ref}",
),
"index": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1.0 * u.dimensionless_unscaled,
description="Positive power-law decline index.",
latex=r"\alpha",
),
}
@classmethod
def _eval(
cls,
t: NDArray[np.float64],
**parameters: CGSParameterValue,
) -> NDArray[np.float64]:
r"""
Evaluate the natural log of :math:`L_\mathrm{bol}(t)`; see the class docstring for the functional form.
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.
"""
amplitude = parameters["amplitude"]
t_ref = parameters["t_ref"]
index = parameters["index"]
with np.errstate(divide="ignore", invalid="ignore"):
log_luminosity = np.log(amplitude) - index * np.log(t / t_ref)
return np.where(t >= t_ref, log_luminosity, -np.inf)
[docs]
class BrokenPowerLawLightcurve(Lightcurve):
r"""
A sharply broken power-law transient with a rise and decline.
.. math::
L(t) =
A
\begin{cases}
\left(\dfrac{t}{t_\mathrm{peak}}\right)^{\alpha_\mathrm{rise}},
& 0 < t \le t_\mathrm{peak}, \\[6pt]
\left(\dfrac{t}{t_\mathrm{peak}}\right)^{-\alpha_\mathrm{decline}},
& t > t_\mathrm{peak}.
\end{cases}
Both indices are defined to be positive. The two branches meet exactly at
``t_peak``, where :math:`L(t_\mathrm{peak}) = A`. For positive rise index,
the luminosity tends continuously to zero as :math:`t \rightarrow 0`.
.. rubric:: Parameters
The light curve 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 the power-law break and peak.
* - ``rise_index``
- :math:`\alpha_\mathrm{rise}`
- Positive pre-peak power-law index.
* - ``decline_index``
- :math:`\alpha_\mathrm{decline}`
- Positive post-peak power-law decline index.
"""
_LIGHTCURVE_TYPE = "bolometric"
_DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = {
"amplitude": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e43 * _BOL_LUM_UNIT,
description="Peak bolometric luminosity.",
latex=r"A",
),
"t_peak": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=10.0 * u.day,
description="Time of the power-law break and peak.",
latex=r"t_\mathrm{peak}",
),
"rise_index": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=2.0 * u.dimensionless_unscaled,
description="Positive pre-peak power-law index.",
latex=r"\alpha_\mathrm{rise}",
),
"decline_index": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1.5 * u.dimensionless_unscaled,
description="Positive post-peak power-law decline index.",
latex=r"\alpha_\mathrm{decline}",
),
}
@classmethod
def _eval(
cls,
t: NDArray[np.float64],
**parameters: CGSParameterValue,
) -> NDArray[np.float64]:
r"""
Evaluate the natural log of :math:`L_\mathrm{bol}(t)`; see the class docstring for the functional form.
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.
"""
amplitude = parameters["amplitude"]
t_peak = parameters["t_peak"]
rise_index = parameters["rise_index"]
decline_index = parameters["decline_index"]
with np.errstate(divide="ignore"):
log_time = np.log(t / t_peak)
log_shape = np.where(
t <= t_peak,
rise_index * log_time,
-decline_index * log_time,
)
return np.log(amplitude) + log_shape
[docs]
class SmoothBrokenPowerLawLightcurve(Lightcurve):
r"""
A smoothly broken power-law transient.
The asymptotic behavior is
.. math::
L(t) \propto
\begin{cases}
t^{\alpha_\mathrm{rise}}, & t \ll t_\mathrm{peak}, \\
t^{-\alpha_\mathrm{decline}}, & t \gg t_\mathrm{peak},
\end{cases}
with a smooth transition between the two branches. The implementation uses
.. math::
f(x) =
\left[
x^{-s\alpha_\mathrm{rise}}
+
x^{s\alpha_\mathrm{decline}}
\right]^{-1/s},
but rescales the argument and normalization so that the maximum occurs
exactly at ``t_peak`` and
.. math::
L(t_\mathrm{peak}) = A.
The positive ``smoothness`` parameter :math:`s` controls the sharpness of
the transition: larger values approach a sharply broken power law.
.. rubric:: Parameters
The light curve 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.
* - ``rise_index``
- :math:`\alpha_\mathrm{rise}`
- Positive asymptotic rise index.
* - ``decline_index``
- :math:`\alpha_\mathrm{decline}`
- Positive asymptotic decline index.
* - ``smoothness``
- :math:`s`
- Sharpness of the transition between power-law branches.
"""
_LIGHTCURVE_TYPE = "bolometric"
_DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = {
"amplitude": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e43 * _BOL_LUM_UNIT,
description="Peak bolometric luminosity.",
latex=r"A",
),
"t_peak": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=10.0 * u.day,
description="Time of peak luminosity.",
latex=r"t_\mathrm{peak}",
),
"rise_index": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=2.0 * u.dimensionless_unscaled,
description="Positive asymptotic rise index.",
latex=r"\alpha_\mathrm{rise}",
),
"decline_index": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1.5 * u.dimensionless_unscaled,
description="Positive asymptotic decline index.",
latex=r"\alpha_\mathrm{decline}",
),
"smoothness": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.4),
scale=2.0 * u.dimensionless_unscaled,
description="Sharpness of the transition between power-law branches.",
latex=r"s",
),
}
@classmethod
def _eval(
cls,
t: NDArray[np.float64],
**parameters: CGSParameterValue,
) -> NDArray[np.float64]:
r"""
Evaluate the natural log of :math:`L_\mathrm{bol}(t)`; see the class docstring for the functional form.
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.
"""
amplitude = parameters["amplitude"]
t_peak = parameters["t_peak"]
rise_index = parameters["rise_index"]
decline_index = parameters["decline_index"]
smoothness = parameters["smoothness"]
# For
#
# f(y) = [y^(-s a) + y^(s b)]^(-1/s),
#
# the maximum occurs at
#
# y_peak = (a / b)^[1 / (s(a+b))].
#
# Scale y so that t=t_peak corresponds exactly to y=y_peak.
log_y_peak = np.log(rise_index / decline_index) / (smoothness * (rise_index + decline_index))
with np.errstate(divide="ignore"):
log_y = np.log(t / t_peak) + log_y_peak
log_shape = -(1.0 / smoothness) * np.logaddexp(
-smoothness * rise_index * log_y,
smoothness * decline_index * log_y,
)
log_shape_peak = -(1.0 / smoothness) * np.logaddexp(
-smoothness * rise_index * log_y_peak,
smoothness * decline_index * log_y_peak,
)
return np.log(amplitude) + log_shape - log_shape_peak
[docs]
class DelayedExponentialLightcurve(Lightcurve):
r"""
A polynomial rise followed by an exponential decline.
.. math::
L(t) =
A
\left(\frac{t}{t_\mathrm{peak}}\right)^\alpha
\exp\left[
\alpha\left(1-\frac{t}{t_\mathrm{peak}}\right)
\right],
\qquad t > 0.
This is a gamma-like transient pulse. The chosen parameterization makes
.. math::
L(t_\mathrm{peak}) = A
exactly, while :math:`L \rightarrow 0` as :math:`t \rightarrow 0^+` and
the late-time emission declines exponentially.
The positive shape parameter :math:`\alpha` controls both the steepness of
the rise and the relation between the peak time and exponential timescale.
.. rubric:: Parameters
The light curve 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.
* - ``shape``
- :math:`\alpha`
- Positive dimensionless pulse-shape parameter.
"""
_LIGHTCURVE_TYPE = "bolometric"
_DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = {
"amplitude": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e43 * _BOL_LUM_UNIT,
description="Peak bolometric luminosity.",
latex=r"A",
),
"t_peak": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=5.0 * u.day,
description="Time of peak luminosity.",
latex=r"t_\mathrm{peak}",
),
"shape": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=2.0 * u.dimensionless_unscaled,
description="Positive dimensionless pulse-shape parameter.",
latex=r"\alpha",
),
}
@classmethod
def _eval(
cls,
t: NDArray[np.float64],
**parameters: CGSParameterValue,
) -> NDArray[np.float64]:
r"""
Evaluate the natural log of :math:`L_\mathrm{bol}(t)`; see the class docstring for the functional form.
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.
"""
amplitude = parameters["amplitude"]
t_peak = parameters["t_peak"]
shape = parameters["shape"]
x = t / t_peak
with np.errstate(divide="ignore", invalid="ignore"):
return np.log(amplitude) + shape * np.log(x) + shape * (1.0 - x)
[docs]
class LogNormalPulseLightcurve(Lightcurve):
r"""
A log-normal pulse in time.
.. math::
L(t) =
A
\exp\left[
-\frac{1}{2}
\left(
\frac{\ln(t/t_\mathrm{peak})}{\sigma}
\right)^2
\right],
\qquad t > 0.
The pulse peaks exactly at ``t_peak`` with
.. math::
L(t_\mathrm{peak}) = A.
Unlike a Gaussian pulse in linear time, a log-normal pulse is intrinsically
asymmetric and has support only at positive times. The dimensionless width
``sigma`` controls the width in logarithmic time.
.. rubric:: Parameters
The light curve 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.
* - ``sigma``
- :math:`\sigma`
- Width of the pulse in logarithmic time.
"""
_LIGHTCURVE_TYPE = "bolometric"
_DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = {
"amplitude": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e43 * _BOL_LUM_UNIT,
description="Peak bolometric luminosity.",
latex=r"A",
),
"t_peak": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=5.0 * u.day,
description="Time of peak luminosity.",
latex=r"t_\mathrm{peak}",
),
"sigma": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.4),
scale=0.5 * u.dimensionless_unscaled,
description="Width of the pulse in logarithmic time.",
latex=r"\sigma",
),
}
@classmethod
def _eval(
cls,
t: NDArray[np.float64],
**parameters: CGSParameterValue,
) -> NDArray[np.float64]:
r"""
Evaluate the natural log of :math:`L_\mathrm{bol}(t)`; see the class docstring for the functional form.
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.
"""
amplitude = parameters["amplitude"]
t_peak = parameters["t_peak"]
sigma = parameters["sigma"]
with np.errstate(divide="ignore"):
log_time = np.log(t / t_peak)
return np.log(amplitude) - 0.5 * (log_time / sigma) ** 2
[docs]
class PlateauPowerLawLightcurve(Lightcurve):
r"""
A constant plateau followed by a power-law decline.
.. math::
L(t) =
A
\begin{cases}
1, & 0 \le t \le t_\mathrm{break}, \\[4pt]
\left(\dfrac{t}{t_\mathrm{break}}\right)^{-\alpha},
& t > t_\mathrm{break}.
\end{cases}
This provides a minimal phenomenological description of a transient with a
sustained plateau followed by scale-free fading, such as some GRB afterglow
and engine-powered transient light curves.
.. rubric:: Parameters
The light curve parameters are summarized below.
.. list-table::
:header-rows: 1
:widths: 18 18 64
* - Parameter
- Symbol
- Description
* - ``amplitude``
- :math:`A`
- Plateau bolometric luminosity.
* - ``t_break``
- :math:`t_\mathrm{break}`
- Time at which the plateau transitions to a power-law decline.
* - ``index``
- :math:`\alpha`
- Positive post-plateau power-law decline index.
"""
_LIGHTCURVE_TYPE = "bolometric"
_DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = {
"amplitude": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e43 * _BOL_LUM_UNIT,
description="Plateau bolometric luminosity.",
latex=r"A",
),
"t_break": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1.0 * u.day,
description="Time at which the plateau transitions to a power-law decline.",
latex=r"t_\mathrm{break}",
),
"index": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1.5 * u.dimensionless_unscaled,
description="Positive post-plateau power-law decline index.",
latex=r"\alpha",
),
}
@classmethod
def _eval(
cls,
t: NDArray[np.float64],
**parameters: CGSParameterValue,
) -> NDArray[np.float64]:
r"""
Evaluate the natural log of :math:`L_\mathrm{bol}(t)`; see the class docstring for the functional form.
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.
"""
amplitude = parameters["amplitude"]
t_break = parameters["t_break"]
index = parameters["index"]
with np.errstate(divide="ignore"):
log_decline = -index * np.log(t / t_break)
return np.log(amplitude) + np.where(
t <= t_break,
0.0,
log_decline,
)
[docs]
class VillarLightcurve(Lightcurve):
r"""
A parametric supernova-like light curve.
.. math::
L(t) =
A \times
\begin{cases}
\dfrac{1 + \beta(t - t_0)}{1 + \exp[-(t-t_0)/\tau_\mathrm{rise}]},
& t < t_1, \\[8pt]
\dfrac{(1 + \beta\gamma)\,
\exp[-(t-t_1)/\tau_\mathrm{fall}]}{1 + \exp[-(t-t_0)/\tau_\mathrm{rise}]},
& t \ge t_1,
\end{cases}
with :math:`t_1 = t_0 + \gamma`. A logistic rise (timescale
:math:`\tau_\mathrm{rise}`) turns on around :math:`t_0`; the light curve
then declines linearly with slope :math:`\beta` (a plateau, for small
:math:`|\beta|`) until :math:`t_1`, after which it switches to an
exponential decline with timescale :math:`\tau_\mathrm{fall}`. The two
branches agree exactly at :math:`t_1`, by construction.
This is the parametric form used by :footcite:t:`2019ApJ...884...83V` to
fit multi-band supernova photometry, adapted here to a bolometric
luminosity rather than a per-band flux.
.. rubric:: Parameters
The light curve parameters are summarized below.
.. list-table::
:header-rows: 1
:widths: 18 18 64
* - Parameter
- Symbol
- Description
* - ``amplitude``
- :math:`A`
- Overall luminosity normalization.
* - ``t0``
- :math:`t_0`
- Reference time at which the logistic rise is centered.
* - ``gamma``
- :math:`\gamma`
- Duration of the plateau, measured from t0.
* - ``beta``
- :math:`\beta`
- Linear slope of the plateau.
* - ``tau_rise``
- :math:`\tau_\mathrm{rise}`
- Logistic rise timescale.
* - ``tau_fall``
- :math:`\tau_\mathrm{fall}`
- Exponential decline timescale, after the plateau.
See Also
--------
uvex_transients.models.supernovae.VillarCoolingBlackbodySED :
Pairs this bolometric light curve with a cooling-blackbody spectrum.
References
----------
.. footbibliography::
"""
_LIGHTCURVE_TYPE = "bolometric"
_DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = {
"amplitude": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e43 * _BOL_LUM_UNIT,
description="Overall luminosity normalization.",
latex=r"A",
),
"t0": Parameter(
prior=NormalPrior(mean=0.0, sigma=1.0),
scale=1.0 * u.day,
description="Reference time at which the logistic rise is centered.",
latex=r"t_0",
),
"gamma": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=10.0 * u.day,
description="Duration of the plateau, measured from t0.",
latex=r"\gamma",
),
"beta": Parameter(
prior=NormalPrior(mean=0.0, sigma=0.5),
scale=1e-2 / u.day,
description="Linear slope of the plateau.",
latex=r"\beta",
),
"tau_rise": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=5.0 * u.day,
description="Logistic rise timescale.",
latex=r"\tau_\mathrm{rise}",
),
"tau_fall": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=30.0 * u.day,
description="Exponential decline timescale, after the plateau.",
latex=r"\tau_\mathrm{fall}",
),
}
# 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 `Lightcurve._eval`.
@classmethod
def _eval( # type: ignore[override]
cls,
t: NDArray[np.float64],
*,
amplitude: CGSParameterValue,
t0: CGSParameterValue,
gamma: CGSParameterValue,
beta: CGSParameterValue,
tau_rise: CGSParameterValue,
tau_fall: CGSParameterValue,
) -> NDArray[np.float64]:
r"""
Evaluate the natural log of :math:`L_\mathrm{bol}(t)`; see the class docstring for the functional form.
Parameters
----------
t : numpy.ndarray
Time since explosion, in seconds.
amplitude, t0, gamma, beta, tau_rise, tau_fall : float or numpy.ndarray
This model's parameter values, in cgs units; see the class docstring.
Returns
-------
numpy.ndarray
The natural log of :math:`L_\mathrm{bol}(t)`, in erg/s.
"""
x = t - t0
t1 = t0 + gamma
is_rise = t < t1
with np.errstate(invalid="ignore"):
linear = np.where(
is_rise,
1.0 + beta * x,
(1.0 + beta * gamma) * np.exp(-(t - t1) / tau_fall),
)
log_shape = np.log(linear) - np.logaddexp(0.0, -x / tau_rise)
return np.log(amplitude) + log_shape
[docs]
class TwoComponentBazinLightcurve(Lightcurve):
r"""
A superposition of two independent Bazin pulses.
.. math::
L(t) =
A_0\,
\frac{\exp[-(t-t_0)/\tau_{\mathrm{fall},0}]}{1 + \exp[-(t-t_0)/\tau_{\mathrm{rise},0}]}
+
A_1\,
\frac{\exp[-(t-t_1)/\tau_{\mathrm{fall},1}]}{1 + \exp[-(t-t_1)/\tau_{\mathrm{rise},1}]}.
Two ordinary :class:`BazinLightcurve` pulses, added rather than multiplied, each with its own
amplitude, transition time, and rise/fall timescales. Because the two components are
independent and additive, this covers both single- and double-peaked light curves in the same
functional form: with :math:`A_0 \ll A_1` (or a comparable but much earlier/narrower first
pulse well clear of the second), the second component alone sets the observed shape -- a
single peak; with :math:`A_0` comparable to :math:`A_1`, both pulses are visible, with a dip
between them where each has decayed enough to let the other dominate. Since the two peaks are
genuinely independent components rather than one shape reweighted by another, their relative
amplitude, timing, and widths can be tuned separately to fit real double- and single-peaked
events alike.
.. rubric:: Parameters
The light curve parameters are summarized below.
.. list-table::
:header-rows: 1
:widths: 18 18 64
* - Parameter
- Symbol
- Description
* - ``amplitude_0``
- :math:`A_0`
- Luminosity normalization of the first (typically earlier) pulse.
* - ``t0``
- :math:`t_0`
- Characteristic transition time of the first pulse.
* - ``rise_0``
- :math:`\tau_{\mathrm{rise},0}`
- Logistic rise timescale of the first pulse.
* - ``fall_0``
- :math:`\tau_{\mathrm{fall},0}`
- Exponential decline timescale of the first pulse.
* - ``amplitude_1``
- :math:`A_1`
- Luminosity normalization of the second (typically later) pulse.
* - ``t1``
- :math:`t_1`
- Characteristic transition time of the second pulse.
* - ``rise_1``
- :math:`\tau_{\mathrm{rise},1}`
- Logistic rise timescale of the second pulse.
* - ``fall_1``
- :math:`\tau_{\mathrm{fall},1}`
- Exponential decline timescale of the second pulse.
See Also
--------
BazinLightcurve :
The single-pulse form this combines two of.
"""
_LIGHTCURVE_TYPE = "bolometric"
_DEFAULT_PARAMETERS: ClassVar[dict[str, Parameter]] = {
"amplitude_0": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e42 * _BOL_LUM_UNIT,
description="Luminosity normalization of the first (typically earlier) pulse.",
latex=r"A_0",
),
"t0": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=2.0 * u.day,
description="Characteristic transition time of the first pulse.",
latex=r"t_0",
),
"rise_0": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=0.3 * u.day,
description="Logistic rise timescale of the first pulse.",
latex=r"\tau_{\mathrm{rise},0}",
),
"fall_0": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.3),
scale=5.0 * u.day,
description="Exponential decline timescale of the first pulse.",
latex=r"\tau_{\mathrm{fall},0}",
),
"amplitude_1": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=1e43 * _BOL_LUM_UNIT,
description="Luminosity normalization of the second (typically later) pulse.",
latex=r"A_1",
),
"t1": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=18.0 * u.day,
description="Characteristic transition time of the second pulse.",
latex=r"t_1",
),
"rise_1": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.5),
scale=5.0 * u.day,
description="Logistic rise timescale of the second pulse.",
latex=r"\tau_{\mathrm{rise},1}",
),
"fall_1": Parameter(
prior=LogNormalPrior(mean=0.0, sigma=0.3),
scale=30.0 * u.day,
description="Exponential decline timescale of the second pulse.",
latex=r"\tau_{\mathrm{fall},1}",
),
}
@classmethod
def _eval(
cls,
t: NDArray[np.float64],
**parameters: CGSParameterValue,
) -> NDArray[np.float64]:
r"""
Evaluate the natural log of :math:`L_\mathrm{bol}(t)`; see the class docstring for the functional form.
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.
"""
x0 = t - parameters["t0"]
log_bazin_0 = -x0 / parameters["fall_0"] - np.logaddexp(0.0, -x0 / parameters["rise_0"])
x1 = t - parameters["t1"]
log_bazin_1 = -x1 / parameters["fall_1"] - np.logaddexp(0.0, -x1 / parameters["rise_1"])
return np.logaddexp(
np.log(parameters["amplitude_0"]) + log_bazin_0,
np.log(parameters["amplitude_1"]) + log_bazin_1,
)