Found on the way: negative item 'log change of variable' compared 3474 with 4516, the total of a DIFFERENT configuration; the honest pair is the same configuration in x, CHA 3643 -> 3474, APS 5822 -> 6940. Fixed in both texts. The gap-decomposition numbers were only traced to saved logs; they are now RECOMPUTED by verify_article.py from fresh runs of the diagnosis scripts (which return their values): 216 checks OK on Linux, 762 tests pass twice. Open, for Valeri: the .md still says the projection's doubling is 'not a tuning constant', which simroot/pure.py (project_credit) refutes and which is why the .tex proposes credit; the .md's Tables 2-3 predate Chandrupatla's own method as a baseline (2785 APS, first place), so its 'parity' abstract is out of date; Brent 2890/4101 in the .md against 2889/4099 in the current sweep. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
212 lines
9.2 KiB
Python
212 lines
9.2 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, 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 = []
|
|
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()
|