Files
sim-numerics/tests/test_prestep_gate.py

94 lines
4.1 KiB
Python

"""Implementation gates R1-R4 for the pre-step gate (PREREG_prestep_gate_2026-09-21.md).
The measured cause of our cost is that the safeguard REPAIRS 77.5 % of evaluations after
the fact, while Chandrupatla REFUSES its model for free on 72 % of steps and halves
instead. The gate under test asks, BEFORE spending an evaluation, whether the similarity
step promises at least what bisection would remove — a comparison of two lengths already
in hand, no threshold and no new constant.
"""
from __future__ import annotations
import inspect
import math
import re
import pytest
from simroot import pure as P
PROBLEMS = [
(lambda t: t ** 3 - 2.0 * t - 5.0, 2.0, 3.0),
(lambda t: math.exp(t) - 5.0, 0.0, 3.0),
(lambda t: t ** 2 - 2.0, 0.5, 2.0),
(lambda t: math.cos(t) - t, 0.0, 1.0),
(lambda t: (t - 1.0) ** 3, -1.0, 4.0),
]
def test_R1_the_published_path_is_bit_identical_with_the_gate_off():
for f, a, b in PROBLEMS:
for guard in ("credit", "project", "safe", "step"):
base = P.pure_root(f, a, b, 1e-12, 300, similarity="ratio",
scale="derived3", guard=guard)
off = P.pure_root(f, a, b, 1e-12, 300, similarity="ratio",
scale="derived3", guard=guard, prestep="none")
assert base == off, (guard, base, off)
def test_R2_the_gate_carries_no_chosen_constant():
src = inspect.getsource(P.pure_root)
# anchor on the CONDITION, not on a whole line: the line grew a `step is None and`
# prefix when the slope gate was inserted ahead of it, and the exact-line anchor broke
start = src.index('prestep == "progress"')
end = src.index('if step is None and space ==', start) # the gate block only
body = src[start:end]
# comments carry the MEASURED numbers that justify the gate (77.5 %, 2.310 halvings);
# they are documentation, not constants, so the check reads the code only
code = "\n".join(line.split("#", 1)[0] for line in body.splitlines())
literals = set(re.findall(r"(?<![\w.])\d+\.\d+(?![\w.])", code))
# 0.5 is the definition of bisection, 1.0 comes from S in [0,1]
assert literals <= {"0.0", "0.5", "1.0"}, f"chosen constants in the gate: {literals}"
def test_R3_a_refusal_costs_no_extra_evaluation_PER_STEP():
"""R3 as the prereg states it: a refusal REPLACES the step, so evaluations never exceed
steps + the two bracket probes.
NOT claimed (and measured false on 2026-09-21): that the whole RUN cannot become more
expensive. Forcing bisection changes the trajectory — it removes more bracket but less
error than a good secant step — and one probe problem went 25 -> 29 evaluations. Whether
that trade pays over the two benchmark sets is exactly what P2/P3 measure; a unit test
must not pre-judge it."""
for f, a, b in PROBLEMS:
st = {}
_x, n, ok = P.pure_root(f, a, b, 1e-12, 300, similarity="ratio", scale="derived3",
guard="credit", prestep="progress", stats=st)
assert ok
# every evaluation is one step (the gate spends the same probe on the midpoint),
# plus f(a) and f(b)
assert st["pre_refused"] <= n - 2, (st, n)
def test_R4_refusals_are_counted_separately_from_repairs():
st = {}
f = PROBLEMS[4][0]
x, n, ok = P.pure_root(f, -1.0, 4.0, 1e-10, 300, similarity="ratio", scale="derived3",
guard="credit", prestep="progress", stats=st)
assert ok and abs(x - 1.0) < 1e-3
assert set(("forced", "pre_refused")) <= set(st)
assert st["pre_refused"] >= 1 # a cubic with a triple root refuses often
assert isinstance(st["forced"], int) and isinstance(st["pre_refused"], int)
def test_the_gate_is_validated_not_silently_ignored():
with pytest.raises(ValueError):
P.pure_root(lambda t: t, -1.0, 1.0, 1e-10, 100, prestep="whatever")
def test_the_gate_still_solves_every_probe_problem():
for f, a, b in PROBLEMS:
x, n, ok = P.pure_root(f, a, b, 1e-12, 300, similarity="ratio", scale="derived3",
guard="credit", prestep="progress")
assert ok and abs(f(x)) < 1e-6, (a, b, x, n, ok)