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
+70
View File
@@ -0,0 +1,70 @@
"""On wide brackets we spend what bisection spends. WHO caps us — the step rule or the guard?
Measured (diag_fun1_endgame.py): on the same function our cost grows with the bracket width
(7, 12, 19, 59, 63 against Chandrupatla's 8, 12, 15, 24, 44) while our observed ORDER is
higher than theirs, so the endgame is not the problem. 59 evaluations on a 2e4-wide bracket
is what bisection needs (log2(2e16) ~ 54), i.e. we degrade to one halving per evaluation.
Two candidate causes, and they are distinguishable by a run:
* the STEP RULE cannot march (the similarity step is an absolute length), or
* the GUARANTEE's projection clamps the accepted step to a halving-sized move.
So the same runs are repeated with each guard, including guard="none" (no guarantee at all).
If "none" marches, the guard is the cap; if it also crawls, the step rule is.
python -u diag_who_caps_us.py
"""
from __future__ import annotations
import math
from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS_DICTS
from simroot.baselines import bisection, chandrupatla, chandrupatla_scipy
from simroot.pure import pure_root
from sweep_full import ACC, MAXFEV, XTOL
GUARDS = ("none", "step", "project", "safe", "credit")
def main():
print(f"{'problem':<9} {'width':>10} {'bisect':>7} {'CH':>5} " +
" ".join(f"{g:>8}" for g in GUARDS))
for d in _CHANDRUPATLA_TESTS_DICTS:
pid = str(d["ID"])
if not (pid.startswith("fun1.") or pid in ("fun3.1", "fun5.1", "fun4.3")):
continue
f = d["f"]
a, b = (float(v) for v in d["bracket"])
root = float(d["root"])
ok = lambda x, c: bool(c and math.isfinite(float(x)) # noqa: E731
and (abs(float(x) - root) <= ACC or f(float(x)) == 0.0))
_xb, nb, cb = bisection(f, a, b, XTOL, MAXFEV)
best = None
for impl in (chandrupatla, chandrupatla_scipy):
try:
xc, nc, cc = impl(f, a, b, XTOL, MAXFEV)
except Exception:
continue
if ok(xc, cc) and (best is None or nc < best):
best = int(nc)
cells = []
for g in GUARDS:
try:
x, n, c = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio",
scale="derived3", guard=g, prestep="slope",
direction="dissimilar")
cells.append(f"{n:8d}" if ok(x, c) else f"{n:7d}✗")
except Exception:
cells.append(f"{'err':>8}")
print(f"{pid:<9} {abs(b - a):10.3g} {nb:7d} {best if best else 0:5d} " +
" ".join(cells))
print("\nlegend: ✗ = the run did not reach the root (an unguarded method may not)")
print("if guard='none' marches where 'safe'/'project' crawl, the GUARANTEE is the cap;")
print("if 'none' crawls too, the STEP RULE is the cap.")
if __name__ == "__main__":
main()