the wide-bracket gap is NOT closed, and now it has an address: the STEP RULE, not the guarantee and not the reading places. fun1 is one function at growing widths — 7/8, 12/12, 19/15, 59/24, 63/44 — so we tie when narrow and lose as it widens, while our observed order is the HIGHER one (3.63 vs 2.02); 59 on a 2e4 bracket is what bisection needs, i.e. one halving per evaluation against their 2.3. With the safeguard removed entirely we still spend 46/57/61/68 against bisection 57/77/45/45 and Chandrupatla 24/44 — worse than bisection on two of them. |f|/|f'| is an absolute length and cannot march across decades; written into the paper as the stated limit. Also fixed a tracer bug that had best_of double-counting evaluations (2993 vs 1487)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
vvs
2026-09-21 00:09:18 +01:00
co-authored by Claude Opus 5
parent 8d20816163
commit 0fb75be3cf
8 changed files with 372 additions and 6 deletions
+75
View File
@@ -0,0 +1,75 @@
"""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, 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, chandrupatla_scipy):
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()