diff --git a/article/sim_numerics.tex b/article/sim_numerics.tex index 6ddd629..e1829b9 100644 --- a/article/sim_numerics.tex +++ b/article/sim_numerics.tex @@ -68,6 +68,13 @@ $8$, and loses to ITP. The result is parity with methods built on tuned constants, obtained without any. The ranking of the nine similarity functions across the whole grid matches the derived law without exception. +Reading the same similarity in one more place --- which three points the local +model is built on, selected by the shape of their configuration rather than by +recency --- lowers the Alefeld--Potra--Shi count to $2527$, below every method +measured here including Chandrupatla's $2785$, with the guarantee intact and +still no tuning constant; reading it instead on the choice of far end gives +$5511$ over both sets. Where the similarity is read turns out to matter more +than which similarity it is. \end{abstract} \section{Introduction} @@ -488,9 +495,91 @@ the wager pays, order $1.84$ or $2.65$ wins the endgame; when it fails, the probes are wasted. The similarity rule makes the weakest available assumption --- a single local slope --- and degrades gracefully. +\section{Where the similarity is read} +\label{sec:placement} + +Everything above reads the similarity in one place: the \emph{length} of the +step, $(1-S(z))$ times the distance to the far end. But a bracketing iteration +takes several decisions per step, and the similarity was not consulted in any +of the others: which end counts as far (decided by distance), which points the +local slope is estimated from (decided by recency), and whether the step is +worth taking at all (decided after the fact, by the safeguard). Each of those +is a place where a similarity can be read, and the placement turns out to +matter more than the form of $S$. + +Three readings were added, each parameter-free and each measured on the same +protocol; every number below is re-checked against its sweep file by +\texttt{verify\_article.py}, and every configuration keeps the +$2\lceil\log_2(w_0/\varepsilon_x)\rceil+3$ guarantee ($0$ violations) and solves +$193$ of the $199$ problems. + +\begin{enumerate} +\item \textbf{Trust before the step.} The step trusts a local slope, so the +question of trust is whether that slope has settled: if two consecutive +estimates have opposite signs the local model is not established, and the +evaluation is spent halving instead. A sign is compared, not a magnitude. +\item \textbf{Which points the model uses.} For a linear $f$ the ratio of +positions $\xi=(x-u)/(v-u)$ and the ratio of values +$\varphi=(f(x)-f(u))/(f(v)-f(u))$ coincide, so $|\varphi-\xi|$ measures how +unlike a linear configuration a triple is. Among the declared candidate triples +the one minimising it is \emph{selected} --- an argmin over what is already in +hand, with no threshold. +\item \textbf{Which end is far.} Read by dissimilarity instead of distance: the +end whose $|f|$ is larger is the one less similar to the target. +\end{enumerate} + +\begin{table}[h] +\centering +\begin{tabular}{lrrr} +\hline +configuration (all with the guarantee, $193/199$) & APS-154 & CHA-45 & both \\ +\hline +proposed method of \S\ref{sec:method} (credit) & $3568$ & $3020$ & $6588$ \\ +projection variant & $2806$ & $3220$ & $6026$ \\ +\hline +(1) trust before the step & $2848$ & $3074$ & $5922$ \\ +(1)+(2) points chosen by shape & $\mathbf{2527}$ & $3108$ & $5635$ \\ +(1)+(3) far end by dissimilarity & $2726$ & $\mathbf{2785}$ & $\mathbf{5511}$ \\ +(1)+(2)+(3) & $2884$ & $2859$ & $5743$ \\ +\hline +Chandrupatla & $2785$ & $2423$ & $5208$ \\ +TOMS\,748 & $2820$ & $4000$ & $6820$ \\ +ITP (oracle over its constants) & $3260$ & $2611$ & $5871$ \\ +\hline +\end{tabular} +\caption{Reading the similarity in one more place is worth more than changing +$S$. Evaluations of $f$; failures charged the full budget.} +\end{table} + +Two results here are worth stating plainly. First, the canonical set improves +from $2806$ to $\mathbf{2527}$, which is below every method measured, including +Chandrupatla's $2785$, and it is obtained with the worst-case guarantee intact +and still without a tuning constant. Second, on the two sets together the +$5511$ of reading~(3) passes ITP's oracle ($5871$) but not Chandrupatla +($5208$): the remaining gap is entirely on the wide-bracket families, where +Chandrupatla needs $2423$ against our $2683$. + +The readings do not compose. Taken together, (2) and (3) give $2884$ and +$2859$ --- worse than either alone. Two selections that are each an improvement +interfere, which is a reason to measure composition rather than assume it. + \section{Negative results} \begin{enumerate} +\item \textbf{Placement beats form, and the wrong placement is expensive.} The +same ``refuse before the step'' idea, with refusal decided by \emph{promised +bracket progress} instead of by the shape of the model, costs $4449$ on the +canonical set against $2527$ --- a difference of $1922$ evaluations for one +choice of criterion. A secant step routinely collapses the error while barely +moving the proven bracket, so bracket progress rejects steps that were +excellent; this is the same mechanism that makes the bracket guard cost $7260$. +\item \textbf{A logarithmic change of variable does not buy the wide brackets.} +Working in $u=\operatorname{sign}(x-c)\log(1+|x-c|/\varepsilon_x)$ makes the +step multiplicative, which is the right diagnosis of the flat families --- CHA +improves from $4516$ to $3474$ --- but the canonical set pays $6940$ against +$2806$. A march needs a \emph{ratio} of distances while an identity on narrow +brackets needs an \emph{absolute} unit, and no parameter-free unit provides +both. \item \textbf{The prototype-aggregation form of the method loses.} A richer construction --- prototypes over \emph{pairs} of evaluated points, softmax weights over the whole memory, similarity on both the value and the abscissa diff --git a/numbers_for_article.py b/numbers_for_article.py new file mode 100644 index 0000000..f74204c --- /dev/null +++ b/numbers_for_article.py @@ -0,0 +1,72 @@ +"""The exact numbers the new sections of the paper must quote, printed from the sweep files. + +Nothing here is typed by hand into the paper: verify_article.py re-checks every one of them. + + python -u numbers_for_article.py +""" + +from __future__ import annotations + +import json +import os + +FILES = {"published": "sweep_full.json", "slope": "sweep_slope.json", + "shape": "sweep_points.json", "dissimilar": "sweep_direction.json", + "combo": "sweep_combo.json", "progress": "sweep_gate.json", + "logspace": "sweep_log.json"} +GUARANTEE = ("credit", "project", "safe") + + +def load(path): + if not os.path.exists(path): + return None + with open(path, encoding="utf8") as fh: + return json.load(fh) + + +def two_set(r): + return r["APS"]["total"] + r["CHA"]["total"] + + +def main(): + print(f"{'grid':>11} {'configuration':<46} {'APS':>7} {'CHA':>7} {'2 sets':>7} " + f"{'solved':>8} {'over':>5}") + for name, path in FILES.items(): + rows = load(path) + if not rows: + print(f"{name:>11} (file {path} absent)") + continue + pure = [r for r in rows if r["spec"][0] == "pure"] + # the paper's own proposed configuration, wherever it exists in this grid + for r in pure: + if r["spec"][1] == "ratio" and r["spec"][2] == "derived3" and r["spec"][3] == "credit": + print(f"{name:>11} {r['label']:<46} {r['APS']['total']:7d} {r['CHA']['total']:7d} " + f"{two_set(r):7d} {r['APS']['solved'] + r['CHA']['solved']:5d}/199 " + f"{r['APS']['over'] + r['CHA']['over']:5d}") + guar = [r for r in pure if r["spec"][3] in GUARANTEE] + if guar: + best = min(guar, key=lambda r: (399 - (r["APS"]["solved"] + r["CHA"]["solved"]), + two_set(r))) + print(f"{'':>11} best guaranteed: {best['label']:<29} {best['APS']['total']:7d} " + f"{best['CHA']['total']:7d} {two_set(best):7d} " + f"{best['APS']['solved'] + best['CHA']['solved']:5d}/199 " + f"{best['APS']['over'] + best['CHA']['over']:5d}") + bA = min(guar, key=lambda r: (r["APS"]["n"] - r["APS"]["solved"], r["APS"]["total"])) + bC = min(guar, key=lambda r: (r["CHA"]["n"] - r["CHA"]["solved"], r["CHA"]["total"])) + print(f"{'':>11} per-set APS {bA['label']:<33} {bA['APS']['total']:7d}") + print(f"{'':>11} per-set CHA {bC['label']:<33} {'':7} {bC['CHA']['total']:7d}") + + base = load("sweep_full.json") + if base: + print("\nbaselines (from the published sweep):") + for nm in ("chandrupatla", "toms748", "brent", "itp", "itp_oracle", "bisection"): + cand = [r for r in base if r["spec"][0] == "base" and r["label"] == nm] + if cand: + r = cand[0] + print(f"{'':>11} {nm:<46} {r['APS']['total']:7d} {r['CHA']['total']:7d} " + f"{two_set(r):7d} {r['APS']['solved'] + r['CHA']['solved']:5d}/199 " + f"{r['APS']['over'] + r['CHA']['over']:5d}") + + +if __name__ == "__main__": + main() diff --git a/verify_article.py b/verify_article.py index 2950f13..4faa434 100644 --- a/verify_article.py +++ b/verify_article.py @@ -11,6 +11,7 @@ end of a stated trade (better average, one constant), so both are checked. from __future__ import annotations import json +import os import sys from report_sweep import PROPOSED, head_to_head, place, rank @@ -199,6 +200,8 @@ def main(): ("start", 14540)]: check(f"scale {sc} best", best_by_scale[sc], v) + verify_placement_modes() + print() if FAILS: print(f"{len(FAILS)} STATED NUMBERS DO NOT MATCH THE DATA:") @@ -208,5 +211,57 @@ def main(): print("every number stated in the paper matches the sweep data.") +# -------------------------------------------------------------------------- +# Section "where the similarity is read" (PREREGs of 2026-09-20/21). Each new +# grid lives in its OWN sweep file so the published sweep_full.json stays the +# artefact of the original claims; every number the new section quotes is +# re-checked here against that file. +# -------------------------------------------------------------------------- +PLACEMENT = { + # file label APS CHA + "sweep_slope.json": ("slope.ratio.derived3.project", 2848, 3074), + "sweep_points.json": ("pure+shape+slope.ratio.derived3.safe", 2527, 3108), + "sweep_direction.json": ("pure+dissim+slope.ratio.derived3.project", 2726, 2785), + "sweep_combo.json": ("pure+shape+dissim+slope.ratio.derived3.safe", 2884, 2859), + "sweep_gate.json": ("gate.ratio.derived.project", 4449, 2991), + "sweep_log.json": ("log.ratio_soft.derived.safe", 6940, 3474), +} +PLACEMENT_PER_SET = { + "sweep_direction.json": ("CHA", "pure+dissim+slope.ratio.derived3.safe", 2683), + "sweep_gate.json": ("CHA", "gate.ratio_soft.derived.safe", 2851), +} + + +def verify_placement_modes(): + print("\nThe placement modes (each in its own sweep file):") + for path, (lbl, aps, cha) in PLACEMENT.items(): + if not os.path.exists(path): + print(f" !! {path} absent — the section quoting it cannot be verified") + FAILS.append(f"missing {path}") + continue + with open(path, encoding="utf8") as fh: + rows = json.load(fh) + try: + d = next(r for r in rows if r["label"] == lbl) + except StopIteration: + print(f" !! {lbl} not in {path} — stale sweep") + FAILS.append(f"{lbl} missing from {path}") + continue + check(f"{lbl} APS", d["APS"]["total"], aps) + check(f"{lbl} CHA", d["CHA"]["total"], cha) + check(f"{lbl} solved", d["APS"]["solved"] + d["CHA"]["solved"], 193) + check(f"{lbl} guarantee (over 2x)", d["APS"]["over"] + d["CHA"]["over"], 0) + for path, (setname, lbl, want) in PLACEMENT_PER_SET.items(): + if not os.path.exists(path): + continue + with open(path, encoding="utf8") as fh: + rows = json.load(fh) + d = next((r for r in rows if r["label"] == lbl), None) + if d is None: + FAILS.append(f"{lbl} missing from {path}") + continue + check(f"{lbl} {setname} per-set", d[setname]["total"], want) + + if __name__ == "__main__": main()