Source code for pyvbmc.priors.prior

from abc import ABC, abstractmethod

import numpy as np


def _check_finite(arguments, note=""):
    """Check that the arguments defining a prior are finite.

    Every comparison with a NaN is false and an infinity satisfies a strict
    order, so the order checks of the box priors pass such arguments while
    the density they define is not a density.

    Parameters
    ----------
    arguments : dict
        The arguments by name, each a scalar or an array.
    note : str, optional
        A sentence appended to the error message, to say what the prior
        needs. Default `""`.

    Raises
    ------
    ValueError
        If any element of any argument is NaN or infinite.
    """
    for name, value in arguments.items():
        if not np.all(np.isfinite(value)):
            raise ValueError(
                f"All elements of {name}={value} should be finite." + note
            )


[docs] class Prior(ABC): """Abstract base class for PyVBMC prior distributions."""
[docs] def log_pdf(self, x, keepdims=True): """Compute the log-pdf of the distribution. Parameters ---------- x : np.ndarray The array of input point(s), of dimension `(D,)` or `(n, D)`, where `D` is the distribution dimension. The point(s) are read as `float64`, whatever dtype the array carries. keepdims : bool Whether to keep the input dimensions and return an array of shape `(n, 1)`, or discard them and return an array of shape `(n,)`. Returns ------- log_pdf : np.ndarray The log-density of the prior at the input point(s), of dimension `(n, 1)` or `(n,)` (depending on ``keepdims``). """ x_orig_shape = x.shape # The densities accumulate in the dtype of the input, so an integer # or a narrower float input would truncate or wrap the result. x = x.astype(np.float64, copy=False) x = np.atleast_2d(x) n, D = x.shape if self.D == 1 and n == 1: x = x.T elif D != self.D: raise ValueError( f"x should have shape ({self.D},) or (n, {self.D}) but has shape {x_orig_shape}." ) log_pdf = self._log_pdf(x) if keepdims: return log_pdf else: return log_pdf.ravel()
[docs] def pdf(self, x, keepdims=True): """Compute the pdf of the distribution. Parameters ---------- x : np.ndarray The array of input point(s), of dimension `(D,)` or `(n, D)`, where `D` is the distribution dimension. keepdims : bool Whether to keep the input dimensions and return an array of shape `(n, 1)`, or discard them and return an array of shape `(n,)`. Returns ------- pdf : np.ndarray The density of the prior at the input point(s), of dimension `(n, 1)` or `(n,)` (depending on ``keepdims``). """ return np.exp(self.log_pdf(x, keepdims=keepdims))
[docs] def support(self): r"""Returns the box that bounds the support of the distribution. For priors that define ``_support``, this is the smallest axis-aligned box containing the support, except that a ``Product`` reads each coordinate from its marginal, so a marginal that defines no ``_support`` (a ``UserFunction``) spans the whole line. Otherwise, the method reports the whole space. ``VBMC`` checks that the hard bounds lie inside the reported box. Returns ------- lb, ub : tuple(np.ndarray, np.ndarray) A tuple of lower and upper bounds of the support, such that [``lb[i]``, ``ub[i]``] bounds the support of the ``i``\ th marginal. """ if hasattr(self, "_support"): return self._support() else: return np.full(self.D, -np.inf), np.full(self.D, np.inf)
@abstractmethod def __init__(self): """Initialize the distribution.""" self.D = 1 @abstractmethod def _log_pdf(self, x): """Compute the log-pdf of the distribution. This private method is wrapped by ``self.log_pdf()``, which handles input validation and output shape. Parameters ---------- x : np.ndarray The array of input point(s), of dimension `(D,)` or `(n, D)`, where `D` is the distribution dimension. Returns ------- log_pdf : np.ndarray The log-density of the prior at the input point(s), of dimension `(n, 1)`. """ pass
[docs] @abstractmethod def sample(self, n, rng=None): """Sample random variables from the distribution. Parameters ---------- n : int The number of points to sample. rng : None, int, SeedSequence or Generator, optional Random generator or seed; if None a generator is derived from NumPy's global random state. Returns ------- rvs : np.ndarray The samples points, of shape `(n, D)`, where `D` is the dimension. """ pass
@classmethod @abstractmethod def _generic(cls, D=1): """Return a generic instance of the class (used for tests).""" return cls(D=D)