feat(adr-0244): Phase 5d — ADR-0245 §3 acceptance gate (f32 speedup + collision)
Close the two missing §3 acceptance legs. tests/test_adr_0245_acceptance_gate.py: - ≥10× f32 speedup: the Rust core_rs.geometric_product f32 fast path vs the pure Python kernel. Rust-guarded (skips with reason where core_rs is unbuilt); includes a parity sanity check so the speedup can't come from skipped work. Measured on Apple M-series this run: rust 2.86 us/op vs python 1340 us/op = 467× (assert a conservative ≥10× for CI timing robustness). - _content_id collision-resistance: full 256-bit digest (len 64, not 16-hex), type/structure-faithful (int≠str, bool≠int, nested-sensitive, key-order- invariant), fails closed on non-serializable input (no default=str collapse); _psi_digest full + sub-epsilon sensitive + deterministic. - coverage manifest test maps all four §3 legs (parity f64/f32, 0-LAPACK-on- repeat, f32 speedup, collision) to their home test files. The other two legs (parity, 0-LAPACK) were already green; this makes the §3 gate complete and legible. Test-only. Verified: 6 new tests pass (speedup ran with a local core_rs build). Full smoke + fast lane at Phase 5 close.
This commit is contained in:
parent
eacea087b2
commit
b31d6fdee1
1 changed files with 125 additions and 0 deletions
125
tests/test_adr_0245_acceptance_gate.py
Normal file
125
tests/test_adr_0245_acceptance_gate.py
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
"""ADR-0245 §3 — mechanical-sympathy + semantic-rigor acceptance gate.
|
||||
|
||||
The §3 gate has four assertions. Two were already covered before D4:
|
||||
|
||||
* **Parity** (Rust GP bit-identical to Python) — `test_geometric_product_f64_parity.py`
|
||||
(f64, N=10,000 bit-identical) + `test_geometric_product_rust_parity.py` (f32,
|
||||
component-exact).
|
||||
* **0 LAPACK on repeat** (`_cached_eigh` memoization) —
|
||||
`test_adr_0244_mechanical_sympathy.py`.
|
||||
|
||||
This file closes the two that were missing (D4 Phase 5):
|
||||
|
||||
* **≥10× f32 speedup** of the Rust `geometric_product` fast path over the pure
|
||||
Python kernel (Rust-guarded: skips with a reason where `core_rs` is unbuilt).
|
||||
* **`_content_id` collision-resistance** — full 256-bit digest, type-faithful,
|
||||
fail-closed on non-serializable input (no `default=str` collapse).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from core.physics.cognitive_lifecycle import _content_id, _psi_digest
|
||||
|
||||
_HAS_RUST = importlib.util.find_spec("core_rs") is not None
|
||||
|
||||
|
||||
# --- ≥10× f32 speedup (Rust-guarded) --------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.skipif(not _HAS_RUST, reason="core_rs not built (Rust f32 kernel unavailable)")
|
||||
def test_rust_f32_geometric_product_is_at_least_10x():
|
||||
import core_rs
|
||||
|
||||
from algebra.cl41 import geometric_product as py_gp
|
||||
|
||||
rng = np.random.default_rng(0)
|
||||
A = rng.standard_normal(32).astype(np.float32)
|
||||
B = rng.standard_normal(32).astype(np.float32)
|
||||
|
||||
# parity sanity: the speedup must not come from skipping work.
|
||||
r_rust = np.asarray(core_rs.geometric_product(A, B), dtype=np.float64)
|
||||
r_py = np.asarray(py_gp(A, B), dtype=np.float64)
|
||||
np.testing.assert_allclose(r_rust, r_py, atol=1e-5)
|
||||
|
||||
for _ in range(200): # warmup (JIT-free, but stabilizes caches/branch pred.)
|
||||
core_rs.geometric_product(A, B)
|
||||
py_gp(A, B)
|
||||
|
||||
def _best(fn, n):
|
||||
best = float("inf")
|
||||
for _ in range(3):
|
||||
t0 = time.perf_counter()
|
||||
for _ in range(n):
|
||||
fn(A, B)
|
||||
best = min(best, time.perf_counter() - t0)
|
||||
return best
|
||||
|
||||
n = 200
|
||||
t_rust = _best(core_rs.geometric_product, n)
|
||||
t_py = _best(py_gp, n)
|
||||
speedup = t_py / t_rust
|
||||
# Real measured speedup on Apple M-series is ~400-500×; assert a conservative
|
||||
# ≥10× so the gate is robust to CI timing noise while still failing loudly if
|
||||
# the fast path regresses to the Python order of magnitude.
|
||||
assert speedup >= 10.0, f"Rust f32 GP only {speedup:.1f}× faster than Python (< 10×)"
|
||||
|
||||
|
||||
# --- _content_id collision-resistance -------------------------------------
|
||||
|
||||
|
||||
def test_content_id_is_full_256_bit_no_truncation():
|
||||
cid = _content_id({"kind": "x", "n": 1})
|
||||
assert len(cid) == 64 # 256-bit, not a 16-hex (2^32) truncation
|
||||
assert all(c in "0123456789abcdef" for c in cid)
|
||||
|
||||
|
||||
def test_content_id_is_type_and_structure_faithful():
|
||||
# distinct payloads that a weak/str-collapsed digest could conflate:
|
||||
assert _content_id({"a": 1}) != _content_id({"a": "1"}) # int vs str
|
||||
assert _content_id({"a": True}) != _content_id({"a": 1}) # bool vs int
|
||||
assert _content_id({"a": {"b": 1}}) != _content_id({"a": {"b": 2}}) # nested
|
||||
# key order must NOT matter (canonical sort_keys), but content must:
|
||||
assert _content_id({"a": 1, "b": 2}) == _content_id({"b": 2, "a": 1})
|
||||
|
||||
|
||||
def test_content_id_fails_closed_on_non_serializable():
|
||||
# No default=str: a non-serializable payload raises instead of silently
|
||||
# collapsing distinct objects onto one id (ADR-0245 §2.3 semantic rigor).
|
||||
with pytest.raises(TypeError):
|
||||
_content_id({"x": object()})
|
||||
|
||||
|
||||
def test_psi_digest_is_full_and_sensitive():
|
||||
p1 = np.zeros(32, dtype=np.float64)
|
||||
p1[1] = 1.0
|
||||
p2 = np.zeros(32, dtype=np.float64)
|
||||
p2[1] = 1.0 + 1e-12 # a sub-epsilon perturbation must still change the id
|
||||
assert len(_psi_digest(p1)) == 64
|
||||
assert _psi_digest(p1) != _psi_digest(p2)
|
||||
assert _psi_digest(p1) == _psi_digest(p1.copy()) # deterministic
|
||||
|
||||
|
||||
# --- coverage manifest (legibility of the 4-assertion gate) ---------------
|
||||
|
||||
|
||||
def test_acceptance_gate_coverage_manifest():
|
||||
"""The four §3 criteria and where each is proven — a failing import here
|
||||
means an acceptance leg lost its home."""
|
||||
import pathlib
|
||||
|
||||
tests_dir = pathlib.Path(__file__).parent
|
||||
coverage = {
|
||||
"parity_f64": tests_dir / "test_geometric_product_f64_parity.py",
|
||||
"parity_f32": tests_dir / "test_geometric_product_rust_parity.py",
|
||||
"zero_lapack_on_repeat": tests_dir / "test_adr_0244_mechanical_sympathy.py",
|
||||
"f32_speedup_ge_10x": pathlib.Path(__file__),
|
||||
"content_id_collision_resistance": pathlib.Path(__file__),
|
||||
}
|
||||
for name, path in coverage.items():
|
||||
assert path.exists(), f"acceptance leg {name!r} missing its test file {path}"
|
||||
Loading…
Reference in a new issue