diff --git a/apply_article_controls.py b/apply_article_controls.py new file mode 100644 index 0000000..35b890c --- /dev/null +++ b/apply_article_controls.py @@ -0,0 +1,97 @@ +"""PREREG_controls_2026-09-22 outcome into article/sim_numerics.md, as the pre-registration +prescribes: C1 passed for every detector (the claim of 8.6 stands, with the numbers), and K1 - +with the multiplicity known the classical transformation is much cheaper - stated plainly. +New section 9.1 (Tables 9b, 9c); a sentence each in the abstract, 8.3, 8.6, the conclusion, and +the future work. Exact anchors, each found exactly once, or nothing is written. + + python -u apply_article_controls.py +""" +from __future__ import annotations + +PATH = "article/sim_numerics.md" +REPL = [] + + +def R(old, new): + REPL.append((old, new)) + + +R("and without them the lead is 2403 against 3569, on the simple-root families alone 737 against 747.", + "and without them the lead is 2403 against 3569, on the simple-root families alone 737 against 747. " + "Against simpler detectors put in the place of the analogy — Lagouanelle's multiplicity estimate " + "with the same memory, Chandrupatla's acceptance band, or none — it wins on that corpus in total " + "and problem by problem (3656 against 4488, 6236 and 6165); against the classical transformation " + "$\\operatorname{sign}(f)|f|^{1/m}$ with the multiplicity given in advance it loses every multiple or " + "fractional root, by a factor of 2.4 to 6.8 — the price of estimating the order.") +R("classical since Schröder [16] and Traub [17]; what is new here is only where $m$ comes from.", + "classical since Schröder [16] and Traub [17]; what is new here is only where $m$ comes from. With " + "the multiplicity given in advance the transformation is far cheaper still (§9.1): the contribution " + "is the estimate, and its price is measured there.") +R("Roots.jl's `Order1B` and `Order2B` [12]); Chandrupatla takes the decision to halve by an empirical acceptance band [3].", + "Roots.jl's `Order1B` and `Order2B` [12]); Chandrupatla takes the decision to halve by an empirical " + "acceptance band [3]. §9.1 puts this claim against the detectors a reader would try first, and " + "against the transformation with the multiplicity known in advance.") + +SEC91 = """ + +### 9.1 Controls: simpler detectors, and the multiplicity known in advance + +Two controls ask whether the gain of §8 comes from the recognition by analogy or only from the split (7) with any reasonable detector. Both were pre-registered, with what the paper would say under every outcome, before the run (`sweep_controls.py`). + +**Other detectors in A′.** A′ is run with only its detector swapped; the memory, the split, the exit and the five readings of §8.4 are unchanged. (a) *Lagouanelle's estimate* $m_L=1/(1-\\theta)$, $\\theta=ff''/f'^2$ from the parabola through the last three points ($m_L=\\infty$ for $\\theta\\ge1$), with the same memory — once seen, held until the exit, a hysteresis — entering at $m_L\\ge2$ and at $0 cost(b[p]) for p in ps) + return w, len(ps) - w - l, l + + def sign_p(w, l): + k, n = min(w, l), w + l + return min(1.0, 2.0 * sum(math.comb(n, i) for i in range(k + 1)) / 2.0 ** n) + rows = {"K2L": (2235, 1437, 4026, 1495, 1490, 4488, 15171), + "K2P": (2682, 2526, 5485, 2117, 1622, 6236, 20668), + "K2F": (2693, 2526, 5569, 2126, 1670, 6165, 20749)} + check("Table 9b A' row", tuple(tot(ap[s]) for s in sets) + (sum(tot(ap[s]) for s in sets),), + (2259, 1074, 3290, 1371, 1382, 3656, 13032)) + for k, want in rows.items(): + k2 = R["K2"][k] + check(f"Table 9b {k} row", tuple(tot(k2[s]) for s in sets) + (sum(tot(k2[s]) for s in sets),), + want) + for k, wtl, pp in (("K2L", (84, 121, 35), ("1g", "8e-06")), ("K2P", (110, 96, 34), ("2g", "1.5e-10")), + ("K2F", (113, 87, 40), ("1g", "3e-09"))): + got = h2h(ap["D"], R["K2"][k]["D"]) + check(f"Table 9b A' vs {k} on corpus 4", got, wtl) + check(f"Table 9b p vs {k}", format(sign_p(got[0], got[2]), "." + pp[0]), pp[1]) + check("Table 9b violations", sum(v[2] for k in rows for s in sets for v in R["K2"][k][s].values()), 0) + for cls, want in (("D2", (1163, 1739)), ("D3", (924, 1207)), ("D5", (503, 477))): + keep = lambda p, c=cls: p.startswith(c) # noqa: E731 + check(f"9.1 {cls}: A' vs Lagouanelle", (tot(ap["D"], keep), tot(R["K2"]["K2L"]["D"], keep)), want) + level = [(tot(ap["D"], lambda p, c=c: p.startswith(c)), + tot(R["K2"]["K2L"]["D"], lambda p, c=c: p.startswith(c))) for c in ("D1", "D4", "D6")] + check("9.1 level on the rest (within 1.5%)", all(abs(a - b) <= 0.015 * b for a, b in level), True) + ab = _load("sweep_ablation_all.json")["abl-regime"] + no_regime_all = sum(tot(ab[s]) for s in sets) + check("band and no-detector worse than no regime (six corpora)", + all(sum(tot(R["K2"][k][s]) for s in sets) > no_regime_all for k in ("K2P", "K2F")), True) + check("band and no-detector worse than no regime (corpus 4)", + all(tot(R["K2"][k]["D"]) > tot(ab["D"]) for k in ("K2P", "K2F")), True) + t9c = {"CHA": (20, 654, 96, 66, 1068), "NEW": (40, 1229, 358, 337, 1792), + "D": (88, 2159, 901, 844, 4151)} + nonsimple = lambda m: m >= 2.0 or m <= 2.0 / 3.0 # noqa: E731 + wins = 0 + for s, want in t9c.items(): + k1 = R["K1"][s] + keep = lambda p, _m=k1["mstar"]: nonsimple(_m[p]) # noqa: E731 + got = (sum(1 for p in k1["mstar"] if keep(p)), tot(ap[s], keep), tot(k1["chandrupatla_g"], keep), + tot(k1["brent_g"], keep), tot(chf[s], keep)) + check(f"Table 9c {s}", got, want) + wins += h2h({p: v for p, v in ap[s].items() if keep(p)}, k1["chandrupatla_g"])[0] + check("Table 9c: A' wins none of the 148", wins, 0) + check("Table 9c: 2.4 to 6.8 times", (round(2159 / 901, 1), round(654 / 96, 1), + min(round(a / c, 1) for _n, a, c, _b, _f in t9c.values()), + max(round(a / c, 1) for _n, a, c, _b, _f in t9c.values())), + (2.4, 6.8, 2.4, 6.8)) + APS_ = "APS" + for s, want in ((APS_, (2259, 2640)), ("CHA", (420, 437)), ("NEW", (2061, 1924)), ("D", (1497, 1427))): + k1 = R["K1"][s] + keep = lambda p, _m=k1["mstar"]: not nonsimple(_m[p]) # noqa: E731 + check(f"9.1 rest {s}: A' vs Chandrupatla on g", (tot(ap[s], keep), tot(k1["chandrupatla_g"], keep)), + want) +''' + +src = open(PATH, encoding="utf8").read() +call_old = " verify_revision1()\n\n print()" +call_new = " verify_revision1()\n verify_controls()\n\n print()" +marker = '\n\nif __name__ == "__main__":\n main()\n' +assert src.count(call_old) == 1 and src.count(marker) == 1 +src = src.replace(call_old, call_new).replace(marker, FUNC + marker) +open(PATH, "w", encoding="utf8").write(src) +print("added verify_controls") diff --git a/article/sim_numerics.md b/article/sim_numerics.md index 9f5afb5..e7e22a4 100644 --- a/article/sim_numerics.md +++ b/article/sim_numerics.md @@ -10,7 +10,7 @@ Bisection places the next iterate at the midpoint because it uses no information The derivation fixes a *base rule* before any benchmark is consulted: a similarity whose approach to 1 is linear, the finest available data-derived scale, and a safeguard that projects the step onto the admissible interval rather than replacing it. It is, explicitly, a Newton step with a three-point divided-difference derivative, clipped to the bracket and projected onto the bisection budget. The similarity sets that step, and its form is decisive: with the tent the step is Newton's, with any of the other eight functions the proposed method costs from 19% to five times more. Its order is Muller's, 1.839. It carries a proven worst case of twice bisection plus one step and one free numerical constant, the multiplier of that budget, which we declare. On the 154 problems of Alefeld, Potra and Shi it needs 2634 evaluations of $f$ against 2636 for TOMS 748, 2707 for Brent and 2595 for Chandrupatla's method; it wins more problems than it loses against Chandrupatla, 87 to 43, and needs 1.5% more in total because its losses are the larger ones. On the 45 wide-bracket problems of Chandrupatla it beats Brent and TOMS 748 and loses to ITP and to Chandrupatla (2357 against 1946 and 1505). For every data-derived scale the three similarity functions whose $1-S$ has a non-zero slope at the origin come before the three whose slope is zero, as the derived law predicts. -The wide-bracket gap turns out to be not the order of the model but a decision — when to give the model up — and the decision is a similarity after all, of an unfamiliar kind: the similarity of a run to its own past. A pure power is self-similar under zoom, so the dimensionless configuration of the last three points recurs, while at a simple root it tends to the linear prototype; the analogous pair yields the local order, $m=\ln L_f/\ln L_x$, and an exponential, self-similar by translation, is its limit $m=\infty$. Entering a halving regime when that analogy holds with $m\ge2$ — the condition under which a tangent step loses to a halving — splitting at the point the order predicts, and leaving when the split looks linear, together with four smaller readings of the same kind and one borrowed element, gives the method proposed here, A′. On the two sets it needs 2259 and 1074 evaluations against Chandrupatla's 2595 and 1505 and wins more problems than it loses on both, 103 to 31 and 31 to 10. The chain of decisions that produced it was selected on those sets; tested once, after its code was frozen, on 240 problems from six new families, it needs 3656 evaluations against Chandrupatla's 5717 and wins 177 problems to 28 — a corpus the author composed knowing where the method is strong: two of its six families have multiple or fractional roots, and without them the lead is 2403 against 3569, on the simple-root families alone 737 against 747. Over 855 problems in six corpora it is ahead of Brent, TOMS 748, ITP and the recent ModAB everywhere and stays within its own bound of twice bisection on every problem, a bound that Brent, TOMS 748 and ModAB, which do not promise it, exceed on 118, 84 and 9 problems; against Chandrupatla it is ahead in total on five corpora and level on the sixth. The lead is large on multiple, fractional and nearly flat roots and a matter of per cent on simple ones. Several of the resulting mechanisms coincide with classical ones — Lagouanelle's multiplicity estimate, false position on $|f|^{1/m}$ — reached here from similarity and analogy and recognised afterwards; what we found no counterpart for is the decision layer, where every choice — when to give the model up, where to split, when to return — is a judgment of similarity or of analogy, and the similarity that sets the step, whose form is decisive (replacing it costs 19% to 425%). +The wide-bracket gap turns out to be not the order of the model but a decision — when to give the model up — and the decision is a similarity after all, of an unfamiliar kind: the similarity of a run to its own past. A pure power is self-similar under zoom, so the dimensionless configuration of the last three points recurs, while at a simple root it tends to the linear prototype; the analogous pair yields the local order, $m=\ln L_f/\ln L_x$, and an exponential, self-similar by translation, is its limit $m=\infty$. Entering a halving regime when that analogy holds with $m\ge2$ — the condition under which a tangent step loses to a halving — splitting at the point the order predicts, and leaving when the split looks linear, together with four smaller readings of the same kind and one borrowed element, gives the method proposed here, A′. On the two sets it needs 2259 and 1074 evaluations against Chandrupatla's 2595 and 1505 and wins more problems than it loses on both, 103 to 31 and 31 to 10. The chain of decisions that produced it was selected on those sets; tested once, after its code was frozen, on 240 problems from six new families, it needs 3656 evaluations against Chandrupatla's 5717 and wins 177 problems to 28 — a corpus the author composed knowing where the method is strong: two of its six families have multiple or fractional roots, and without them the lead is 2403 against 3569, on the simple-root families alone 737 against 747. Against simpler detectors put in the place of the analogy — Lagouanelle's multiplicity estimate with the same memory, Chandrupatla's acceptance band, or none — it wins on that corpus in total and problem by problem (3656 against 4488, 6236 and 6165); against the classical transformation $\operatorname{sign}(f)|f|^{1/m}$ with the multiplicity given in advance it loses every multiple or fractional root, by a factor of 2.4 to 6.8 — the price of estimating the order. Over 855 problems in six corpora it is ahead of Brent, TOMS 748, ITP and the recent ModAB everywhere and stays within its own bound of twice bisection on every problem, a bound that Brent, TOMS 748 and ModAB, which do not promise it, exceed on 118, 84 and 9 problems; against Chandrupatla it is ahead in total on five corpora and level on the sixth. The lead is large on multiple, fractional and nearly flat roots and a matter of per cent on simple ones. Several of the resulting mechanisms coincide with classical ones — Lagouanelle's multiplicity estimate, false position on $|f|^{1/m}$ — reached here from similarity and analogy and recognised afterwards; what we found no counterpart for is the decision layer, where every choice — when to give the model up, where to split, when to return — is a judgment of similarity or of analogy, and the similarity that sets the step, whose form is decisive (replacing it costs 19% to 425%). --- @@ -425,7 +425,7 @@ Halving is the answer when nothing is known about the shape; inside the regime t $$t=\frac{|F_A|^{1/m}}{|F_A|^{1/m}+|F_B|^{1/m}},\tag{7}$$ -which is exact for a pure power and becomes the midpoint as $m\to\infty$. It is false position applied to $\operatorname{sign}(f)|f|^{1/m}$ — the transformation that turns a root of multiplicity $m$ into a simple one, classical since Schröder [16] and Traub [17]; what is new here is only where $m$ comes from. The order read from a *failed promise*, $(1-1/m)^m=r$, was tried first and gave $m=\infty$ in 163 of 174 entries, because the base step is not a pure tangent; the order read from the *analogy* is the one that works. With it the flat families of the wide-bracket set cost 743–847 evaluations against 1044 for pure bisection and 1068 for Chandrupatla. +which is exact for a pure power and becomes the midpoint as $m\to\infty$. It is false position applied to $\operatorname{sign}(f)|f|^{1/m}$ — the transformation that turns a root of multiplicity $m$ into a simple one, classical since Schröder [16] and Traub [17]; what is new here is only where $m$ comes from. With the multiplicity given in advance the transformation is far cheaper still (§9.1): the contribution is the estimate, and its price is measured there. The order read from a *failed promise*, $(1-1/m)^m=r$, was tried first and gave $m=\infty$ in 163 of 174 entries, because the base step is not a pure tangent; the order read from the *analogy* is the one that works. With it the flat families of the wide-bracket set cost 743–847 evaluations against 1044 for pure bisection and 1068 for Chandrupatla. ### 8.4 Five more readings @@ -503,7 +503,7 @@ Several mechanisms of A′ coincide with classical ones, and the coincidence is | the tolerance step | Dekker [9], Brent [2] — borrowed and named as such | | the exit judgment of §8.2 (the value at a split against the line) | ModAB's linearity check at the midpoint [10, 20], $|y_m-y_3|{\raggedright\arraybackslash}p{(\columnwidth - 12\tabcolsep) * \real{0.3291}} + >{\raggedright\arraybackslash}p{(\columnwidth - 12\tabcolsep) * \real{0.0759}} + >{\raggedright\arraybackslash}p{(\columnwidth - 12\tabcolsep) * \real{0.0759}} + >{\raggedright\arraybackslash}p{(\columnwidth - 12\tabcolsep) * \real{0.1013}} + >{\raggedright\arraybackslash}p{(\columnwidth - 12\tabcolsep) * \real{0.1139}} + >{\raggedright\arraybackslash}p{(\columnwidth - 12\tabcolsep) * \real{0.1392}} + >{\raggedright\arraybackslash}p{(\columnwidth - 12\tabcolsep) * \real{0.1646}}@{}} +\toprule\noalign{} +\begin{minipage}[b]{\linewidth}\raggedright +detector +\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright +APS +\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright +CHA +\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright +corpus 4 +\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright +six corpora +\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright +A′ on corpus 4 +\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright +\(p\) +\end{minipage} \\ +\midrule\noalign{} +\endhead +\bottomrule\noalign{} +\endlastfoot +\textbf{analogy (A′)} & 2259 & \textbf{1074} & \textbf{3656} & +\textbf{13032} & --- & \\ +Lagouanelle, with memory & \textbf{2235} & 1437 & 4488 & 15171 & +84/121/35 & \(8\cdot10^{-6}\) \\ +Chandrupatla's band & 2682 & 2526 & 6236 & 20668 & 110/96/34 & +\(1.5\cdot10^{-10}\) \\ +none & 2693 & 2526 & 6165 & 20749 & 113/87/40 & \(3\cdot10^{-9}\) \\ +\end{longtable} + +On corpus 4, the one test, A′ beats all three in total and problem by +problem, as the pre-registered criterion required. The competent +alternative is Lagouanelle's estimate: it is slightly better on the +canonical set (2235 against 2259), and on corpus 4 A′'s lead over it +lies in the multiple-root and fractional-power families (1163 against +1739, 924 against 1207); it is behind on \(\log(1+q^2)\) of a cubic (503 +against 477) and level on the rest. The band and the regime without a +detector are worse than no regime at all. The decision layer by analogy +therefore earns its place against the detectors a reader would try +first. + +\textbf{The multiplicity known in advance.} The second control asks how +good the classical transformation is when its multiplicity is given. +Library Chandrupatla and Brent are run on +\(g=\operatorname{sign}(f)\,|f|^{1/m^*}\), with \(m^*\) the order read +at the known root --- the slope of \(\ln|f|\) against \(\ln|x-r|\) on +both sides at \(h=10^{-5}\), rounded to 0.01, and 1 where either side +shows no zero (a jump). It is not an achievable method but an upper +bound. On the multiple and fractional roots, \(m^*\ge2\) or +\(m^*\le\tfrac23\) --- the branches of §8.1 where a tangent step loses +to a halving: + +\textbf{Table 9c.} Multiple and fractional roots with the multiplicity +given: totals, failures charged. A′ wins none of the 148 problems. + +\begin{longtable}[]{@{} + >{\raggedright\arraybackslash}p{(\columnwidth - 10\tabcolsep) * \real{0.1667}} + >{\raggedright\arraybackslash}p{(\columnwidth - 10\tabcolsep) * \real{0.1667}} + >{\raggedright\arraybackslash}p{(\columnwidth - 10\tabcolsep) * \real{0.1667}} + >{\raggedright\arraybackslash}p{(\columnwidth - 10\tabcolsep) * \real{0.1667}} + >{\raggedright\arraybackslash}p{(\columnwidth - 10\tabcolsep) * \real{0.1667}} + >{\raggedright\arraybackslash}p{(\columnwidth - 10\tabcolsep) * \real{0.1667}}@{}} +\toprule\noalign{} +\begin{minipage}[b]{\linewidth}\raggedright +corpus +\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright +problems +\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright +A′ +\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright +Chandrupatla on \(g\) +\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright +Brent on \(g\) +\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright +Chandrupatla on \(f\) +\end{minipage} \\ +\midrule\noalign{} +\endhead +\bottomrule\noalign{} +\endlastfoot +Chandrupatla & 20 & 654 & 96 & 66 & 1068 \\ +new corpus 1 & 40 & 1229 & 358 & 337 & 1792 \\ +corpus 4 & 88 & 2159 & 901 & 844 & 4151 \\ +\end{longtable} + +With the multiplicity known the transformation is 2.4 to 6.8 times +cheaper than A′. A′ is far ahead of Chandrupatla on \(f\) itself, which +does not know the multiplicity either. On the remaining problems the +transformed and untransformed runs differ little and A′ is close to +them: ahead on the canonical sets (2259 against 2640, 420 against 437 +for Chandrupatla on \(g\)), behind on corpora 1 and 4 (2061 against +1924, 1497 against 1427). So on multiple roots the contribution of A′ is +the estimate of the order without knowing it, and the gap to Table 9c is +the price of that estimate --- large, and the clearest direction for +improving the method. + \begin{center}\rule{0.5\linewidth}{0.5pt}\end{center} \hypertarget{negative-results}{% @@ -2183,11 +2323,15 @@ the local order, the order decides between the model and a halving by bisection's own \(\tfrac12\), and it says where to split. The method built on it, A′, is ahead of Chandrupatla on both canonical sets in total and problem by problem, and on a corpus fixed after its code was -frozen it needs 36\% fewer evaluations and wins 177 problems to 28. The -claim the measurements support is a lead over Brent, TOMS 748, ITP and -ModAB everywhere, within a bound of twice bisection that Brent, TOMS 748 -and ModAB do not promise and exceed, and over Chandrupatla a lead that -is large where roots are multiple, fractional or nearly flat and small +frozen it needs 36\% fewer evaluations and wins 177 problems to 28. Its +decision layer beats Lagouanelle's estimate, Chandrupatla's band and no +detector put in its place; with the multiplicity known in advance the +classical transformation is 2.4 to 6.8 times cheaper on multiple roots, +which measures what estimating the order costs. The claim the +measurements support is a lead over Brent, TOMS 748, ITP and ModAB +everywhere, within a bound of twice bisection that Brent, TOMS 748 and +ModAB do not promise and exceed, and over Chandrupatla a lead that is +large where roots are multiple, fractional or nearly flat and small where they are simple --- not superiority by the same margin everywhere. \texttt{fun9}, near-cancelling terms and smooth applied equations are where it is behind or level. @@ -2201,13 +2345,19 @@ hand is measured rather than asserted. \hypertarget{future-work}{% \subsubsection{Future work}\label{future-work}} -Two questions remain, both narrower than the one this section used to +Three questions remain, all narrower than the one this section used to pose. -The first is the steep flank. Where the loss remains --- \texttt{fun9}, +The first is the price of estimating the order. With the multiplicity +given, the transformation of §8.3 is 2.4 to 6.8 times cheaper than A′ on +multiple and fractional roots (Table 9c): the order read from the +analogous pair, or its use in the split, loses most of what the +transformation could deliver. + +The second is the steep flank. Where the loss remains --- \texttt{fun9}, the near-cancelling class, 4 − tan x at its pole --- the base step approaches along a flank far steeper than its model, and the half -contradiction of §8.4 removes only part of the cost. The second is the +contradiction of §8.4 removes only part of the cost. The third is the one geometric choice of §8.2, the Euclidean metric of the \((\xi,\varphi)\) plane: a metric derived from the configuration's own sensitivity would remove it. diff --git a/run_step2.sh b/run_step2.sh new file mode 100644 index 0000000..dec36e4 --- /dev/null +++ b/run_step2.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Revision step 2 close-out: verifier, .tex/PDF build, regression x2 - detached, with markers. +export OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1 +export PYTHONPATH="$HOME/simbracket_build/site:." +cd "$HOME/sim-numerics" || exit 1 +~/torchenv/bin/python -u verify_article.py > ~/verify_uniform.log 2>&1 +echo "verify OK $(grep -c '^ OK' ~/verify_uniform.log) BAD $(grep -c '^ BAD' ~/verify_uniform.log)" +grep '^ BAD' ~/verify_uniform.log +grep -A8 '^Traceback' ~/verify_uniform.log +~/torchenv/bin/python -u build_tex.py 2>&1 | tail -3 +for pass in 1 2; do + echo "=== PASS $pass ===" + ~/torchenv/bin/python -m pytest tests -q -p no:cacheprovider -n 6 2>&1 | tail -1 +done +echo "=== STEP2 DONE ===" diff --git a/verify_article.py b/verify_article.py index f740e78..cbd60c6 100644 --- a/verify_article.py +++ b/verify_article.py @@ -221,6 +221,7 @@ def main(): verify_safeguard_and_tables() verify_flat_regime_and_oos() verify_revision1() + verify_controls() print() if FAILS: @@ -1003,5 +1004,83 @@ def verify_revision1(): (5336, 4991)) +def verify_controls(): + """Section 9.1: the controls (PREREG_controls_2026-09-22, sweep_controls.json).""" + import math + print("\nSection 9.1 (controls, sweep_controls.json):") + R = _load("sweep_controls.json") + seen, four = _load("sweep_fourth_seen.json"), _load("sweep_fourth_fourth.json") + ap = dict(seen["Aprime"]) + ap.update(four["Aprime"]) + chf = dict(seen["chandrupatla"]) + chf.update(four["chandrupatla"]) + sets = ("APS", "CHA", "NEW", "RJL", "APP", "D") + + def cost(v): + return v[0] if v[1] else 200 + + def tot(r, keep=None): + return sum(cost(v) for p, v in r.items() if keep is None or keep(p)) + + def h2h(a, b, keep=None): + ps = [p for p in a if keep is None or keep(p)] + w = sum(cost(a[p]) < cost(b[p]) for p in ps) + l = sum(cost(a[p]) > cost(b[p]) for p in ps) + return w, len(ps) - w - l, l + + def sign_p(w, l): + k, n = min(w, l), w + l + return min(1.0, 2.0 * sum(math.comb(n, i) for i in range(k + 1)) / 2.0 ** n) + rows = {"K2L": (2235, 1437, 4026, 1495, 1490, 4488, 15171), + "K2P": (2682, 2526, 5485, 2117, 1622, 6236, 20668), + "K2F": (2693, 2526, 5569, 2126, 1670, 6165, 20749)} + check("Table 9b A' row", tuple(tot(ap[s]) for s in sets) + (sum(tot(ap[s]) for s in sets),), + (2259, 1074, 3290, 1371, 1382, 3656, 13032)) + for k, want in rows.items(): + k2 = R["K2"][k] + check(f"Table 9b {k} row", tuple(tot(k2[s]) for s in sets) + (sum(tot(k2[s]) for s in sets),), + want) + for k, wtl, pp in (("K2L", (84, 121, 35), ("1g", "8e-06")), ("K2P", (110, 96, 34), ("2g", "1.5e-10")), + ("K2F", (113, 87, 40), ("1g", "3e-09"))): + got = h2h(ap["D"], R["K2"][k]["D"]) + check(f"Table 9b A' vs {k} on corpus 4", got, wtl) + check(f"Table 9b p vs {k}", format(sign_p(got[0], got[2]), "." + pp[0]), pp[1]) + check("Table 9b violations", sum(v[2] for k in rows for s in sets for v in R["K2"][k][s].values()), 0) + for cls, want in (("D2", (1163, 1739)), ("D3", (924, 1207)), ("D5", (503, 477))): + keep = lambda p, c=cls: p.startswith(c) # noqa: E731 + check(f"9.1 {cls}: A' vs Lagouanelle", (tot(ap["D"], keep), tot(R["K2"]["K2L"]["D"], keep)), want) + level = [(tot(ap["D"], lambda p, c=c: p.startswith(c)), + tot(R["K2"]["K2L"]["D"], lambda p, c=c: p.startswith(c))) for c in ("D1", "D4", "D6")] + check("9.1 level on the rest (within 1.5%)", all(abs(a - b) <= 0.015 * b for a, b in level), True) + ab = _load("sweep_ablation_all.json")["abl-regime"] + no_regime_all = sum(tot(ab[s]) for s in sets) + check("band and no-detector worse than no regime (six corpora)", + all(sum(tot(R["K2"][k][s]) for s in sets) > no_regime_all for k in ("K2P", "K2F")), True) + check("band and no-detector worse than no regime (corpus 4)", + all(tot(R["K2"][k]["D"]) > tot(ab["D"]) for k in ("K2P", "K2F")), True) + t9c = {"CHA": (20, 654, 96, 66, 1068), "NEW": (40, 1229, 358, 337, 1792), + "D": (88, 2159, 901, 844, 4151)} + nonsimple = lambda m: m >= 2.0 or m <= 2.0 / 3.0 # noqa: E731 + wins = 0 + for s, want in t9c.items(): + k1 = R["K1"][s] + keep = lambda p, _m=k1["mstar"]: nonsimple(_m[p]) # noqa: E731 + got = (sum(1 for p in k1["mstar"] if keep(p)), tot(ap[s], keep), tot(k1["chandrupatla_g"], keep), + tot(k1["brent_g"], keep), tot(chf[s], keep)) + check(f"Table 9c {s}", got, want) + wins += h2h({p: v for p, v in ap[s].items() if keep(p)}, k1["chandrupatla_g"])[0] + check("Table 9c: A' wins none of the 148", wins, 0) + check("Table 9c: 2.4 to 6.8 times", (round(2159 / 901, 1), round(654 / 96, 1), + min(round(a / c, 1) for _n, a, c, _b, _f in t9c.values()), + max(round(a / c, 1) for _n, a, c, _b, _f in t9c.values())), + (2.4, 6.8, 2.4, 6.8)) + APS_ = "APS" + for s, want in ((APS_, (2259, 2640)), ("CHA", (420, 437)), ("NEW", (2061, 1924)), ("D", (1497, 1427))): + k1 = R["K1"][s] + keep = lambda p, _m=k1["mstar"]: not nonsimple(_m[p]) # noqa: E731 + check(f"9.1 rest {s}: A' vs Chandrupatla on g", (tot(ap[s], keep), tot(k1["chandrupatla_g"], keep)), + want) + + if __name__ == "__main__": main()