Source code for cosmofit.stats.priors
"""
Prior distributions.
CosmoFit currently implements independent uniform ("top-hat")
priors, which is what essentially every MCMC-based cosmology
analysis uses for the background parameters (H0, Omega_m, w0,
wa, rd, ...). More informative priors (Gaussian, etc.) can be
added later following the same interface.
"""
from __future__ import annotations
import numpy as np
# ============================================================
# Uniform prior
# ============================================================
[docs]
class UniformPrior:
"""
Independent uniform priors over a set of parameters.
Parameters
----------
names : list[str]
Parameter names, in the order matching the ``theta``
vector this prior will be evaluated on.
bounds : dict[str, tuple[float, float]]
Mapping of parameter name -> (lower, upper). Every name
in ``names`` must have an entry.
"""
def __init__(self, names, bounds):
self.names = list(names)
missing = [n for n in self.names if n not in bounds]
if missing:
raise ValueError(
f"Missing bounds for parameter(s): {missing}"
)
self.lower = np.array(
[bounds[n][0] for n in self.names],
dtype=float,
)
self.upper = np.array(
[bounds[n][1] for n in self.names],
dtype=float,
)
if np.any(self.upper <= self.lower):
raise ValueError(
"Every parameter must have upper > lower bound."
)
# ---------------------------------------------------------
@property
def ndim(self) -> int:
return len(self.names)
# ---------------------------------------------------------
[docs]
def log_prior(self, theta) -> float:
"""
Return 0.0 if every entry of ``theta`` lies within its
bounds, otherwise -inf.
"""
theta = np.asarray(theta, dtype=float)
if theta.shape != self.lower.shape:
raise ValueError(
f"Expected a length-{self.ndim} vector, "
f"got shape {theta.shape}."
)
if np.any(theta < self.lower) or np.any(theta > self.upper):
return -np.inf
return 0.0
# Allow calling the prior directly: prior(theta)
__call__ = log_prior
# ---------------------------------------------------------
[docs]
def sample(self, n_samples: int, rng=None) -> np.ndarray:
"""
Draw ``n_samples`` independent samples from the prior.
Returns
-------
ndarray, shape (n_samples, ndim)
"""
if rng is None:
rng = np.random.default_rng()
return rng.uniform(
self.lower,
self.upper,
size=(n_samples, self.ndim),
)
# ---------------------------------------------------------
def __repr__(self):
bounds = ", ".join(
f"{n}=({lo:g}, {hi:g})"
for n, lo, hi in zip(self.names, self.lower, self.upper)
)
return f"UniformPrior({bounds})"