Source code for cosmofit.likelihoods.des_sn5yr
"""
DES-SN5YR (Dark Energy Survey 5-year) Supernova likelihood.
"""
from __future__ import annotations
import numpy as np
from cosmofit.data.loader import load_des_sn5yr
from .base import BaseLikelihood, AnalyticOffsetMixin
[docs]
class DESSN5YRLikelihood(BaseLikelihood, AnalyticOffsetMixin):
"""
DES-SN5YR (Dark Energy Survey 5-year) Supernova likelihood.
Unlike :class:`~likelihoods.pantheon.PantheonLikelihood` (which
compares an apparent-magnitude-like observable, ``m_b_corr``,
to ``mu(z) + M_B``), DES-SN5YR distributes the SN distance
modulus directly (``MU``, computed assuming a fiducial H0=70).
Comparing that to a model ``mu(z)`` computed at a different H0
leaves a constant offset -- the same H0/absolute-calibration
degeneracy Pantheon+ has, just expressed directly in distance-
modulus space rather than magnitude space -- which is
marginalized over analytically the same way (see
:class:`~likelihoods.base.AnalyticOffsetMixin`); this exactly
matches the DES-SN5YR data release's own reference likelihood
(``DES-Dovekie-SN_Likelihood.py``, ``cov_log_likelihood``).
Parameters
----------
cosmology
Cosmology model instance (LCDM, CPL, ...).
version
DES-SN5YR dataset version.
marginalize_offset : bool, optional
If True (default), the constant distance-modulus offset
(fully degenerate with H0) is marginalized over
analytically. If False, ``cosmology.MB`` is added to the
model as an explicit free/fixed nuisance parameter instead
(mu_model = mu(z) + M_B).
Warning
-------
Do not combine ``"pantheon"`` and ``"des_sn5yr"`` in the same
``Fitter`` fit. DES-SN5YR's low-redshift "anchor" sample
includes CfA3, CfA4, and Foundation supernovae (~11% of its
1820 SNe, IDSURVEY != 10) that are *also* compiled into
Pantheon+ -- fitting both at once double-counts those
supernovae (correlated, non-independent data treated as
independent), understating uncertainties and biasing any joint
result. Use one SN Ia compilation per fit, not both.
References
----------
Popovic et al. (2026), MNRAS, arXiv:2511.07517 -- the DES-Dovekie
reanalysis these files come from (byte-identical to
``4_DISTANCES_COVMAT/DES-Dovekie_HD.csv`` and ``STAT+SYS.npz`` in
des-science/DES-SN5YR): the same supernovae recalibrated with the
Dovekie photometric calibration, 1820 SNe after selection.
It supersedes the original release (Vincenzi et al. 2024; DES
Collaboration 2024, arXiv:2401.02929; 1829 SNe), which is still
tagged as version 1.3 of that repository but is *not* what is
bundled here. The two give different distances to the same
supernovae, and so different w0-wa constraints -- quote the
right one.
"""
[docs]
def __init__(
self,
cosmology,
version: str = "des-sn5yr",
marginalize_offset: bool = True,
):
dataset = load_des_sn5yr(
version=version,
)
self.marginalize_offset = marginalize_offset
super().__init__(
name="DES-SN5YR",
dataset=dataset,
cosmology=cosmology,
)
if self.marginalize_offset:
self._setup_offset_marginalization()
# ---------------------------------------------------------
def model(
self,
) -> np.ndarray:
"""
Predicted distance modulus.
m_B = mu(z) [marginalize_offset=True]
m_B = mu(z) + M_B [marginalize_offset=False]
As with Pantheon+, D_M is evaluated at the Hubble-diagram
redshift ``z_hd`` and the ``(1 + z)`` source-frame dilation
factor uses the heliocentric redshift ``z_hel``:
D_L = (1 + z_hel) * D_M(z_hd)
This matches the DES-SN5YR reference likelihood's
``extract_theory_points``, which computes
``5 log10[(1+zcmb)(1+zhel) D_A(zcmb)] + 25`` --
algebraically identical to the above, since
D_A(z) = D_M(z)/(1+z).
"""
dm_model = self.cosmology.distance.DM(
self.data.z_hd,
)
dl_model = dm_model * (1.0 + self.data.z_hel)
mu_model = 5.0 * np.log10(dl_model) + 25.0
if self.marginalize_offset:
return mu_model
return mu_model + self.cosmology.MB
# ---------------------------------------------------------
def residuals(
self,
) -> np.ndarray:
"""
Data minus model residuals.
"""
return (
self.data.mu
-
self.model()
)
# ---------------------------------------------------------
# best_fit_offset() is provided by AnalyticOffsetMixin. It is
# only meaningful here when marginalize_offset is True.
# ---------------------------------------------------------
def chi2(
self,
) -> float:
"""
Chi-square statistic.
"""
if self.marginalize_offset:
return self.marginalized_chi2()
return self.covariance.chi2(
self.residuals(),
)