bayesmith.marginal.campaign
Source-derived signatures and contracts. See the reference index for coverage and conventions.
Module contract
A campaign, compressed from the graph, one epoch at a time.
Everything below this module works on matrices a caller assembled:
:func:`~bayesmith.marginal.compress.compress_epoch` takes designs, data and a
covariance and knows nothing about where they came from. This is where they
come from a :class:`~bayesmith.graph.graph.Graph`.
**Derived, not declared twice**, which is the migration spec's whole argument
for rewriting B11 rather than transplanting it. Every input
``compress_epoch`` needs is read off the graph:
=================== ======================================================
what where it comes from
=================== ======================================================
the partition :func:`~bayesmith.marginal.factorize.factorize` --
plate membership, then ``epoch_leakage`` to test it
the designs one ``jacfwd`` of the graph's own prediction map
the constant part that map evaluated at zero
the data the observed node's own ``obs``, sliced by epoch
the covariance :func:`~bayesmith.exact.gaussian.precision_at`, sliced
the nuisance priors the per-epoch latents' own ``dist_fn``
=================== ======================================================
The last row is the one rheplicant cannot have: its ``Factorization`` carries
per-epoch priors separately from the ``ParameterSpace`` that declares them,
and "the same space declared twice" is the error class this kills.epoch_terms
Defined in bayesmith.marginal.campaign, line 47.
epoch_terms(graph: Graph, epoch_plate: str, *, at: dict[str, Any] | None=None, factorization: Factorization | None=None, stacked_only: bool=False) -> list[SqrtInfo]One ``[R | z]`` term per epoch, over the survivors.
Each epoch's own per-epoch latents are integrated out inside it, at the
prior the graph declares for them -- so what comes back is a list of terms
all over the same latents, ready to fold.
Args:
graph: the model. Exactly one observed node, plated on ``epoch_plate``.
epoch_plate: the plate whose axis indexes epochs.
at: values for latents this holds fixed. Defaults to zeros, which is
what an affine model's design does not depend on.
factorization: a partition already derived and checked. ``None``
derives one, **including the leakage check** -- pass one only if
you have already run that.
stacked_only: return the terms as STACKED arrays rather than a list,
so :func:`compress_campaign` can fold them under ``lax.scan``
instead of unpacking E of them only to re-stack. Internal; the
list is the shape a caller wants.
Raises:
StructureError: from :func:`factorize`; or if the graph does not have
exactly one observed node plated on ``epoch_plate``, which is what
makes "one epoch's data" a thing that can be sliced.fold_epochs
Defined in bayesmith.marginal.campaign, line 218.
fold_epochs(designs: jax.Array, targets: jax.Array, offsets: jax.Array, width: int) -> SqrtInfo | tuple[jax.Array, jax.Array, jax.Array]Fold ``E`` stacked ``[R | z]`` terms into one, under ``lax.scan``.
Traceable, and that is the point. The Python loop it replaces costs one
eager QR per epoch and an unrolled ``jit`` of it compiles in time
proportional to ``E`` -- measured at ``E = 1000``, 5.4 s to compile a fold
whose warm run is 2 ms. A scan compiles once whatever ``E`` is.
The carry is the accumulated ``(factor, target, offset)``, and it keeps a
FIXED shape across the whole campaign -- which is why
:meth:`~bayesmith.marginal.sqrtinfo.SqrtInfo.null` is square rather than
zero-row. A carry whose treedef moved would retrace once per epoch and
defeat the exercise.
Args:
designs: ``(E, rows, width)`` stacked factors.
targets: ``(E, rows)`` stacked targets.
offsets: ``(E,)`` stacked constants.
width: the column count, needed statically for the carry's shape.
Returns:
``(factor, target, offset)`` of the folded term.compress_campaign
Defined in bayesmith.marginal.campaign, line 273.
compress_campaign(graph: Graph, epoch_plate: str, *, at: dict[str, Any] | None=None, factorization: Factorization | None=None) -> SqrtInfoFold every epoch into one term over the survivors. Holds one epoch's term at a time, which is the point of the exercise: the raw data and the forward evaluation are gone by the end, and what remains is a sufficient statistic whose size does not grow with the campaign.
epoch_observation
Defined in bayesmith.marginal.campaign, line 296.
epoch_observation(graph: Graph, epoch_plate: str) -> strThe one observed node whose data IS this plate's epochs, or a refusal. PUBLIC because a caller deciding *whether* to fold needs the same structural verdict the fold itself makes, and cannot get it from ``factorize`` alone -- see :func:`~bayesmith.dispatch.streaming.streaming_route`, which was the package's only cross-module private import until this name lost its underscore.