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

213 lines
9.3 KiB
Python

"""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_BASELINE
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_BASELINE,): # the library alone (2026-09-21)
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 = []
results = {}
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
results[pid] = (n, ch, mean(per["ours"]), mean(per["iqiR"]), mean(per["iqiB"]))
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})")
# returned so verify_article.py can check the paper's numbers against a fresh run
# instead of against a transcription of this printout
return {"per_problem": results, "neg_ok": neg_ok, "neg_all": neg_all,
"lose": lose}
if __name__ == "__main__":
main()