Source code for sequince.flows
"""Flow interfaces used by Sequince transforms and proposals."""
from __future__ import annotations
import copy
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any, Protocol
from .array_api import asarray
from .typing import Array
if TYPE_CHECKING:
from .steps.context import StepContext
[docs]
class NormalizingFlow(Protocol):
"""Minimal interface for normalizing flows used by Sequince."""
xp: Any
self,
particles: Array,
context: StepContext | None = None,
*,
rng: Any = None,
**kwargs: Any,
) -> "NormalizingFlow": ...
[docs]
def forward(
self, particles: Array, context: StepContext | None = None
) -> tuple[Array, Array]:
"""Map particles into a transformed space and return log-Jacobians."""
...
[docs]
def inverse(
self, particles: Array, context: StepContext | None = None
) -> tuple[Array, Array]:
"""Undo the transform applied by :meth:`forward`."""
...
[docs]
def log_prob(
self, particles: Array, context: StepContext | None = None
) -> Array:
"""Evaluate the flow log-density in the original particle space."""
...
[docs]
def sample(
self,
num_samples: int = 1,
context: StepContext | None = None,
*,
rng: Any = None,
) -> Array:
"""Draw samples from the flow distribution."""
...
[docs]
def flow_asarray(flow: NormalizingFlow, array: Any):
"""Cast ``array`` to the backend array type expected by ``flow``."""
return asarray(array, xp=flow.xp)
[docs]
def flow_state_dict(flow: NormalizingFlow) -> dict[str, Any]:
"""Return persistent flow state when the adapter exposes it."""
state_fn = getattr(flow, "state_dict", None)
if state_fn is None:
return {}
state = state_fn()
if not isinstance(state, Mapping):
raise TypeError("Flow state_dict() must return a mapping.")
return copy.deepcopy(dict(state))
[docs]
def load_flow_state_dict(
flow: NormalizingFlow, state: Mapping[str, Any]
) -> None:
"""Restore persistent flow state when supported by the adapter."""
load_fn = getattr(flow, "load_state_dict", None)
if load_fn is None:
if state:
raise ValueError(
f"{type(flow).__name__} does not support checkpoint state."
)
return
load_fn(copy.deepcopy(dict(state)))
[docs]
def clone_flow(flow: NormalizingFlow) -> NormalizingFlow:
"""Return a fresh copy of ``flow``.
External flow adapters can expose ``_init_args`` to control how the clone is
reconstructed; otherwise ``deepcopy`` is used as a fallback.
"""
init_args = getattr(flow, "_init_args", None)
if isinstance(init_args, dict):
cloned_args = copy.deepcopy(dict(init_args))
extra_kwargs = cloned_args.pop("kwargs", None)
if extra_kwargs is not None:
if not isinstance(extra_kwargs, dict):
raise TypeError(
"Expected flow._init_args['kwargs'] to be a mapping."
)
cloned_args = {**extra_kwargs, **cloned_args}
return flow.__class__(**cloned_args)
return copy.deepcopy(flow)