bayesmithDocumentation0.10.0 source

bayesmith.dispatch.costs

Source-derived signatures and contracts. See the reference index for coverage and conventions.

Module contract
Read-only cost scoreboard for the split / collapse / joint tradeoff (P5).

This module is the independently useful half of Wave 2: it tells a user what
the canonical coupling ``rho`` and the three strategy condition numbers ARE,
and how much a collapsed sampler would save -- without changing any routing.
``compile(strategy="cost")`` builds it and prints it; the declared plan's
blocks and methods are untouched.

Three hard constraints, each one measured before it was written down:

1. **No elimination is asserted anywhere.** The CG term appears ``tau*m``
   times in the split numerator and once in the collapse/joint term, and it
   is never cancelled.  ``split_cost``, ``collapse_cost`` and ``joint_cost``
   each spell their CG term out independently.
2. **There is no closed-form crossover in ``c``.** ``kappa_marg`` is not a
   function of ``c`` -- measured: ``F_tt = diag(1,100)`` and ``diag(100,1)``
   give true ``kappa(Schur)`` of 5025 and 1.99 at the same ``c=0.99``, while
   ``kappa(F_tt)/(1-c^2)`` says 5025 both times, a 2525x miss.  The crossover
   is numeric-only; nothing here solves for ``c``.
3. **``c_gtheta``, ``c_gc``, ``c_A`` are measured, never modelled.**  The
   grad / (A + A^T) ratio moves in opposite directions with ``n`` on the two
   reference fixtures, so a formula in ``n`` or ``k`` would be a lie.  They
   are inputs (:class:`TimingConstants`), not functions of the graph size.

The ESS trap, also written down because rules rot: **never put Kish ESS and
chain ESS in the same argmin, and never compare a whole-graph row against a
chain row.**  ``plated_radiometer(n=25, kappa=0.4)`` at N=1200: SNIS is 1.40
posterior-sd from the truth, NUTS 18.5, yet NUTS's chain ESS (33) exceeds
the Kish ESS (14) -- an ESS-priced objective picks the answer 13x further
from the truth.  The scoreboard therefore compares COST, never ESS, and each
row carries a :attr:`CostRow.kind` so the two ESS currencies can be kept in
separate comparisons when the collapse arm lands.

Abstention: any ``+inf`` input makes its row ineligible to win; if every row
is ``+inf`` the scoreboard abstains, ``compile()`` falls back to today's
behaviour, and ``str(plan)`` is byte-identical to the declared plan.

**The reconciliation ledger (P7) is the part of this module that stays useful
even if every expression above is wrong.**  :class:`CostReconciliation` records
what was PREDICTED (a row's cost interval), what was MEASURED (seconds per
effective sample), and which of the cost expression's own terms dominated the
prediction -- so a miss can be located at ``c`` (the coupling: a funnel reads
0.0 to a Laplace measurement and the amplification term is then priced off a
number that describes nothing), at ``a`` (the leapfrog constant: a calibration
that did not transfer), or at an interval so wide that satisfying it proved
nothing.  None of that depends on the three cost expressions being right; it
depends only on their inputs having been written down.  Every predicted/measured
pair a run produces is one row of calibration data for a cost model that does
not have any yet.

CONTESTED_BANDWIDTH

Defined in bayesmith.dispatch.costs, line 62.

CONTESTED_BANDWIDTH: float = 0.25

DOMINANCE_SHARE

Defined in bayesmith.dispatch.costs, line 235.

DOMINANCE_SHARE: float = 0.5

TIMING_NOISE_TOLERANCE

Defined in bayesmith.dispatch.costs, line 75.

TIMING_NOISE_TOLERANCE: float = 0.1

K_CG_TOL

Defined in bayesmith.dispatch.costs, line 86.

K_CG_TOL: float = 0.001

CostReconciliation

Defined in bayesmith.dispatch.costs, line 601.

class CostReconciliation
strategy: str
predicted_lo: float
predicted_hi: float
measured: float
seconds: float
ess: float
within: bool
width: float
shares: tuple[tuple[str, float], ...]
dominant: str
rho: float
a: float
kappa_cond: float
kappa_marg: float
kappa_joint: float
kappa_x: float
m: int
fingerprint: str
One run's predicted-versus-measured pair, and what it was made of.

**This is the part of the module that is worth having even if every cost
expression above is wrong.** It does not assert that the prediction was
right; it records the prediction, the measurement, and the inputs both
were computed from -- which is what a cost model needs to be calibrated
against, and what this package has never collected.

