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:
vvs
2026-09-20 23:12:34 +01:00
co-authored by Claude Opus 5
parent be10fc0b22
commit 0617243ed4
5 changed files with 215 additions and 10 deletions
+25 -6
View File
@@ -413,7 +413,7 @@ def _local_order(x2, f2, x1, f1, x0, f0):
def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
guard="step", stats=None, budget_factor=2.0,
budget_extra=None, endowment=1.0, space="x"):
budget_extra=None, endowment=1.0, space="x", prestep="none"):
"""The pure similarity root finder.
similarity : key into PURE_SIMS
@@ -467,6 +467,8 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
raise ValueError(f"unknown scale {scale!r}")
if space not in SPACES:
raise ValueError(f"unknown space {space!r}")
if prestep not in ("none", "progress"):
raise ValueError(f"unknown prestep {prestep!r}")
if space == "log" and scale not in ("derived", "derived3"):
# u-space needs a slope; with a chosen or bracket scale there is none to
# transform, and computing something anyway would hide that (PREREG §3a).
@@ -494,8 +496,10 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
sg = _Safeguard(guard, abs(B - A), xtol, budget_factor, budget_extra)
sg.credit = endowment
forced = 0
pre_refused = 0
if stats is not None:
stats['forced'] = 0
stats['pre_refused'] = 0
while abs(B - A) > xtol:
if sg.bisect_before_step(abs(B - A)):
@@ -592,7 +596,22 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
x_new = 0.5 * (A + B)
else:
step = None
if space == "log":
if prestep == "progress":
# PRE-STEP GATE (PREREG 2026-09-21). Measured cause of our cost:
# the safeguard REPAIRS 77.5% of evaluations after the fact, while
# Chandrupatla REFUSES its model on 72% of steps for free and
# halves instead - 2.310 halvings per evaluation against our 1.422.
# Our step never leaves the bracket (0 <= S <= 1), so the question
# is not overshoot but PROGRESS: if the similarity step promises
# less than bisection would remove, spend the same single
# evaluation on the midpoint instead. Two lengths already in hand
# are compared; there is no threshold and no new constant.
cand = (1.0 - S(abs(fx) / sc)) * (far - x)
half = 0.5 * (A + B) - x
if abs(cand) < abs(half):
step = half
pre_refused += 1
if step is None and space == "log":
# c = the bracket end with the smaller |f| (the side the
# root is nearer); s = xtol, fixed by derivation in
# PREREG amendment 1, never chosen. Falls back to the
@@ -624,7 +643,7 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
if x_new == x or x_new == x_prev:
# the bracket can no longer be split in floating point
if stats is not None:
stats['forced'] = forced
stats['forced'] = forced; stats['pre_refused'] = pre_refused
return x_new, g.n, abs(B - A) <= xtol
w_before = abs(B - A)
@@ -636,7 +655,7 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
x, fx = x_new, fnew
if fnew == 0.0:
if stats is not None:
stats['forced'] = forced
stats['forced'] = forced; stats['pre_refused'] = pre_refused
return x_new, g.n, True
if FA * fnew < 0.0:
B, FB = x_new, fnew
@@ -645,9 +664,9 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
if guard == "credit":
sg.note_shrink(w_before, abs(B - A))
if stats is not None:
stats['forced'] = forced
stats['forced'] = forced; stats['pre_refused'] = pre_refused
return 0.5 * (A + B), g.n, True
except _Budget:
if stats is not None:
stats['forced'] = forced
stats['forced'] = forced; stats['pre_refused'] = pre_refused
return x, g.n, False