Not empty-handed: our own parameter-free reading 'defect' - refuse when |phi - xi| > min(xi, 1 - xi) - sets a new canonical best, APS 2523 at 153/154 (previous 2527, Chandrupatla 2785), guarantee intact (0 of 135 family cells), refusing on 7.0% of steps. It is a second point on the same trade, not a replacement: 5733 over both sets against 5511, because CHA gives back 3210. Also: _shape_ratios crashed on a missing third point, which evaluate would have turned into a silent sweep of zeros; the slope-gate pin was anchored on line spelling; and the server copy had drifted (stale relax/sor.py, test_relaxation.py absent - 746 tests instead of 755). All synced, 92/92 files identical by content, 755 pass on the server. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
100 lines
4.4 KiB
Python
100 lines
4.4 KiB
Python
"""Gates T1-T5 for the slope-stability gate (PREREG_slope_gate_2026-09-21.md).
|
|
|
|
The progress gate refused on the wrong currency (bracket progress) and charged APS 4449
|
|
against 2806. This gate refuses on the SHAPE of the model instead: two consecutive slope
|
|
estimates of opposite sign mean the local model is not established, so the evaluation is
|
|
spent halving. A SIGN is compared — no threshold, no factor, no constant.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import inspect
|
|
import math
|
|
import re
|
|
|
|
import pytest
|
|
|
|
from simroot import pure as P
|
|
|
|
SMOOTH = [ # simple roots, stable slope sign: the gate must be inert
|
|
(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),
|
|
]
|
|
# the bracket must straddle zero AND contain a sign change of the SLOPE: f' = 3t^2 - 1
|
|
# vanishes at t = +-0.577, so the local slope estimate flips sign inside (-2, 2).
|
|
# (An earlier version used sin(8t)e^-t + 0.05 on (0,1), which does not straddle zero at
|
|
# all — f(0) = +0.05, f(1) = +0.41 — and the method rightly refused the call.)
|
|
WOBBLY = (lambda t: t ** 3 - t + 0.2, -2.0, 2.0)
|
|
|
|
|
|
def test_T1_the_published_path_is_bit_identical_with_the_gate_off():
|
|
for f, a, b in SMOOTH:
|
|
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_T2_the_gate_carries_no_constant():
|
|
src = inspect.getsource(P.pure_root)
|
|
# anchor on the CONDITION, never on the whole line: on 2026-09-21 the slot-2 readings
|
|
# were inserted ahead of this gate and turned the line into
|
|
# `if step is None and prestep == "slope":`, which broke a pin that was testing
|
|
# something else entirely. A pin about constants must not depend on line spelling.
|
|
start = src.index('prestep == "slope":')
|
|
end = src.index('prestep == "progress"', start)
|
|
code = "\n".join(line.split("#", 1)[0] for line in src[start:end].splitlines())
|
|
literals = set(re.findall(r"(?<![\w.])\d+\.\d+(?![\w.])", code))
|
|
assert literals <= {"0.0", "0.5"}, f"constants in the gate: {literals}"
|
|
assert ">" in code and "!=" in code # a SIGN comparison, not a magnitude test
|
|
|
|
|
|
def test_T3_the_gate_is_inert_where_the_slope_sign_is_stable():
|
|
"""This is what the progress gate could not do — it fired on excellent steps."""
|
|
for f, a, b in SMOOTH:
|
|
off = P.pure_root(f, a, b, 1e-12, 300, similarity="ratio", scale="derived3",
|
|
guard="credit")
|
|
on = P.pure_root(f, a, b, 1e-12, 300, similarity="ratio", scale="derived3",
|
|
guard="credit", prestep="slope")
|
|
assert on[1] == off[1], (off, on) # identical evaluation count
|
|
|
|
|
|
def test_T4_the_gate_fires_where_the_slope_sign_flips():
|
|
f, a, b = WOBBLY
|
|
st = {}
|
|
x, n, ok = P.pure_root(f, a, b, 1e-10, 300, similarity="ratio", scale="derived3",
|
|
guard="credit", prestep="slope", stats=st)
|
|
assert ok and abs(f(x)) < 1e-6
|
|
assert st["pre_refused"] >= 1, st
|
|
|
|
|
|
def test_T5_a_refusal_costs_no_extra_evaluation():
|
|
f, a, b = WOBBLY
|
|
st = {}
|
|
_x, n, ok = P.pure_root(f, a, b, 1e-10, 300, similarity="ratio", scale="derived3",
|
|
guard="credit", prestep="slope", stats=st)
|
|
assert ok
|
|
assert st["pre_refused"] <= n - 2, (st, n)
|
|
|
|
|
|
def test_the_mode_needs_a_slope_to_judge():
|
|
with pytest.raises(ValueError):
|
|
P.pure_root(lambda t: t, -1.0, 1.0, 1e-10, 100, scale="bracket", prestep="slope")
|
|
with pytest.raises(ValueError):
|
|
P.pure_root(lambda t: t, -1.0, 1.0, 1e-10, 100, prestep="nonsense")
|
|
|
|
|
|
def test_both_gates_remain_available_and_distinct():
|
|
""""progress" is kept as a MEASURED NEGATIVE (APS 4449 vs 2806), not deleted."""
|
|
f, a, b = WOBBLY
|
|
a1 = P.pure_root(f, a, b, 1e-10, 300, similarity="ratio", scale="derived3",
|
|
guard="credit", prestep="progress")
|
|
a2 = P.pure_root(f, a, b, 1e-10, 300, similarity="ratio", scale="derived3",
|
|
guard="credit", prestep="slope")
|
|
assert a1[2] and a2[2]
|
|
assert a1 != a2 or a1[1] == a2[1] # they may coincide, but both must run
|