Attributes:
    strategy: which row this run is reconciled against -- the row for what
        RAN, not the row the scoreboard would have picked. The scoreboard
        changes no routing, so those are frequently different, and
        reconciling against the winner would compare a measurement of one
        thing with a prediction of another.
    predicted_lo, predicted_hi: the row's cost interval, in seconds per
        effective sample.
    measured: ``seconds / ess`` actually observed, same units.
    seconds, ess: the two numbers ``measured`` was formed from, kept apart
        because a miss caused by a slow wall clock and a miss caused by a
        chain that did not mix are different diagnoses.
    within: whether ``measured`` landed inside the predicted interval.
        **A ``True`` here is only as strong as ``width``**: an interval
        opened wide enough is satisfied by anything.
    width: ``predicted_hi / predicted_lo``. With only the timing noise
        declared as uncertainty this is exactly
        ``(1 + t) / (1 - t) = 1.2222`` at ``t = 0.10``; anything wider came
        from an input the model does not declare, and that is the third
        place a missed prediction can be located.
    shares: each cost term's share of the prediction, in ``("c", "a",
        "cg")`` order -- see :func:`cost_shares`.
    dominant: the term above :data:`DOMINANCE_SHARE`, or ``""``.
    rho, a, kappa_cond, kappa_marg, kappa_joint, kappa_x, m: the inputs,
        recorded verbatim, because "the inputs were written down" is the
        claim this record actually makes.
    fingerprint: the scoreboard's own input fingerprint, so a ledger row
        can be matched to the prediction it came from.

CostReconciliation.line

line(self) -> str
The ledger's one printed line: predicted, measured, attribution.

CostRow

Defined in bayesmith.dispatch.costs, line 261.

class CostRow
strategy: str
kind: Literal['chain', 'whole-graph']
cost_lo: float
cost_hi: float
m: int | None = None
One strategy row: its cost interval, and which ESS currency it owns.

Attributes:
    strategy: ``"split"``, ``"collapse"`` or ``"joint"``.
    kind: ``"chain"`` for the split (its samples come from a Gibbs chain) or
        ``"whole-graph"`` for collapse/joint (a reduced or full graph NUTS,
        whose diagnostic is chain ESS too).  Carried so that a future
        ESS-based comparator keeps Kish ESS and chain ESS in separate
        comparisons -- see the module docstring.
    cost_lo, cost_hi: the cost interval from input uncertainty (timing noise,
        conditioning bound).  ``cost_hi`` is what the argmin reads.
    m: the split's multiplier, ``None`` for rows that have none.
    

LadderInputs

Defined in bayesmith.dispatch.costs, line 328.

class LadderInputs
rho: float
kappa_cond: float
kappa_marg: float
kappa_joint: float
kappa_x: float
a: float
timing: TimingConstants
m: int
The measured inputs one scoreboard is built from (all scalars).

LadderRecord

Defined in bayesmith.dispatch.costs, line 391.

class LadderRecord(eqx.Module)
strategy: str = eqx.field(static=True)
winner: str = eqx.field(static=True)
rho: float = eqx.field(static=True)
kappa_cond: float = eqx.field(static=True)
kappa_marg: float = eqx.field(static=True)
kappa_joint: float = eqx.field(static=True)
cost_split: float = eqx.field(static=True)
cost_collapse: float = eqx.field(static=True)
cost_joint: float = eqx.field(static=True)
contested: tuple[str, ...] = eqx.field(static=True)
abstained: bool = eqx.field(static=True)
fingerprint: str = eqx.field(static=True)
kappa_x: float = eqx.field(static=True)
a: float = eqx.field(static=True)
c_gtheta: float = eqx.field(static=True)
c_gc: float = eqx.field(static=True)
c_A: float = eqx.field(static=True)
c_g_all: float = eqx.field(static=True)
m: int = eqx.field(static=True)
The scalar-only, all-static scoreboard a plan can carry and print.

Every field is ``eqx.field(static=True)`` and holds a scalar, string or tuple
of them -- never a numpy array -- because an array-valued static field makes
a SECOND trace compare array pytree metadata and raise (it passes the first,
    so it is the kind of bug that only shows up in production).
    

LadderRecord.line

line(self) -> str
The scoreboard's one printed line, or ``""`` when it abstained.

Empty on abstention on purpose: :meth:`InferencePlan.__str__` appends it
        under the same guard as the streaming line, so an abstained plan prints
        byte-identically to a declared one.
        

Scoreboard

Defined in bayesmith.dispatch.costs, line 284.

