Source code for mftt.single_fidelity.reference
"""Define reference-density adapters used by transport maps."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Callable
import numpy as np
from scipy.stats import multivariate_normal
[docs]
@dataclass
class Reference:
"""Wrap reference log-density and score evaluations.
Parameters
----------
logpdf_fn : callable
Function accepting a batch with shape ``(n_samples, input_dim)`` and
returning one log-density value per row.
score_fn : callable, optional
Function on the same batch returning the gradient of the log density
with shape ``(n_samples, input_dim)``. It is required for analytic AAO
gradients.
"""
logpdf_fn: Callable[[np.ndarray], np.ndarray]
score_fn: Callable[[np.ndarray], np.ndarray] | None = None
[docs]
@classmethod
def standard_normal(cls, input_dim: int) -> "Reference":
"""Build a standard normal reference.
Parameters
----------
input_dim : int
Dimension of the reference coordinates.
Returns
-------
Reference
Standard normal reference with identity covariance.
"""
def logpdf(x: np.ndarray) -> np.ndarray:
x = np.asarray(x, dtype=float)
return -0.5 * np.sum(x * x, axis=1) - 0.5 * input_dim * np.log(2.0 * np.pi)
def score(x: np.ndarray) -> np.ndarray:
return -np.asarray(x, dtype=float)
return cls(logpdf_fn=logpdf, score_fn=score)
[docs]
@classmethod
def from_scipy(cls, executable) -> "Reference":
"""Build a reference wrapper from a frozen SciPy distribution.
Parameters
----------
executable : scipy.stats frozen distribution
Distribution exposing ``logpdf``. A distribution exposing ``mean``
and ``cov`` also receives an analytic score function.
Returns
-------
Reference
Adapter using the distribution's log density and, when available,
its Gaussian score.
Raises
------
TypeError
If the unfrozen SciPy ``multivariate_normal`` constructor is used.
"""
if isinstance(executable, multivariate_normal.__class__):
raise TypeError("Pass a frozen scipy distribution instance, not the multivariate_normal constructor.")
if hasattr(executable, "mean") and hasattr(executable, "cov"):
mean = np.asarray(executable.mean, dtype=float)
cov = np.asarray(executable.cov, dtype=float)
cov_inv = np.linalg.inv(cov)
def score(x: np.ndarray) -> np.ndarray:
delta = np.asarray(x, dtype=float) - mean
return -(delta @ cov_inv.T)
return cls(logpdf_fn=executable.logpdf, score_fn=score)
return cls(logpdf_fn=executable.logpdf)
[docs]
def evaluate_logpdf(self, x: np.ndarray) -> np.ndarray:
"""Evaluate the reference log-density on a batch of reference samples.
Parameters
----------
x : array-like, shape (n_samples, input_dim)
Reference-space coordinates.
Returns
-------
numpy.ndarray, shape (n_samples,)
Log-density values.
"""
return np.asarray(self.logpdf_fn(np.asarray(x, dtype=float)), dtype=float)
[docs]
def evaluate_score(self, x: np.ndarray) -> np.ndarray:
"""Evaluate the reference score on a batch of reference samples.
Parameters
----------
x : array-like, shape (n_samples, input_dim)
Reference-space coordinates.
Returns
-------
numpy.ndarray, shape (n_samples, input_dim)
Gradients of the reference log density.
Raises
------
ValueError
If this reference was created without a score function.
"""
if self.score_fn is None:
raise ValueError("Reference score function is required for analytic-gradient AAO training.")
return np.asarray(self.score_fn(np.asarray(x, dtype=float)), dtype=float)