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.25DOMINANCE_SHARE
Defined in bayesmith.dispatch.costs, line 235.
DOMINANCE_SHARE: float = 0.5TIMING_NOISE_TOLERANCE
Defined in bayesmith.dispatch.costs, line 75.
TIMING_NOISE_TOLERANCE: float = 0.1K_CG_TOL
Defined in bayesmith.dispatch.costs, line 86.
K_CG_TOL: float = 0.001CostReconciliation
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: strOne 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) -> strThe 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 = NoneOne 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: intThe 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) -> strThe 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: boolThe 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: floatMeasured 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 | NoneBuild 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) -> boolD95: ``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], ...]) -> strThe 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) -> boolD93: ``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) -> CostReconciliationBuild 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) -> LadderInputsThe :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, ...]) -> ScoreboardPick 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) -> boolD103: ``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) -> boolD94: ``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() -> TimingConstantsThe 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.