class Scoreboard
winner: CostRow | None
contested: tuple[CostRow, ...]
abstained: bool
The argmin verdict over :class:`CostRow` rows, cost-only, never ESS.

``winner`` is the row with the smallest ``cost_hi``; ``contested`` lists
every other row whose interval overlaps the winner's or whose gap is
within :data:`CONTESTED_BANDWIDTH`; ``abstained`` is set when every
row's ``cost_hi`` is non-finite (``+inf``), in which case there is no
verdict to print and the caller falls back to the declared plan.

TimingConstants

Defined in bayesmith.dispatch.costs, line 165.

class TimingConstants
c_gtheta: float
c_gc: float
c_A: float
c_g_all: float
Measured per-gradient wall-clock reference magnitudes, in seconds.

Measured with ``time.perf_counter`` around the actual operation, never
derived from ``n`` or ``k`` -- the grad / (A + A^T) ratio moves in opposite
directions with ``n``, so a formula would be wrong in one direction or the
other (see the module docstring).  ``c_g_all`` is the full-joint gradient.
    

build_ladder

Defined in bayesmith.dispatch.costs, line 450.

build_ladder(inputs: LadderInputs, *, strategy: Literal['declared', 'cost']='declared') -> LadderRecord | None
Build the printable record, or ``None`` when the strategy is declared.

``strategy="declared"`` means "do not build a scoreboard at all", so the
    returned value is ``None`` and every declared plan stays byte-identical.
    ``strategy="cost"`` builds the three rows, runs the argmin, and returns a
    record whose :meth:`LadderRecord.line` is empty exactly when the
    scoreboard abstained.
    

cg_tol_positive

Defined in bayesmith.dispatch.costs, line 225.

cg_tol_positive(tol: float) -> bool
D95: ``tol > 0.0`` -- the CG tolerance ``k_cg`` prices a solve at.

``log(2 / tol)`` is undefined for ``tol <= 0``, so a non-positive
tolerance cannot name a finite CG iteration count.

collapse_cost

Defined in bayesmith.dispatch.costs, line 138.

collapse_cost(kappa_marg: float, kappa_x: float, a: float, c_gc: float, c_A: float, *, tol_x: float=K_CG_TOL) -> float
``a*sqrt(kappa_marg)*c_gc + k_cg(kappa_x, tol_x)*c_A``.

The collapsed route solves the marginal once per gradient; its CG term is
one ``k_cg`` rather than the split's ``tau*m`` copies.
    

cost_shares

Defined in bayesmith.dispatch.costs, line 527.

cost_shares(inputs: LadderInputs, strategy: str) -> tuple[tuple[str, float], ...]
Each term's share of one row's predicted cost, summing to one.

A partition, not a sensitivity: it says which term the PREDICTION is made
of, which is the question a missed prediction has to start from. The joint
row is all gradient by construction (share 1.0 on ``a``), the collapse row
has no amplification (share 0.0 on ``c``), and only the split row can put
weight on all three.

A non-finite or non-positive total yields all-zero shares rather than a
nan: a row priced at ``+inf`` has no attribution to give, and a nan share
would win no comparison and be skipped by the dominance test in silence.

dominant_input

Defined in bayesmith.dispatch.costs, line 550.

dominant_input(shares: tuple[tuple[str, float], ...]) -> str
The one term above :data:`DOMINANCE_SHARE`, or ``""`` where none is.

``""`` is abstention and is the honest answer whenever two terms could be
tied for largest -- see :data:`DOMINANCE_SHARE`. The caller still has the
shares.

gap_is_contested

Defined in bayesmith.dispatch.costs, line 205.

gap_is_contested(gap: float) -> bool
D93: ``gap < CONTESTED_BANDWIDTH`` -- the argmin contested verdict.

A relative cost gap below the contested bandwidth is not evidence that
the winner would still win on a re-measure, so the scoreboard marks the
row contested instead of a decision.

joint_cost

Defined in bayesmith.dispatch.costs, line 155.

joint_cost(kappa_joint: float, a: float, c_gall: float) -> float
``a*sqrt(kappa_joint)*c_g_all`` -- NUTS over the full joint graph.

No split and no marginal, so no CG term; the whole cost is one leapfrog
gradient against the joint conditioning.
    

k_cg

Defined in bayesmith.dispatch.costs, line 95.

k_cg(kappa: float, tol: float) -> int
``ceil(0.5 * sqrt(kappa) * log(2/tol))`` -- CG iterations for one solve.

The ``1/2 * sqrt(kappa)`` factor is the textbook CG convergence rate; the
``log(2/tol)`` is the number of halvings needed to reach ``tol``.  Rounded
UP because a solve cannot take a fraction of an iteration.
    

