Files
sim-numerics/explain_chandrupatla.py
T
vvsandClaude Opus 5 1b6c626f67 Valeri's decisions (1) competitors from the library, (2) variant a. (1) Chandrupatla is scipy.optimize.elementwise.find_root, Brent is brentq, no more per-problem best_of. Our Chandrupatla stopped on a bracket twice as wide as every other method (the factor the protocol corrects for ITP); fixed (+0.5*xtol) in baselines.py and in the two copies found by grep (stage16_blend, stage19_modern) and the traced mirror, and pinned: ours == library on every problem. Regenerating sweep_full.json changed exactly three rows - chandrupatla APS 2785->2787, CHA 2423->2441, brent CHA 4099->4115 - and nothing else (diff_sweeps.py: 0 unexpected).
(2) The proposed method is again the scheduled projection (2806/3220/6026), its budget multiplier DECLARED as the one free constant: the canonical total is the same for every multiplier 1.25-4, the wide-bracket one is not. The earned projection is the constant-free variant (3568/3020/6588). What the old tables hid: problem by problem the proposed rule beats Chandrupatla on the canonical set 87/24/42, exact sign test p = 9e-5, while trailing him by 0.7% in total; on the wide-bracket set he is genuinely stronger (26 to 9, p = 0.006). Gap decomposition recomputed with the library: 244 (was 262), 160 of it on the flat families (two thirds), fun1 58 (a quarter, not a third as the abstract said).

