docs/calculator-test-patterns: 3 new patterns from π* kernel work + 4 exemplar files
Land the three test-pattern shapes the Explore-agent investigation of fox's overnight π* kernel commits surfaced. Patterns 1-8 in this doc were the original 9-item checklist; patterns 9-11 are new domain-specific contract pins that the π* kernels require but that t3_bound_calculator (the original exemplar) does not. == New patterns added == **§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. Pinned in code-py-ast@v1 (Python → S-expr) and time-series-quantized@v1 (JSON → quantized text). When the output type DOES equal the input type (arithmetic kernel's ℚ → ℚ), use the dual: round-trip idempotence ``canonicalize(canonicalize(x)) == canonicalize(x)``. Both pin a contract; pick by the kernel's type signature. **§10 Dispatch-order pin (Python type-hierarchy gotchas)** Python's ``bool`` subclasses ``int``, so a naive ``isinstance(x, int)`` chain never reaches a bool branch. Kernels distinguishing ``True`` from ``1`` (Python AST normalizers, etc) must check ``bool`` first. Pin the branch order so a "simplify the dispatch" PR fires loud. From ``593550b``. **§11 Tie-breaking-rule pin (banker's rounding)** Python's ``round()`` uses ties-to-even (PEP 3141): 0.5→0, 1.5→2, 2.5→2. Naive switch to ``math.floor(x + 0.5)`` (round-half-up) produces 0.5→1, 1.5→2, 2.5→3 — different output for tie inputs without breaking non-tie tests. From ``2585d3c``. Each pattern has worked-example pseudocode + cross-reference to the actual test file in fox's commit. The pattern numbers extend the existing 1-8 sequence; renumbering would have invalidated prior references. == Checklist updates == Items 10/11/12 added (conditional — only when the kernel's shape exposes the corresponding surface). Many calculator modules (t3 bound, anchor PRG) need only items 1-9. == Exemplar files reorganized == Replaced the single-exemplar reference (t3_bound_calculator only) with a 5-file table cross-referencing the 9-12 checklist items each exemplar covers: test_t3_bound_calculator.py items 1-9 (53 tests) test_pi_star_arithmetic.py items 1-6 + 9 + idempotence (56) test_pi_star_logic.py items 1-6 + 9 + 11 (53) test_pi_star_code.py items 1-6 + 9 + 10 + 11 (32) test_pi_star_time_series.py items 1-6 + 9 + 10 + 12 (35) All five test counts AUTOCOUNT-tagged so future drift fires the regression test landed in ``fc5ba50`` / ``03c0f6a``. Total tagged claims now 49 (was 44; +5). == Source == Patterns surfaced from the Explore-agent investigation of fox's overnight 2026-05-10 commits (``6c9bc04`` arithmetic, ``e7bef5f`` logic, ``593550b`` code, ``2585d3c`` time-series — 176 KATs total across 4 π* canonical-projection kernels). The agent walked each commit, noted the test patterns that didn't appear in the original 9-item checklist, and reported the pattern shapes back. This commit promotes those findings from session memory to architecture-reference docs. Verification: $ pytest tests/test_doc_counts.py -v 3 passed in 3.39s
This commit is contained in:
parent
a786d6d206
commit
f5dbfabed5
1 changed files with 135 additions and 3 deletions
|
|
@ -243,6 +243,106 @@ 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
|
||||
|
|
@ -263,10 +363,42 @@ When opening a new ``bench/scripts/<X>.py`` or substrate primitive:
|
|||
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
|
||||
|
||||
The <!--AUTOCOUNT:tests:tests/test_t3_bound_calculator.py-->53<!--/AUTOCOUNT--> tests fox shipped for ``t3_bound_calculator`` are the
|
||||
exemplar; new modules should aim for similar coverage density on
|
||||
their own surface.
|
||||
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-->53<!--/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.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue