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

77 lines
2.8 KiB
Python

"""The largest single item of the wide-bracket gap: fun1's endgame (+61 evaluations).
If it has a structural cause, a derived fix may exist; if it is simply the order of
convergence, the answer is "no" and the reason is worth stating. Reports, per fun1 problem:
the bracket, the multiplicity-like behaviour of f near the root, the observed order of both
methods, and the endgame split.
python -u diag_fun1_endgame.py
"""
from __future__ import annotations
import math
from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS_DICTS
from simroot.baselines import CHANDRUPATLA_BASELINE
from simroot.baselines import chandrupatla, chandrupatla_scipy
from simroot.pure import pure_root
from stage4_why import observed_order
from sweep_full import ACC, MAXFEV, XTOL
def ours(f, a, b, xtol, maxfev):
return pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived3",
guard="safe", prestep="slope", direction="dissimilar")
def multiplicity_probe(f, root):
"""How does |f| grow away from the root? |f(root+t)| ~ C t^m gives m from two points;
m = 1 is a simple root, m >= 2 flattens the endgame for every interpolating method."""
t1, t2 = 1e-5, 1e-4
f1, f2 = abs(f(root + t1)), abs(f(root + t2))
if f1 == 0.0 or f2 == 0.0:
return float("nan")
return math.log(f2 / f1) / math.log(t2 / t1)
def main():
print(f"{'problem':<9} {'bracket':>26} {'root':>10} {'m':>6} "
f"{'ours n':>7} {'ord':>6} {'CH n':>6} {'ord':>6}")
for d in _CHANDRUPATLA_TESTS_DICTS:
pid = str(d["ID"])
if not pid.startswith("fun1."):
continue
f = d["f"]
a, b = (float(v) for v in d["bracket"])
root = float(d["root"])
m = multiplicity_probe(f, root)
x_o, n_o, ok_o = ours(f, a, b, XTOL, MAXFEV)
best = None
for impl in (CHANDRUPATLA_BASELINE,): # the library alone (2026-09-21)
try:
x_c, n_c, ok_c = impl(f, a, b, XTOL, MAXFEV)
except Exception:
continue
if best is None or n_c < best[1]:
best = (float(x_c), int(n_c), bool(ok_c))
try:
ord_o = observed_order(ours, f, a, b, root)
except Exception:
ord_o = float("nan")
try:
ord_c = observed_order(chandrupatla, f, a, b, root)
except Exception:
ord_c = float("nan")
print(f"{pid:<9} [{a:10.4g}, {b:10.4g}] {root:10.5g} {m:6.2f} "
f"{n_o:7d} {ord_o if isinstance(ord_o, float) else float('nan'):6.2f} "
f"{best[1]:6d} {ord_c if isinstance(ord_c, float) else float('nan'):6.2f}")
print(f" |f| at root+1e-5 = {abs(f(root + 1e-5)):.3e}, "
f"root+1e-2 = {abs(f(root + 1e-2)):.3e}, "
f"ours x={x_o:+.10g} ok={ok_o}")
if __name__ == "__main__":
main()