reconcile

Defined in bayesmith.dispatch.costs, line 677.

reconcile(record: LadderRecord, strategy: str, *, seconds: float, ess: float) -> CostReconciliation
Build the ledger row for a run against the scoreboard that predicted it.

``ess`` is the posterior's own effective sample size, MIN over every site
and coordinate. A zero makes ``measured`` ``+inf`` rather than raising: a run
that produced no effective samples took infinitely long per one of them,
which is both the true statement and the one that lands outside every
finite interval.

recorded_inputs

Defined in bayesmith.dispatch.costs, line 577.

recorded_inputs(record: LadderRecord) -> LadderInputs
The :class:`LadderInputs` a :class:`LadderRecord` was built from.

Every field round-trips as a scalar, which is why the record can carry
them at all -- see :class:`LadderRecord`.

relative_gap

Defined in bayesmith.dispatch.costs, line 195.

relative_gap(low: float, high: float) -> float
``|high - low| / max(high, low)``, so the gap is a fraction of the row.

Used by both contested tests; spelled once so the two cannot drift.
    

relative_width

Defined in bayesmith.dispatch.costs, line 563.

relative_width(low: float, high: float) -> float
``high / low`` -- how much room a predicted interval left itself.

Reported rather than gated. With only :data:`TIMING_NOISE_TOLERANCE`
declared, every row of :func:`_rows` is linear in the timing constants, so
this is ``(1 + t) / (1 - t)`` exactly and identically on all three rows; a
wider one is evidence that an undeclared uncertainty got in, and a reader
who sees it does not need a threshold to know what to think.

scoreboard

Defined in bayesmith.dispatch.costs, line 299.

scoreboard(rows: tuple[CostRow, ...]) -> Scoreboard
Pick the winner by minimum ``cost_hi``; mark overlapping rows contested.

A ``+inf`` ``cost_hi`` makes a row ineligible to win; if every row is
``+inf`` the scoreboard abstains.  A row is contested when its ``cost_lo``
is below the winner's ``cost_hi`` (the intervals overlap) or its gap to the
winner is within :data:`CONTESTED_BANDWIDTH` of the row cost.

Nothing here reads an ESS.  The winner is a cost winner; whether to trust it
as a sampling decision is a separate question the collapse arm answers.
    

share_is_dominant

Defined in bayesmith.dispatch.costs, line 251.

share_is_dominant(share: float) -> bool
D103: ``share > DOMINANCE_SHARE`` -- this term owns the prediction.

Open at the boundary: a term at exactly one half leaves the rest of the
partition summing to one half too, so it is not uniquely the largest.

split_cost

Defined in bayesmith.dispatch.costs, line 116.

split_cost(kappa_cond: float, kappa_x: float, c: float, a: float, c_gtheta: float, c_A: float, *, tol_x: float=K_CG_TOL, m: int=1) -> float
``tau(c) * [ a*sqrt(kappa_cond)*c_gtheta + m*k_cg(kappa_x, tol_x)*c_A ]``.

``m`` is 1 for the plain GCR split and 3 for the GCR + Metropolis-Hastings
sweep, which re-solves for every proposed draw and its proposal.  The CG
term is spelled out inside the brackets and is NOT cancelled against the
collapse/joint CG terms -- see the module docstring.
    

tau

Defined in bayesmith.dispatch.costs, line 107.

tau(c: float) -> float
``(1 + c^2) / (1 - c^2)`` -- the Gibbs sweep amplification of coupling.

The number of outer sweeps the split route pays per independent sample,
growing without bound as the canonical correlation ``c`` approaches one.
    

timing_noise_in_domain

Defined in bayesmith.dispatch.costs, line 215.

timing_noise_in_domain(tol: float) -> bool
D94: ``tol < 1.0`` -- a timing noise tolerance is a proper fraction.

The spread on the measured timing constants must keep ``1.0 - tol``
positive, or a row ``cost_lo`` would be negative and its cost interval
meaningless.

timing_reference

Defined in bayesmith.dispatch.costs, line 180.

timing_reference() -> TimingConstants
The plan's measured float64 reference, ``mu = theta . B x``, n=100 k=8.

c_gc 43.2us, c_gtheta 5.2us, c_A 6.8us (ratio 8.3).  ``c_g_all`` is the
full-joint gradient, which spans both blocks; the reference pins it to the
collapse gradient plus the theta gradient -- the two gradients a full-joint
step must form -- and is the only entry here that is a composition rather
than a direct single-operation measurement.
    

Type a concept or API name.