arborist/docs/calculator-test-patterns.md
russell@unturf.com 1d1a942d57
ticket #000036 Tier-2: dav1d Option B (conservative B1 envelope) applied in v1
Per fox: apply the conservative max_envelope B1 model by changing the
v1 calculator's default — NOT by forking a v2. CALCULATOR_VERSION stays
"t3-bound-v1-bottou-refinement" (the descriptor names the unchanged B3
term); b1_model is echoed in the output AND the inputs dict so KAT
replays are unambiguous about which model produced a row.

Calculator (bench/scripts/t3_bound_calculator.py):
- New b1_model kwarg + --b1-model CLI flag, choices:
    max_envelope         (default)  max(fraction_channels, aggregate_bias)
    fraction_channels               g · W · log₂(1 + G/σ)
    aggregate_bias                      W · log₂(1 + g·G/σ)
    effective_control_v1            g · W · log₂(1 + g·G/σ)   (old non-worst-case)
- Default is now max_envelope — genuinely upper-bounding across both
  interpretations of g (dav1d review §3 closure blocker, RESOLVED).
- Every output reports all three concrete B1 variants
  (B1_fraction_channels / B1_aggregate_bias / B1_effective_control_v1),
  b1_selected, and both SNR readings (snr_grad = g·G/σ,
  snr_per_channel = G/σ) regardless of which b1_model was requested.
- model_assumptions[] now carries f"B1_model_{b1_model}".
- inputs echo now includes c_b1/c_b2/c_b3/b1_model (replay-complete).
- Invalid b1_model rejected with a ValueError naming the field.
- Baseline I_window: 625.8716 (effective_control_v1) → 6183.0154
  (max_envelope: B1=aggregate_bias 5849.63 dominates fraction_channels
  1729.72), certification_status NOT_CERTIFIED_BY_BOUND at W=10000.

KAT fixture (bench/fixtures/t3-bound/known-answer-tests.jsonl):
- Regenerated 2026-05-11 — 12 entries: the 8 §7-derived configs under
  the new max_envelope default, a g=0 edge case, plus explicit-mode
  pins for effective_control_v1 / fraction_channels / aggregate_bias.
- Each entry carries b1_model, expected_b1_selected,
  expected_b1_{fraction_channels,aggregate_bias,effective_control_v1},
  expected_snr_per_channel, expected_certification_status.

Tests (tests/test_t3_bound_calculator.py, 75 → 83):
- test_t3_bound_known_answer_tests no longer skips (fixture active);
  pins b1_model, b1_selected, certification_status + numbers, tolerates
  optional new fields on older fixtures.
- New: test_b1_max_envelope_exact_formula, test_invalid_b1_model_rejected,
  test_cli_b1_model_flag (effective_control_v1 / fraction_channels /
  aggregate_bias). test_b1_exact_formula renamed
  test_b1_effective_control_v1_exact_formula and now passes the explicit
  model. Updated baseline / below-256 / CLI tests for the new numbers.

Doc (docs/soft-hash-channel-t3-bound.md):
- Header + §0 + §3.1 + §6 + §7 (worked examples) + §8 (operator
  guidance W-solving) + §10 (closure blockers RESOLVED) + §10.1 +
  §11 (calculator schema) + §12 all updated for the max_envelope
  default. §8: target-256 W drops from ~4196 to ~415 steps under the
  conservative model — the ~10× cost of not assuming which g-reading
  holds; operators who can measure effective-control applies can use
  --b1-model effective_control_v1 for the looser W (a calibration
  claim they must justify, not a default).

