Why the fun9 well is closed only partly (diagnostics only, method unchanged). The well itself IS closed: reaching the root's side takes 4 evaluations with contra/defect against Chandrupatla's 3 (13 and 33 before). The remaining +20 (75 vs 55) is after it: (1) the exponential flank on the widest brackets - our step moves ~1 per evaluation down exp(x) (f/f' = 1), 10 evaluations where Chandrupatla halves 3 times; the analogy entry comes late because exp is self-similar by TRANSLATION, not scaling - the pair's x-scale does not shrink, so m = ln(f ratio)/ln(x ratio) is undefined or negative; (2) overconfident near-zero steps - on a steep flank the three-point slope is huge, z ~ 0, S ~ 1, the model promises the root at a point that is not it: 8 wasted evaluations on fun9 (27 without the decisions).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
vvs
2026-09-21 18:35:07 +01:00
co-authored by Claude Opus 5
parent 08863c170c
commit 485f25f165
3 changed files with 187 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
"""How common are near-zero steps - the model promising 'the root is right here' at a point
that is not the root, so the next evaluation lands almost on the same x and f barely moves?
Seen on fun9 (diag_fun9_well.py): 0.0002 -> 0.0002, 6.75 -> 6.734, 1.964 -> 1.941, 20.2502 ->
20.2501. On a steep flank the three-point slope is dominated by a huge value, the scale
sigma = |slope| D is huge, z = |f|/sigma ~ 0 and S(z) ~ 1.
DIAGNOSTIC thresholds (reporting only, never in the method): a step is near-zero when
|x_new - x| < 1% of the bracket, and wasted when after it |f| kept >= 90% of its value.
Counted on the recorded evaluations of the proposed rule (projection) and of the best
variant, per group. Nothing in the method changes.
python -u diag_near_zero_steps.py
"""
from __future__ import annotations
from collections import defaultdict
from simroot.pure import pure_root
from sweep_full import MAXFEV, XTOL, load_problems
FLAT = ("fun3", "fun4", "fun5", "fun6")
def main():
for pre in ("none", "defect+flatanalogmempt"):
agg = defaultdict(lambda: [0, 0, 0, 0]) # problems, evals, near-zero, wasted
for s, pid, f, a, b, _root in load_problems():
pts = []
def g(x, f=f):
v = f(x)
pts.append((float(x), float(v)))
return v
pure_root(g, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3",
guard="project", prestep=pre)
fam = pid.split(".")[0]
grp = "APS" if s == "APS" else ("CHA-flat" if fam in FLAT else f"CHA-{fam}")
lo, hi = min(a, b), max(a, b)
near = wasted = 0
for (x0, f0), (x1, f1) in zip(pts[2:], pts[3:]):
width = hi - lo
if width > 0 and abs(x1 - x0) < 0.01 * width:
near += 1
if f0 != 0.0 and abs(f1) >= 0.9 * abs(f0):
wasted += 1
if f1 != 0.0 and lo < x1 < hi:
# track the bracket as the method does (sign against the left end)
if (f1 > 0) == (f(lo) > 0):
lo = x1
else:
hi = x1
ag = agg[grp]
ag[0] += 1
ag[1] += len(pts)
ag[2] += near
ag[3] += wasted
print(f"\n== {pre}")
print(f" {'group':<9} {'problems':>8} {'evals':>6} {'near-zero':>10} {'wasted':>7}")
for grp in sorted(agg):
n, e, nz, w = agg[grp]
print(f" {grp:<9} {n:8d} {e:6d} {nz:10d} {w:7d}")
print("diag done")
if __name__ == "__main__":
main()