Source code for cosmofit.samplers.base

"""
What every sampler of the new core shares.
"""

from __future__ import annotations

import numpy as np


__all__ = ["Sampler", "ChainColumns"]


[docs] class ChainColumns: """ The columns of a chain row after ``weight`` and ``minuslogpost``: the sampled parameters, the derived ones not dropped, ``minuslogprior``, and each likelihood's ``chi2__<name>`` (dots as underscores). Parameters ---------- model : core.model.Model """ def __init__(self, model): parameters = model.parameters self.names = list(parameters.sampled) self.derived_names = [ n for n in parameters.derived if not parameters.specs[n].drop ] self.likelihood_names = list(model.likelihoods) self.columns = [ *self.names, *self.derived_names, "minuslogprior", *(f"chi2__{name.replace('.', '_')}" for name in self.likelihood_names), ]
[docs] def of(self, x, result) -> np.ndarray: """``x`` and its evaluation, in :attr:`columns` order.""" derived = [result.derived.get(n, np.nan) for n in self.derived_names] chi2 = [-2.0 * result.loglikes.get(n, np.nan) for n in self.likelihood_names] return np.concatenate([np.asarray(x, dtype=float), derived, [-result.logprior], chi2])
[docs] class Sampler: """ Base sampler. Parameters ---------- info : dict The sampler's options from the input. model : core.model.Model The model to sample. seed : int, optional Seeds ``rng``. An ``info["seed"]`` takes precedence. output : core.output.Output, optional Where to write; nothing is written without one. Notes ----- Subclasses read their options in :meth:`initialize`, do the work in :meth:`run`, and return what they found from :meth:`products`. A sampler that cannot pick up where an earlier run stopped leaves :attr:`resumable` false, and is refused ``resume: true``. """ #: Options this sampler takes, with defaults. defaults: dict = {} #: Whether ``resume: true`` can continue an earlier run's output. resumable: bool = False #: Whether this sampler shares a run between processes under #: ``mpirun``. One that does not is refused there, rather than run #: once per process into the same output. parallel: bool = False def __init__(self, info: dict | None, model, seed: int | None = None, output=None): from cosmofit.core.output import Output info = dict(info or {}) unknown = set(info) - set(self.defaults) - {"seed"} if unknown: raise ValueError( f"{type(self).__name__}: unknown option(s) {sorted(unknown)}; " f"it takes {sorted(self.defaults)}." ) self.options = {**self.defaults, **info} self.model = model self.seed = info.get("seed", seed) self.rng = np.random.default_rng(self.seed) self.output = output if output is not None else Output({}) #: Called with a dict of progress at every check, by the #: samplers that check (``mcmc``, ``emcee``); see #: :func:`core.run.run`. self.callback = None self.initialize()
[docs] def initialize(self) -> None: """Read options. Override."""
def run(self) -> None: raise NotImplementedError def products(self) -> dict: raise NotImplementedError # --------------------------------------------------------- # What every sampler writes a point as # --------------------------------------------------------- def _set_columns(self) -> None: """ :attr:`names` (sampled), :attr:`derived_names`, :attr:`likelihood_names` and :attr:`columns`: a row's names after ``weight`` and ``minuslogpost``, as the chain files carry them. """ layout = ChainColumns(self.model) self._layout = layout self.names = layout.names self.d = len(self.names) self.derived_names = layout.derived_names self.likelihood_names = layout.likelihood_names self.columns = layout.columns # Theories' derived parameters, at every point, only when a # chain column needs them. Asked for regardless, the legacy # model's sound horizon was evaluated at every step of fits that # never use it -- and warned that omega_b h^2 was outside its # calibration on a fit whose rd was fixed. self.want_derived = bool(self.derived_names) def _columns_of(self, x, result) -> np.ndarray: """``x`` and its evaluation, in :attr:`columns` order.""" return self._layout.of(x, result) def _prepare_output(self) -> bool: """ Ready the output prefix; whether this run resumes an earlier one. """ from cosmofit.core.output import OutputError resumed = self.output.prepare() if resumed and not self.resumable: raise OutputError( f"{type(self).__name__} cannot resume an earlier run; use " f"'force: true' to start over, or a new output prefix." ) return resumed