Status (#000036 ticket + TICKETS.md): both prior dav1d closure
blockers cleared (B1 worst-case model + active KAT fixture); remaining
= fox's final close-or-iterate call.

AUTOCOUNT markers bumped 75 → 83. Full suite: 2288 passed, 28 skipped.
2026-05-11 07:06:50 -04:00

425 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Calculator / probe test patterns
Architecture reference for testing **calculator-style** Python
modules: pure functions over numeric / cryptographic inputs that
emit JSON-serializable structured output and (often) a CLI surface.
The patterns codified here surfaced from the 2026-05-10 pattern
bench across three modules:
- ``arborist/substrate/anchor_prg.py`` — HMAC-SHA-512 PRG for #000035
- ``bench/scripts/phi_alignment_probe.py`` — Lanczos alignment probe
for #000034 Phase 1a
- ``bench/scripts/t3_bound_calculator.py`` — T3 channel-capacity
closed-form bound for #000036
The exemplar test file is ``tests/test_t3_bound_calculator.py``
(<!--AUTOCOUNT:tests:tests/test_t3_bound_calculator.py-->83<!--/AUTOCOUNT--> cases as of 2026-05-10; fox shipped 51 in the
initial cut and the +2 KAT-fixture-gap closure landed in
``581ad90``). The other two test files
(``tests/test_anchor_prg.py`` and
``tests/test_phi_alignment_probe.py``) were retroactively
backfilled with the same patterns in commits
``de997f7`` and ``a4b3056``.
---
## When to use these patterns
**Apply to**: pure functions over numeric input, math-heavy code,
modules that expose a structured-output dataclass / JSON-emitting
CLI, anything with closed-form formulas the test can hand-compute.
**Don't apply to**: parser / state-machine code (e.g.
``arborist/qa/warrant_resolver.py``), verifier code (e.g.
``arborist/qa/warrant_chain.py``), retrieval code, anything where
inputs and outputs lack a hand-derivable mathematical relationship.
The verifier-style tests in ``tests/test_warrant_resolver.py``
(<!--AUTOCOUNT:tests:tests/test_warrant_resolver.py-->23<!--/AUTOCOUNT--> tests) and ``tests/test_warrant_chain.py`` (<!--AUTOCOUNT:tests:tests/test_warrant_chain.py-->9<!--/AUTOCOUNT--> tests) are
appropriately structured for their domain — descriptive
function-name-per-input-shape works better than parametrized
tables for parsers, and there's no formula to hand-check.
---
## The patterns
### 1. KAT (known-answer-test) regression
Pin a small fixture of (input, expected_output) tuples into
``bench/fixtures/<module>/known-answer-tests.jsonl``. Tests replay
every fixture row against the live module; mismatches fail. Catches
unintentional algorithm changes between releases.
**Examples**:
- ``bench/fixtures/phi-prg/known-answer-tests.jsonl`` (10 vectors)
- ``bench/fixtures/phi-alignment/synthetic-checkpoints.jsonl`` (30 vectors)
**Discipline**: bump the module's ``VERSION`` constant + emit a new
fixture file when the algorithm changes. Old runs replay against
old data; new runs replay against new data.
**Caveat**: KAT regression alone can't catch bugs in the original
implementation that were baked into the fixture. Always pair with
hand-computed formula tests below.
### 2. Hand-computed formula assertion
For closed-form math, compute the expected output from first
principles inside the test file using stdlib (``math``, ``hashlib``)
or numpy. Assert agreement with the function output.
**Why**: catches algorithm drift that KAT regression misses (KAT
could have been generated against a buggy version).
**Example** — fox's ``test_b1_exact_formula`` in
``test_t3_bound_calculator.py``:
```python
def test_b1_exact_formula():
g, gnm, sigma, W = 0.1, 2.0, 0.5, 5000
snr = g * gnm / sigma # 0.4
expected = 1.0 * g * W * math.log2(snr + 1)
r = t3_bound_bits(**{**_BASELINE, "gradient_fraction": g, ...})
assert r["B1_contribution"] == pytest.approx(expected, abs=1e-3)
```
The test computes B1 by hand using the §3 formula from the source
spec, then asserts the function returns the same value. Catches a
sign error or constant drop that no KAT regression would.
**Example** — ``test_phi_prg_first_block_matches_direct_hmac`` in
``test_anchor_prg.py``: directly invokes
``hmac.new(seed, h + counter_bytes, sha512).digest()`` and asserts
the function's first 64 bytes match.
**Example** — ``test_uniform_baseline_matches_analytical_formula``
in ``test_phi_alignment_probe.py``: hand-computes
``(1/dim_d) Σ 1/(λ_j+ε)`` from the §2.1 derivation and asserts
agreement with the function's ``a_uniform`` field.
### 3. Monotonicity in each input axis
For each input the function takes, assert that scaling it
monotonically moves the output in the expected direction.
Holding others fixed.
**Why**: catches sign errors, dropped terms, missed dependencies.
**Examples**:
- T3 calculator: doubling ``window_length`` doubles ``B1`` exactly
(linear); raising ``gradient_fraction`` raises ``I_window``
while leaving ``B2`` and ``B3`` untouched.
- phi_prg: increasing ``dim_h`` from N to N+k yields output whose
first N entries are byte-identical to the dim_h=N output
(prefix-extension / streaming-counter invariant).
- phi_alignment_probe: tighter W concentration on low-λ subspace
produces strictly larger ratio.
**Pattern code**:
```python
def test_monotone_in_X():
base = func(**baseline)
higher_x = func(**{**baseline, "X": baseline["X"] * 2})
assert higher_x["output"] > base["output"]
# Bonus: exact relationship if the formula is linear in X
assert higher_x["output"] == pytest.approx(2 * base["output"], rel=1e-6)
```
### 4. Closure / sum-of-parts invariants
If the function returns a structured output with multiple
contributions, assert the contributions sum / compose to the
total. No missing terms, no double-counting.
**Examples**:
- T3: ``I_window ≡ B1 + B2 + B3`` (test_total_equals_sum_of_three_contributions)
- phi_alignment_probe: when ``k_top + k_bot = dim_d`` (full
spectrum), ``a_top + a_bot`` must equal full-spectrum A from
dense ``numpy.linalg.eigh`` decomposition
(test_full_spectrum_a_top_plus_a_bot_covers_full_isotropic_baseline)
- phi_alignment_probe: top-k eigenvalue min ≥ bot-k eigenvalue
max (no overlap; test_eigenvalue_ordering_top_dominates_bot)
**Why**: these are mechanical sanity invariants. A pure
implementation passes them automatically; a buggy implementation
that drops a term, double-counts, or has overlapping ranges fails
loudly.
### 5. Parametrized invalid-input cones
Use ``@pytest.mark.parametrize`` to spawn one test function per
invalid-input class.
**Why**: cleaner than N separate ``test_rejects_X`` /
``test_rejects_Y`` functions for the same validation surface.
**Pattern**:
```python
@pytest.mark.parametrize("g", [0.0, -0.1, 1.5, 2.0])
def test_invalid_gradient_fraction_rejected(g):
args = {**_BASELINE, "gradient_fraction": g}
with pytest.raises(ValueError, match="gradient_fraction"):
func(**args)
```
**Don't over-parametrize**: if each case has a unique error
message that the test asserts specifically, separate functions
remain readable. The cone form works best when the assertion
shape is identical across cases.
### 6. CLI subprocess end-to-end
For modules that expose a CLI surface, test ``argparse + main()``
via ``subprocess.run``. Parse the JSON output; check exit codes
on validation errors.
**Why**: import-only tests miss argparse + main() drift. Refactors
that break the CLI surface (rename flag, change exit code) won't
surface until someone runs the command. fox's
``test_cli_baseline_runs_clean`` and ``test_cli_invalid_input_exits_2``
catch exactly this.
**Example**:
```python
def test_cli_baseline_runs_clean(tmp_path):
cmd = [
sys.executable, "-m", "bench.scripts.t3_bound_calculator",
"--gradient-fraction", "0.05", ...
]
out = subprocess.run(cmd, capture_output=True, text=True,
check=True, cwd="/home/fox/git/arborist")
j = json.loads(out.stdout)
assert j["calculator_version"] == CALCULATOR_VERSION
assert j["I_window_bits_upper_bound"] == pytest.approx(625.87, abs=0.05)
```
**Caveat**: the ``cwd`` argument matters — invoking
``python -m bench.scripts.<X>`` requires the project root as cwd
so the package import resolves.
### 7. Doc parity (and what fox's pattern caught)
Test that the function's actual output matches the numbers in the
spec doc's worked-example sections. Catches **doc drift** —
when a doc was hand-written before the implementation crystallized
and approximate rounding got baked into prose.
**Example finding (2026-05-10)**: fox's
``test_baseline_matches_section_11_doc`` flagged that
``soft-hash-channel-t3-bound.md §11`` said 622.7 / 290.0 / 32.7
bits but the calculator produced 625.87 / 292.48 / 33.39. The
test's docstring (lines 56-61) explicitly tracks the
calibration-pass follow-up. Refresh landed in commit ``de997f7``.
**Why**: docs go stale faster than code. A test that pins the
spec-quoted numbers fails noisily when the doc drifts; the prose
in the doc gets refreshed (or the function gets fixed, depending
on which side has the bug).
### 8. Module-export shape
Test the public exports: version constants, threshold constants,
dataclass round-trip via ``asdict``, JSON-serializability.
**Example** — fox's ``test_returns_calculator_version_token``:
```python
def test_returns_calculator_version_token():
r = func(**_BASELINE)
assert r["calculator_version"] == CALCULATOR_VERSION
assert "v1" in CALCULATOR_VERSION
```
The "v1" assertion encodes the discipline: when the algorithm
changes, the version string MUST change too. A future contributor
who refactors the algorithm without bumping ``CALCULATOR_VERSION``
gets caught here.
**Example** — ``test_alignment_report_round_trips_via_asdict``:
asserts ``asdict(report)`` is JSON-serializable. Catches
non-stdlib types leaking into the dataclass that would break
downstream consumers.
### 9. Projective-contract pin (one-way canonicalizers)
For canonicalizers whose output type ≠ input type — output is
**not** in the input domain by design — assert that re-applying
the function raises. Catches a future "make it round-trippable"
PR that silently breaks the projection.
**Why**: the contract is "one-way projection," not "fixed-point
normalization." If the output were re-canonicalizable, downstream
code might come to depend on idempotence; the test pins the
boundary.
**Examples** —
- ``arborist/pi_star/code_py_ast.py`` (commit ``593550b``):
Python source → S-expression. Re-canonicalizing the
S-expression text raises ``PiStarError`` because it isn't
valid Python source.
- ``arborist/pi_star/time_series_quantized.py`` (commit
``2585d3c``): JSON ``{dt, dv, samples}`` → quantized text
``dt=...;dv=...;n=...;t0=...:v0|v1|...``. The quantized text
isn't JSON; re-canonicalizing raises.
**Pattern code**::
def test_output_is_projective_not_invertible():
canonical = canonicalize(input_value)
with pytest.raises(PiStarError):
canonicalize(canonical)
When the output type **does** equal the input type (e.g. the
arithmetic kernel's rationals), use the dual pattern —
**round-trip idempotence**: ``canonicalize(canonicalize(x)) ==
canonicalize(x)``. Both patterns pin a contract; pick whichever
matches the kernel's type signature.
### 10. Dispatch-order pin (Python type-hierarchy gotchas)
For functions branching on ``isinstance`` over numeric types,
pin the branch order. ``bool`` subclasses ``int`` in Python, so
this naive chain never reaches the bool branch::
if isinstance(x, int): # True is also an int
...
elif isinstance(x, bool): # unreachable
...
A kernel that distinguishes ``True`` from ``1`` (e.g. a Python
AST normalizer) must check ``bool`` first. Pin the branch order
in a test so a future "simplify the dispatch" PR fires loud.
**Why**: subtle Python type-hierarchy gotcha that no other test
shape catches. Determinism + KAT tests pass either way at the
boundary case ``True``; only an explicit dispatch-order pin
exposes the regression.
**Example** — ``arborist/pi_star/code_py_ast.py`` (commit
``593550b``)::
def test_bool_dispatched_before_int():
assert canonicalize("True") != canonicalize("1")
# Both are int-ish; bool-first dispatch keeps them distinct.
**How to apply**: any kernel that branches on ``isinstance`` for
numeric types should have a paired test pinning the branch order.
Same applies to ``Decimal`` vs ``float``, ``bytes`` vs ``bytearray``,
``int`` vs ``numpy.int64`` — anywhere Python's MRO crosses a
distinguishing boundary.
### 11. Tie-breaking-rule pin (ties-to-even / banker's rounding)
For functions using ``round()``, pin the tie-breaking direction.
Python's ``round()`` uses **banker's rounding** by default
(ties-to-even, PEP 3141): 0.5→0, 1.5→2, 2.5→2. A naive switch to
``math.floor(x + 0.5)`` ("round half up": 0.5→1, 1.5→2, 2.5→3)
silently changes output for tie inputs without breaking
non-tie tests.
**Why**: many programmers expect round-half-up; a contributor
unfamiliar with PEP 3141 will refactor to ``floor(x + 0.5)``
"to be explicit" and silently corrupt quantization. Pin the rule
with the actual tie inputs.
**Example** — ``arborist/pi_star/time_series_quantized.py``
(commit ``2585d3c``)::
@pytest.mark.parametrize("v_in,v_out", [
(0.5, 0), # ties-to-even: 0 is even
(1.5, 2), # ties-to-even: 2 is even
(2.5, 2), # ties-to-even: 2 is even (NOT 3)
(3.5, 4), # ties-to-even: 4 is even
])
def test_quantization_uses_bankers_rounding(v_in, v_out):
assert quantize_value(v_in, dv=1) == v_out
**How to apply**: any kernel using ``round()`` or any quantization
that sits on a discrete grid should have a parametrized test
covering at least 4 ties (mix of round-up-to-even and
round-down-to-even cases).
---
## Checklist for new calculator-style code
When opening a new ``bench/scripts/<X>.py`` or substrate primitive:
1. [ ] KAT fixture under ``bench/fixtures/<module>/`` (≥ 5 cases)
2. [ ] Module-level ``VERSION`` constant; tests assert its
presence + the ``v1`` discipline
3. [ ] Hand-computed formula tests — at least one per
independent contribution / output field
4. [ ] Monotonicity test per input axis (or invariance assertion
if the function is meant to be insensitive to that axis)
5. [ ] Closure / sum-of-parts invariant if the output decomposes
6. [ ] Parametrized invalid-input cone (one ``@parametrize`` per
validation class — wrong dim, out-of-range value, wrong type)
7. [ ] CLI subprocess test if the module exposes a CLI
8. [ ] Doc-parity test if the spec doc has worked-example numbers
9. [ ] Module-export shape test (asdict round-trip,
JSON-serializability, version token presence)
10. [ ] Projective-contract pin if output type ≠ input type
(re-canonicalizing the canonical output raises) — OR
round-trip idempotence pin if output type = input type
11. [ ] Dispatch-order pin if the kernel branches on
``isinstance`` for numeric types (bool-before-int, etc)
12. [ ] Tie-breaking-rule pin if the kernel uses ``round()`` or
sits on a discrete quantization grid
Items 10/11/12 are conditional — only apply when the kernel's
shape exposes the corresponding surface. Many calculator modules
(e.g. T3 bound, anchor PRG) need only items 1-9.
Exemplar test files:
- ``tests/test_t3_bound_calculator.py`` —
<!--AUTOCOUNT:tests:tests/test_t3_bound_calculator.py-->83<!--/AUTOCOUNT--> tests covering
items 1-9 for the T3 closed-form bound.
- ``tests/test_pi_star_arithmetic.py`` —
<!--AUTOCOUNT:tests:tests/test_pi_star_arithmetic.py-->56<!--/AUTOCOUNT--> tests covering items
1-6 + 9 + round-trip idempotence (output type = input type =
rationals). From ``6c9bc04`` 2026-05-10.
- ``tests/test_pi_star_logic.py`` —
<!--AUTOCOUNT:tests:tests/test_pi_star_logic.py-->53<!--/AUTOCOUNT--> tests covering items
1-6 + 9 + 11 (tie-breaking on multi-tautology equivalence-class
collapse). From ``e7bef5f`` 2026-05-10.
- ``tests/test_pi_star_code.py`` —
<!--AUTOCOUNT:tests:tests/test_pi_star_code.py-->32<!--/AUTOCOUNT--> tests covering items
1-6 + 9 + 10 (projective: Python → S-expr) + 11 (dispatch:
bool-before-int). From ``593550b`` 2026-05-10.
- ``tests/test_pi_star_time_series.py`` —
<!--AUTOCOUNT:tests:tests/test_pi_star_time_series.py-->35<!--/AUTOCOUNT--> tests covering
items 1-6 + 9 + 10 (projective: JSON → quantized text) + 12
(banker's rounding pin). From ``2585d3c`` 2026-05-10.
New modules should aim for similar coverage density on their
own surface.
---
## What this doc is NOT
- A general testing tutorial. Pytest mechanics + fixture scoping
+ ``tmp_path`` use are documented in pytest's own docs.
- A code-style guide. Ruff / black / type-hints discipline lives
in CLAUDE.md.
- A list of every pattern in the repo. Verifier tests, parser
tests, retrieval tests have their own structures appropriate
to their domains; this doc is calculator-style only.
- A coverage inventory. The per-discipline test-file index lives
at `docs/warrant-substrate-cookbook.md` § "Appendix —
test-coverage cross-reference". This doc is the **checklist
for new tests**; the cookbook appendix is the **index of
existing tests** keyed by substrate discipline. Walk both:
this doc designs the test file, the cookbook appendix records
it under the matching discipline.
The "When to use" caveat at the top is the load-bearing line:
**don't retrofit these patterns onto verifier-style tests**.
Calculator code has math; verifier code has state machines.
Different shapes need different test patterns.