Files
sim-numerics/diag_cha_gap.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

74 lines
2.9 KiB
Python

"""WHERE the remaining gap to Chandrupatla on the wide-bracket set actually is.
Measure before proposing: our best guaranteed configuration reaches CHA 2683 against
Chandrupatla's 2423. Rather than invent a mechanism, this locates the loss per problem and
per family, and reports for each problem what the two methods spend, so the next
pre-registration can name a mechanism that addresses the measured loss.
python -u diag_cha_gap.py
"""
from __future__ import annotations
import math
from collections import defaultdict
from simroot.baselines import CHANDRUPATLA_BASELINE
from simroot.baselines import best_of, chandrupatla, chandrupatla_scipy
from simroot.pure import pure_root
from sweep_full import ACC, MAXFEV, XTOL, load_problems
CHA_BASE = CHANDRUPATLA_BASELINE # the library alone (2026-09-21)
def ours(f, a, b):
st = {}
x, n, ok = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3",
guard="safe", prestep="slope", direction="dissimilar", stats=st)
return x, n, ok, st
def main():
rows = []
fam_loss = defaultdict(lambda: [0, 0, 0]) # family -> [ours, theirs, count]
tot_o = tot_c = 0
print(f"{'problem':<12} {'ours':>6} {'CH':>6} {'diff':>6} {'w0/xtol decades':>16} "
f"{'refused%':>9} {'forced%':>8} {'ok':>4}")
for s, pid, f, a, b, root in load_problems():
if s != "CHA":
continue
x_o, n_o, ok_o, st = ours(f, a, b)
x_c, n_c, ok_c = CHA_BASE(f, a, b, XTOL, MAXFEV)
ok_o = bool(ok_o and math.isfinite(x_o) and abs(x_o - root) <= ACC)
ok_c = bool(ok_c and math.isfinite(float(x_c)) and abs(float(x_c) - root) <= ACC)
c_o = n_o if ok_o else MAXFEV
c_c = n_c if ok_c else MAXFEV
tot_o += c_o
tot_c += c_c
fam = pid.split(".")[0]
fam_loss[fam][0] += c_o
fam_loss[fam][1] += c_c
fam_loss[fam][2] += 1
rows.append((c_o - c_c, pid, c_o, c_c))
print(f"{pid:<12} {c_o:6d} {c_c:6d} {c_o - c_c:+6d} "
f"{math.log2(abs(b - a) / XTOL):16.1f} "
f"{100.0 * st.get('pre_refused', 0) / max(n_o, 1):8.1f}% "
f"{100.0 * st.get('forced', 0) / max(n_o, 1):7.1f}% "
f"{'' if ok_o else 'OURS FAIL':>4}")
print(f"\ntotals: ours {tot_o}, Chandrupatla {tot_c}, gap {tot_o - tot_c:+d}")
print("\nper family (ours / theirs / gap):")
for fam in sorted(fam_loss, key=lambda k: -(fam_loss[k][0] - fam_loss[k][1])):
o, c, n = fam_loss[fam]
print(f" {fam:<8} n={n} {o:6d} / {c:6d} gap {o - c:+6d}")
print("\nthe five problems that cost us most:")
for d, pid, o, c in sorted(rows, reverse=True)[:5]:
print(f" {pid:<10} ours {o:4d} CH {c:4d} gap {d:+5d}")
print("\nthe five where we are ahead:")
for d, pid, o, c in sorted(rows)[:5]:
print(f" {pid:<10} ours {o:4d} CH {c:4d} gap {d:+5d}")
if __name__ == "__main__":
main()