pre-step gate: prestep='none'|'progress' (default bit-identical), refuse the similarity step BEFORE spending an evaluation when it promises less progress than bisection — two lengths already in hand, no new constant; R1-R4 pinned by tests/test_prestep_gate.py (6 green); the gate grid writes to its OWN sweep_gate.json
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
"""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)
|
||||
start = src.index('if 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)
|
||||
Reference in New Issue
Block a user