Also corrected on the way: the 'subsumed by Chandrupatla' pin claimed he is better on the worst single problem - true only with the looser tolerance (84 against the credit rule's 83 once fixed); §8.3 still called zero slack degenerate, which Table 1a refutes; §5.2's Brent figure is our pure-Python implementation and now says so. verify_article.py 286/286, 802 tests pass twice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 10:53:00 +01:00

153 lines
6.3 KiB
Python

"""Why Chandrupatla wins the probe — measured, and stated in similarity terms.
ANALYSIS ONLY. The traced copy below mirrors `simroot.baselines.chandrupatla` so its
decisions can be counted; it is NEVER used for a ranking number, and its fidelity is
asserted against the original (identical evaluation count on all 45 problems). If that
assert fails, the mirror has drifted and nothing here may be believed.
The reading being tested:
Chandrupatla = similarity(configuration, well-conditioned configuration) used as a
GATE on a model step, with bisection as the refusal branch.
ours = similarity(residual, 0) used as a SCALE for the step, with the
safeguard bolted on afterwards.
* the decision variable is t in (0,1): the probe is a FRACTION of the bracket, so the
method is scale-free by construction (what the logarithmic attempt tried to buy);
* the gate is written in two RATIOS, xi = (x1-x2)/(x3-x2) and phi = (f1-f2)/(f3-f2),
both invariant under affine rescaling of x and of f — relations, not magnitudes;
* for a LINEAR f, phi = xi exactly, and that point lies strictly inside the band
1 - sqrt(1-xi) < phi < sqrt(xi), so the band is a neighbourhood of the linear
configuration: the model is trusted exactly where the data still look linear enough.
Metric for "probe quality", parameter-free and comparable across methods:
halvings per evaluation = log2(w0 / xtol) / nfev
Bisection is 1.0 by definition; more is better.
python -u explain_chandrupatla.py
"""
from __future__ import annotations
import math
from simroot.baselines import Counter, chandrupatla
from simroot.pure import pure_root
from sweep_full import ACC, MAXFEV, XTOL, load_problems
def chandrupatla_traced(f, a, b, xtol, maxfev):
"""Mirror of the published routine plus counters. Returns (x, nfev, ok, trace)."""
g = Counter(f, maxfev)
trace = {"iqi": 0, "bisect": 0, "gate_rejected": 0, "no_third_point": 0}
x1, x2 = b, a
f1 = f2 = 0.0
try:
f1, f2 = g(x1), g(x2)
if f1 == 0.0:
return x1, g.n, True, trace
if f2 == 0.0:
return x2, g.n, True, trace
x3, f3 = x2, f2
t = 0.5
while True:
x = x1 + t * (x2 - x1)
fx = g(x)
if (fx > 0.0) == (f1 > 0.0):
x3, f3 = x1, f1
x1, f1 = x, fx
else:
x3, f3 = x2, f2
x2, f2 = x1, f1
x1, f1 = x, fx
xm, fm = (x1, f1) if abs(f1) < abs(f2) else (x2, f2)
if fm == 0.0:
return xm, g.n, True, trace
tol = 2.0 * 2.22e-16 * abs(xm) + 0.5 * xtol # mirrors the 2026-09-21 fix
span = abs(x2 - x1)
if span == 0.0:
return xm, g.n, True, trace
tl = tol / span
if tl > 0.5:
return xm, g.n, True, trace
t = 0.5
took_iqi = False
if x3 != x2 and f3 != f2:
xi = (x1 - x2) / (x3 - x2)
ph = (f1 - f2) / (f3 - f2)
if 0.0 < xi < 1.0 and 1.0 - math.sqrt(1.0 - xi) < ph < math.sqrt(xi):
d1, d2, d3 = f1 - f2, f1 - f3, f2 - f3
if d1 != 0.0 and d2 != 0.0 and d3 != 0.0:
t = (f1 / d1) * (f3 / (-d3)) + \
((x3 - x1) / (x2 - x1)) * (f1 / d2) * (f2 / d3)
took_iqi = True
else:
trace["gate_rejected"] += 1
else:
trace["no_third_point"] += 1
if not math.isfinite(t):
t = 0.5
took_iqi = False
if t < tl:
t = tl
elif t > 1.0 - tl:
t = 1.0 - tl
trace["iqi" if took_iqi else "bisect"] += 1
except Exception:
return float("nan"), g.n, False, trace
def main(prestep="none"):
probs = [p for p in load_problems() if p[0] == "CHA"]
print(f"ours: prestep={prestep!r}")
print(f"CHA problems: {len(probs)} metric: halvings per evaluation "
f"= log2(w0/xtol)/nfev (bisection = 1.0)\n")
print(f"{'problem':<16} {'CH nfev':>8} {'CH h/ev':>8} {'gate ok%':>9} "
f"{'ours nfev':>10} {'ours h/ev':>10} {'forced%':>8}")
agg = {"ch_n": 0, "our_n": 0, "ch_h": [], "our_h": [], "gate": [], "forced": []}
drift = 0
for _s, pid, f, a, b, root in probs:
w0 = abs(b - a)
decades = math.log2(w0 / XTOL)
x_ref, n_ref, ok_ref = chandrupatla(f, a, b, XTOL, MAXFEV)
x_t, n_t, ok_t, tr = chandrupatla_traced(f, a, b, XTOL, MAXFEV)
if (n_ref, ok_ref) != (n_t, ok_t):
drift += 1
steps = tr["iqi"] + tr["bisect"]
gate_ok = 100.0 * tr["iqi"] / steps if steps else float("nan")
stats = {}
x_o, n_o, ok_o = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio",
scale="derived3", guard="credit", stats=stats,
prestep=prestep)
forced = 100.0 * stats.get("forced", 0) / max(n_o, 1)
ch_h = decades / n_ref if n_ref else float("nan")
our_h = decades / n_o if n_o else float("nan")
agg["ch_n"] += n_ref if ok_ref else MAXFEV
agg["our_n"] += n_o if ok_o else MAXFEV
if ok_ref:
agg["ch_h"].append(ch_h)
agg["gate"].append(gate_ok)
if ok_o:
agg["our_h"].append(our_h)
agg["forced"].append(forced)
print(f"{pid:<16} {n_ref:8d} {ch_h:8.3f} {gate_ok:8.1f}% "
f"{n_o:10d} {our_h:10.3f} {forced:7.1f}%")
mean = lambda v: sum(v) / len(v) if v else float("nan") # noqa: E731
print(f"\nmirror fidelity: {'OK' if drift == 0 else f'DRIFTED on {drift} problems'}")
print(f"totals (failures charged {MAXFEV}): Chandrupatla {agg['ch_n']}, ours {agg['our_n']}")
print(f"halvings per evaluation: Chandrupatla {mean(agg['ch_h']):.3f}, "
f"ours {mean(agg['our_h']):.3f}")
print(f"Chandrupatla accepted the model on {mean(agg['gate']):.1f}% of steps "
f"(the rest it REFUSED and halved)")
print(f"our safeguard had to repair {mean(agg['forced']):.1f}% of evaluations")
if __name__ == "__main__":
import sys
main("slope" if "--slope" in sys.argv else "progress" if "--gate" in sys.argv else "none")