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
|
non-stdlib types leaking into the dataclass that would break
|
||||||
downstream consumers.
|
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
|
## 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
|
8. [ ] Doc-parity test if the spec doc has worked-example numbers
|
||||||
9. [ ] Module-export shape test (asdict round-trip,
|
9. [ ] Module-export shape test (asdict round-trip,
|
||||||
JSON-serializability, version token presence)
|
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
|
Items 10/11/12 are conditional — only apply when the kernel's
|
||||||
exemplar; new modules should aim for similar coverage density on
|
shape exposes the corresponding surface. Many calculator modules
|
||||||
their own surface.
|
(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