Files
sim-numerics/tests/test_slope_gate.py
T
vvsandClaude Opus 5 2357379c2d Slot 2, the decision place: Chandrupatla's own acceptance band, put inside our step rule as an instrument, does NOT close the gap - CHA 3066 (project) / 2899 (credit) against his 2423, while APS doubles 2806 -> 5432, and the band is not idle (63.6% refusals). The fifth independent test of one conclusion: the remaining gap is the ORDER of the model the step is built on, not the place the similarity is read in. By the declared stop rule (M1 and M2 both false) the 'where is the similarity read' line is closed: all four places measured.
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>
2026-09-21 06:58:59 +01:00

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