Why the wide-bracket gap remains, taken apart on the SAME evaluations: 'order of the model' explains only a third. Of the 262 evaluations lost on 33 problems, 178 sit on fun3-fun6 - pure powers of order 3, 5, 9, 19 - where no interpolating model works (our step 0.41-0.81 bracket halvings per evaluation, IQI from our own points 0.01-0.17) and Chandrupatla wins by BISECTING 93-100% of his steps; our step is slower than bisection there and our 2x-bisection guarantee licenses it. His band inside our step confirms it per family: fun4/5/6 573/456/456 -> 296/239/235 against his 293/228/228, while +164/+111/+170/+144 on fun1/2/8/9 where it refuses good steps - slot 2's total was two opposite effects summed. Only fun1 (58) is model order: IQI from our three points 10.37 halvings against our 0.62; a convex two-point step cannot carry IQI's negative weight (428/428 non-degenerate states; five underflowed to -0.0).

The flat regime is not detectable by the three-point order estimate: m >= 2 (the exact condition for a tangent step to lose to a halving) fires on 0.2% of APS states and on only 5.8% of states of pure powers of order 3-19. Paper corrected where it said the whole gap is model order (widelimit, decision, abstract, future work - the refuted log transform removed). WARNING recorded in FINDINGS_model_order.md: article/sim_numerics.tex has carried a STALE header since the first commit (current text = sim_numerics.md, unchanged since 08-24); every paper edit of 09-20/21 went into the .tex, the .md has none of them. Which file is canonical is Valeri's call. The new text numbers trace to results_diag_*.txt but are not yet machine-checked by verify_article.py. Trace hook in pure_root is behaviour-neutral and pinned; 762 pass on the server twice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
vvs
2026-09-21 07:38:16 +01:00
co-authored by Claude Opus 5
parent 46c713cdd3
commit f5d036c9d8
13 changed files with 693 additions and 28 deletions
+205
View File
@@ -0,0 +1,205 @@
"""Why the model-order gap remains: the same information, two models.
Hypothesis (stated before the run, 2026-09-21). Our step x' = x + (1-S)(far - x) is a
CONVEX combination of two points, x' = S.x + (1-S).far, with weights in [0,1]; a third
point enters only through the scale sigma, i.e. through a slope. Chandrupatla's step is
inverse quadratic interpolation (IQI): x' = sum w_i x_i with Lagrange weights of the
values at 0, and whenever the triple's values bracket zero ONE weight is necessarily
NEGATIVE - the step pushes away from the stale point. A [0,1] similarity cannot produce
a negative weight, so a similarity-weighted average of points is capped at a first-order
inverse model: Proposition 4 of the paper, extended from two points to three.
The test uses NO new method: replay our own trajectory and, at every state, place IQI on
the SAME evaluations we already paid for. If the gap is the model, IQI from our points
must contract the error more than our step did.
Pre-declared predictions
Q1 in every state whose triple brackets zero, exactly one Lagrange weight is < 0
(a sign identity; measured as a check of the argument, must be 100%)
Q2 on the problems where we lose to Chandrupatla, IQI from the same points gives
more halvings of the error per step than our raw step, on states where IQI lands
inside the proven bracket
Q3 the advantage sits in the APPROACH class (error at least half the current bracket
- the one constant of the method, the bisection 1/2) and shrinks in the endgame
FALSIFIER: if IQI on the same points does NOT contract more, the gap is not how the
information is used, and the explanation must be sought elsewhere - e.g. WHICH points are
kept (Chandrupatla keeps a bracketing triple, we keep the most recent), which is why IQI
is placed twice: on our recent triple and on (x, A, B), the bracketing one.
python -u diag_model_order.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
XTOL, MAXFEV, ACC = 1e-12, 200, 1e-6
OURS = dict(similarity="ratio", scale="derived3", guard="safe", prestep="slope",
direction="dissimilar") # reading (3), CHA 2683 per set
def iqi(p0, p1, p2):
"""Inverse quadratic interpolation at f = 0, plus its Lagrange weights."""
(x0, f0), (x1, f1), (x2, f2) = p0, p1, p2
if len({f0, f1, f2}) < 3 or len({x0, x1, x2}) < 3:
return None, None
d0 = (f0 - f1) * (f0 - f2)
d1 = (f1 - f0) * (f1 - f2)
d2 = (f2 - f0) * (f2 - f1)
# distinct values can still give a product that underflows to 0 (first run of
# 2026-09-21 crashed on exactly that); the model is then undefined, not an error
if d0 == 0.0 or d1 == 0.0 or d2 == 0.0:
return None, None
w0 = f1 * f2 / d0
w1 = f0 * f2 / d1
w2 = f0 * f1 / d2
x = w0 * x0 + w1 * x1 + w2 * x2
if not all(math.isfinite(v) for v in (x, w0, w1, w2)):
return None, None
return x, (w0, w1, w2)
def halvings(e_before, landing, r):
"""-log2 of the error contraction; distances below one ulp of r are not measurable,
so the floor is the ulp, not a chosen number."""
after = max(abs(landing - r), math.ulp(r) if r != 0.0 else 5e-324)
return math.log2(e_before / after)
def ch_cost(f, a, b, r):
best = None
for impl in (chandrupatla, chandrupatla_scipy):
try:
x, n, c = impl(f, a, b, XTOL, MAXFEV)
except Exception:
continue
if c and math.isfinite(float(x)) and abs(float(x) - r) <= ACC:
best = int(n) if best is None else min(best, int(n))
return best
MODELS = ("ours", "iqiR", "iqiB")
def bracket_step(f, lo, flo, hi, fhi, landing):
"""One evaluation at `landing`, the standard bracket update, and the new width.
Evaluated for the diagnosis only - nothing is charged - so every model gets exactly
ONE evaluation from the same state, which is the comparison the gap is stated in."""
fl = f(landing)
if fl == 0.0:
return 0.0
if (fl > 0.0) == (flo > 0.0):
lo = landing
else:
hi = landing
return abs(hi - lo)
def main():
# per model, per phase: [sum bracket-halvings, sum error-halvings, count]
agg = {(m, ph): [0.0, 0.0, 0] for m in MODELS for ph in ("app", "end")}
paired = {"iqiR": [0.0, 0], "iqiB": [0.0, 0]} # sum(h_model - h_ours) on common states
neg_ok = neg_all = 0
valid = {m: 0 for m in MODELS}
states_all = 0
lose = []
print(f"{'problem':<8} {'ours':>5} {'CH':>4} {'states':>6} "
f"bracket halvings per evaluation: {'ours':>6} {'IQI-rec':>8} {'IQI-brk':>8}")
for d in _CHANDRUPATLA_TESTS_DICTS:
pid = str(d["ID"])
f = d["f"]
a, b = (float(v) for v in d["bracket"])
r = float(d["root"])
st = {"trace": []}
x, n, conv = pure_root(f, a, b, XTOL, MAXFEV, stats=st, **OURS)
if not (conv and abs(float(x) - r) <= ACC):
continue
ch = ch_cost(f, a, b, r)
if ch is None or n <= ch:
continue # only the problems where we lose
lose.append(pid)
per = {m: [] for m in MODELS}
cnt = 0
for s in st["trace"]:
if s["xp2"] is None:
continue
e = abs(s["x"] - r)
if e == 0.0:
continue
A, FA, B, FB = s["A"], s["FA"], s["B"], s["FB"]
lo, flo, hi, fhi = (A, FA, B, FB) if A < B else (B, FB, A, FA)
W = hi - lo
if W <= 0.0:
continue
cnt += 1
phase = "app" if e >= 0.5 * W else "end"
recent = ((s["x"], s["fx"]), (s["xp"], s["fp"]), (s["xp2"], s["fp2"]))
fs = [p[1] for p in recent]
if min(fs) < 0.0 < max(fs):
_xq, wq = iqi(*recent)
if wq is not None:
neg_all += 1
neg_ok += sum(w < 0.0 for w in wq) == 1
# Chandrupatla's own triple: both proven ends plus the latest point that is
# neither of them (x itself is always an end by the time a state is recorded)
third = None
for cx, cf in ((s["xp"], s["fp"]), (s["xp2"], s["fp2"])):
if cx is not None and cx != A and cx != B:
third = (cx, cf)
break
land = {"ours": s["x"] + s["raw"],
"iqiR": iqi(*recent)[0],
"iqiB": iqi((A, FA), (B, FB), third)[0] if third else None}
hb = {}
for m in MODELS:
L = land[m]
if L is None or not (lo < L < hi):
continue
Wn = bracket_step(f, lo, flo, hi, fhi, L)
# an exact hit makes the width 0; the floor is one ulp of the root, the
# same unmeasurable-distance floor `halvings` uses - not a chosen cap
h_b = math.log2(W / max(Wn, math.ulp(r) if r != 0.0 else 5e-324))
h_e = halvings(e, L, r)
hb[m] = h_b
valid[m] += 1
per[m].append(h_b)
cell = agg[(m, phase)]
cell[0] += h_b
cell[1] += h_e
cell[2] += 1
for m in ("iqiR", "iqiB"):
if m in hb and "ours" in hb:
paired[m][0] += hb[m] - hb["ours"]
paired[m][1] += 1
states_all += cnt
mean = lambda v: (sum(v) / len(v)) if v else float("nan") # noqa: E731
print(f"{pid:<8} {n:5d} {ch:4d} {cnt:6d} {'':33} "
f"{mean(per['ours']):6.2f} {mean(per['iqiR']):8.2f} {mean(per['iqiB']):8.2f}")
print(f"\nproblems where we lose to Chandrupatla: {len(lose)}, states {states_all}")
print(f"\nQ1 triples bracketing zero with exactly ONE negative Lagrange weight: "
f"{neg_ok}/{neg_all}"
+ (" -> identity holds" if neg_ok == neg_all else " -> IDENTITY FAILS"))
print(" model defined and landing strictly inside the proven bracket: " +
", ".join(f"{m} {100.0 * valid[m] / max(states_all, 1):.0f}%" for m in MODELS))
print("\nQ2/Q3 mean halvings per ONE evaluation from the same state "
"(bracket | error), valid states only")
for ph, name in (("app", "approach (error >= 1/2 bracket)"), ("end", "endgame")):
cells = []
for m in MODELS:
sb, se, c = agg[(m, ph)]
cells.append(f"{m} {sb / c if c else float('nan'):5.2f} | "
f"{se / c if c else float('nan'):5.2f} (n={c})")
print(f" {name:<32} " + " ".join(cells))
print("\nPAIRED on states where both are valid: mean (IQI - ours) bracket halvings")
for m, (s_, c) in paired.items():
print(f" {m}: {s_ / c if c else float('nan'):+.2f} per evaluation (n={c})")
if __name__ == "__main__":
main()