bayesmith.dispatch.streaming
Source-derived signatures and contracts. See the reference index for coverage and conventions.
Module contract
Whether a graph's exact block can be executed one epoch at a time.
``evidence/`` answers "fold this campaign"; it does not answer "is this a
campaign, and on which axis". That second question is a dispatch question --
the same shape as "which latents does an exact method apply to" that
:mod:`~bayesmith.dispatch.classify` already answers -- and until this module
existed nothing asked it. The evidence layer was complete, validated against
dense oracles at every level and cross-checked bitwise against rheplicant's
``inference.sqrtinfo``, with **no caller anywhere in this package**.
**Not a new method, and not a new kind.** Measured on the four-epoch scalar
campaign: ``compile()`` already accepts such a graph and plans one joint
``GCR exact`` block over ``{g, n}`` -- the survivor and all E copies of the
per-epoch nuisance at once. The streamed route solves the SAME problem, so
the plan gains a note rather than a branch::
compile()->estimate() g = -0.10616253398516946 (8.8e-15 from oracle)
compress_campaign + prior g = -0.1061625339851607 (5.6e-17 from oracle)
dense marginal oracle g = -0.10616253398516065
What differs is scaling, not the answer: the joint block's solve grows with
``1 + E`` latents, and the fold holds one epoch at a time.
**The route is derived by TRYING, never by guessing.** A plate is an epoch
plate only if the evidence layer's own structural checks accept it, so this
module runs those checks and forwards their refusals verbatim. It deliberately
declines to pick when more than one plate qualifies: which axis indexes
epochs is a modelling fact the graph does not carry, and a dispatcher that
guessed would be right until it silently was not.StreamingRoute
Defined in bayesmith.dispatch.streaming, line 48.
class StreamingRoute(eqx.Module)
plate: str | None = eqx.field(static=True)
survivors: tuple[str, ...] = eqx.field(static=True)
per_epoch: tuple[str, ...] = eqx.field(static=True)
refused: tuple[tuple[str, str], ...] = eqx.field(static=True)Whether a campaign fold applies to this graph, and on which plate.
Attributes:
plate: the epoch plate, or ``None`` when no plate qualifies or more
than one does. ``None`` is never "we could not be bothered" --
:attr:`refused` says which it is.
survivors: latents outside that plate, which a streamed term is over.
``()`` when there is no route.
per_epoch: latents inside it, integrated away within their own epoch.
refused: ``(plate name, why not)`` per rejected candidate, in plate
order, **in the evidence layer's own words**. A graph with no
plates refuses nothing and this is empty -- an absence of
candidates, not a rejection of any.StreamingRoute.available
available(self) -> boolWhether ``compress_campaign(graph, route.plate)`` will run. Both directions are pinned by ``tests/dispatch/test_streaming.py::TestTheRouteDoesNotLie`` against the real call, because a route that reports a plate the fold then refuses is worse than no route at all.
StreamingRoute.line
line(self) -> strThe one line :class:`~bayesmith.dispatch.plan.InferencePlan` prints. Empty when there is nothing to say, so a graph that is not a campaign prints byte-identically to what it printed before this module existed -- which is what keeps the twenty existing assertions on ``str(plan)`` measuring what they were written to measure.
streaming_route
Defined in bayesmith.dispatch.streaming, line 121.
streaming_route(graph: Graph, *, at: dict[str, Any] | None=None) -> StreamingRouteTry every plate, and report which one the evidence layer accepts.
Runs the two STRUCTURAL checks ``epoch_terms`` makes before it folds --
:func:`~bayesmith.marginal.factorize.factorize` (plate membership, then
``epoch_leakage`` to test it) and "exactly one observed node is plated on
this axis". Running only the first would produce a route that reports a
plate ``compress_campaign`` then refuses, which is the failure this
module's own tests are built around.
It does NOT run the fold, so the one refusal it cannot anticipate is
``_refuse_unconstrained_epochs`` -- a rank verdict on the assembled
pivots, which exists only once the arithmetic has been done. That is
stated rather than hidden: :attr:`StreamingRoute.available` promises the
structural checks pass, which is what a plan can honestly record.
Cost is flat in the campaign length -- measured 0.278 s at E=4 and 0.342 s
at E=512, against ``compile()``'s own 1.0-1.3 s -- because the leakage
probe is one ``jacfwd`` of the whole plate, not one per epoch. A graph
with no plates costs nothing: the loop below does not run.
Args:
graph: the model.
at: values the leakage check holds fixed, forwarded to ``factorize``.
Defaults to zeros, which is enough because an affine model's
leakage does not depend on the point, and a model that is not
affine in its per-epoch latents cannot be folded at all.
Returns:
A :class:`StreamingRoute`. Does not raise for a graph the evidence
layer declines OR one it cannot analyse: a refusal is the ANSWER
here, not an error, because ``compile()`` calls this on every graph
and must not start rejecting graphs it accepts today. The two are
recorded distinguishably -- see the ``ValueError`` clause below.