Revision 3: exact-zero stop in section 4; Table 12 with the post hoc Lagouanelle variant (branch m>=2: corpus 4 5568, six corpora 18372, multiple/fractional 6540, APS 2960; A' 178/34/28), the controls' missing exit in its caption; the reading of Table 12 restated in 7.1, 6.6 and the conclusion - the analogy order transfers to the classical frame and works better there than in A's split (2809 vs 4042 on multiple roots), and A's layer as a whole (detector with exit) earns its place (+1233 vs -3934). Server: verify 548 OK / 0 BAD, regression x2 1239 passed, PDF 32 pages, 0 errors, 0 overfull.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
vvs
2026-09-22 09:31:40 +01:00
co-authored by Claude Opus 5
parent aa304cd262
commit 1eb2c1664f
9 changed files with 244 additions and 7 deletions
+49
View File
@@ -0,0 +1,49 @@
"""Revision 3 into article/sim_numerics.md:
4 the exact-zero stop stated in section 4 (it holds for every method on every corpus)
5 Table 12: the post hoc Lagouanelle variant on the branch m >= 2 (marked), the missing exit of
the controls in the caption, and the conclusion from Table 12 restated in 7.1, 6.6 and 8
Exact anchors, each found exactly once, or nothing is written.
python -u apply_article_revision3.py
"""
from __future__ import annotations
PATH = "article/sim_numerics.md"
R = []
def E(old, new):
R.append((old, new))
E("$x$-tolerance $10^{-12}$; budget 200 evaluations.",
"$x$-tolerance $10^{-12}$; budget 200 evaluations. Every method, ours included, stops when its bracket is no wider than $\\varepsilon_x$ or when it evaluates an exact zero of the function it works on; none stops on a small $|f|$.")
E("**Table 12.** Chandrupatla on the transformed function: the multiplicity estimated during the run, against A′, the oracle of Table 11 and Chandrupatla on $f$. Totals, failures charged; the 148 multiple and fractional roots of Table 11, and the simple-root canonical set.",
"**Table 12.** Chandrupatla on the transformed function: the multiplicity estimated during the run, against A′, the oracle of Table 11 and Chandrupatla on $f$. Totals, failures charged; the 148 multiple and fractional roots of Table 11, and the simple-root canonical set. The estimated forms have no exit: an order once taken is held to the end of the run. A′ leaves its regime by the exit of §6.2.")
E("| Chandrupatla on $g$, $\\hat m$ by Lagouanelle | 7526 | 30991 | 6041 | 7215 |",
"| Chandrupatla on $g$, $\\hat m$ by Lagouanelle | 7526 | 30991 | 6041 | 7215 |\n| the same, branch $m\\ge2$ only (post hoc) | 5568 | 18372 | 6540 | 2960 |")
E("(A variant restricted to the branch $m\\ge2$ was not among the controls recorded.)",
"Restricted after the fact to the branch $m\\ge2$ (post hoc, `POSTHOC_lag_branch_2026-09-22.md`), it no longer breaks, but in this frame it hardly sees a multiple root either: 6540 on the 148 multiple and fractional roots against 7011 for Chandrupatla on $f$, and 5568 on corpus 4, with A′ ahead 178/34/28.")
E("So the decision layer of A′ earns its place against the classical method with an estimated order as well; and the split (7), not the order estimate, is where A′ leaves most to the transformation.",
"Two conclusions follow. First, the order read by analogy transfers to the classical frame and works better there than in A′'s own split: on the multiple and fractional roots Chandrupatla's interpolation with that order beats A′ itself (2809 against 4042). Second, A′'s decision layer as a whole — the detector together with its exit — earns its place: without an exit the transferred detector loses more on simple roots than it gains on multiple ones, and A′ stays ahead on corpus 4 and over the six corpora.")
E("§7.1 puts this claim against the detectors a reader would try first, and against the transformation with the multiplicity known in advance or estimated during the run.",
"§7.1 tests this against the detectors a reader would try first and against the classical transformation with the multiplicity known or estimated, and the outcome is sharper than the claim: the order read by analogy transfers to the classical frame and works better there than in A′'s own split, while A′'s exit is what keeps simple roots cheap.")
E("Its decision layer beats Lagouanelle's estimate, Chandrupatla's band and no detector put in its place, and Chandrupatla's method on the transformed function with the order estimated during the run;",
"Its detector, the order read by analogy, beats Lagouanelle's estimate, Chandrupatla's band and no detector put in its place, and it transfers: inside Chandrupatla's interpolation on the transformed function it does better on multiple roots than A′'s own split, while only A′'s exit keeps simple roots cheap;")
def main():
src = open(PATH, encoding="utf8").read()
bad = [o[:80] for o, _ in R if src.count(o) != 1]
if bad:
for b in bad:
print("ANCHOR NOT FOUND EXACTLY ONCE:", repr(b))
raise SystemExit(f"{len(bad)} anchors failed; nothing written")
for o, n in R:
src = src.replace(o, n)
open(PATH, "w", encoding="utf8").write(src)
print(f"applied {len(R)}")
if __name__ == "__main__":
main()
+48
View File
@@ -0,0 +1,48 @@
"""verify_article.py checks the round-3 additions: the post hoc row of Table 12
(sweep_estimated_posthoc.json), A' against it on corpus 4, its fractional-free holds, and the
statement that the transferred detector loses more on simple roots than it gains on multiple ones.
python -u apply_verify_revision3.py
"""
PATH = "verify_article.py"
FUNC = '''
def verify_revision3():
"""Revision 3 (point 5): the post hoc branch m >= 2 and the reading of Table 12."""
print("\\nRevision 3:")
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))
sets = ("APS", "CHA", "NEW", "RJL", "APP", "D")
P = _load("sweep_estimated_posthoc.json")
K = _load("sweep_estimated.json")["k3_analogy"]
seen, four = _load("sweep_fourth_seen.json"), _load("sweep_fourth_fourth.json")
ap = dict(seen["Aprime"])
ap.update(four["Aprime"])
K1 = _load("sweep_controls.json")["K1"]
nonsimple = lambda m: m >= 2.0 or m <= 2.0 / 3.0 # noqa: E731
def group(r):
return sum(tot(r[s], lambda p, _m=K1[s]["mstar"]: nonsimple(_m[p])) for s in ("CHA", "NEW", "D"))
check("Table 12 post hoc row", (tot(P["D"]), sum(tot(P[s]) for s in sets), group(P), tot(P["APS"])),
(5568, 18372, 6540, 2960))
ps = list(ap["D"])
w = sum(cost(ap["D"][p]) < cost(P["D"][p]) for p in ps)
l = sum(cost(ap["D"][p]) > cost(P["D"][p]) for p in ps)
check("A' vs the post hoc variant on corpus 4", (w, len(ps) - w - l, l), (178, 34, 28))
check("post hoc violations", sum(v[2] for s in sets for v in P[s].values()), 0)
gain = group(ap) - group(K)
loss = (sum(tot(K[s]) for s in sets) - group(K)) - (sum(tot(ap[s]) for s in sets) - group(ap))
check("transferred detector: gain on multiple roots < loss elsewhere (vs A')", (gain, loss, gain < loss),
(1233, 3934, True))
'''
src = open(PATH, encoding="utf8").read()
call_old = " verify_revision2()\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, " verify_revision2()\n verify_revision3()\n\n print()").replace(marker, FUNC + marker)
open(PATH, "w", encoding="utf8").write(src)
print("added verify_revision3")
+6 -5
View File
@@ -183,7 +183,7 @@ Three remarks apply to both forms.
**Problem sets.** The 154 problems of Alefeld, Potra and Shi [1] and the 45 of Chandrupatla [3], taken programmatically from SciPy's `scipy.optimize._tstutils` so that no bracket or root is transcribed by hand. **Problem sets.** The 154 problems of Alefeld, Potra and Shi [1] and the 45 of Chandrupatla [3], taken programmatically from SciPy's `scipy.optimize._tstutils` so that no bracket or root is transcribed by hand.
**Cost model.** Evaluations of $f$. A run counts as solved when it reports convergence and $f$ changes sign within $10^{-6}$ of the returned point, or vanishes there — the bracketing notion of a root, the same criterion on every corpus of this paper; $x$-tolerance $10^{-12}$; budget 200 evaluations. Scoring against the published root instead, as the benchmarks' harness does, charges every method the full budget on six problems where $f$ vanishes identically around the root in floating point (§A.5). Head-to-head counts (wins/ties/losses) are taken over every problem of a set with the same charging as the totals. *Failures are charged the full budget*, so no method can buy a low total by giving up early, and totals are reported over all problems rather than over the subset a method happened to solve. Rankings are by failures first, then by total. **Cost model.** Evaluations of $f$. A run counts as solved when it reports convergence and $f$ changes sign within $10^{-6}$ of the returned point, or vanishes there — the bracketing notion of a root, the same criterion on every corpus of this paper; $x$-tolerance $10^{-12}$; budget 200 evaluations. Every method, ours included, stops when its bracket is no wider than $\varepsilon_x$ or when it evaluates an exact zero of the function it works on; none stops on a small $|f|$. Scoring against the published root instead, as the benchmarks' harness does, charges every method the full budget on six problems where $f$ vanishes identically around the root in floating point (§A.5). Head-to-head counts (wins/ties/losses) are taken over every problem of a set with the same charging as the totals. *Failures are charged the full budget*, so no method can buy a low total by giving up early, and totals are reported over all problems rather than over the subset a method happened to solve. Rankings are by failures first, then by total.
**Competitors.** Where SciPy ships the method, the competitor is the library — Chandrupatla is `scipy.optimize.elementwise.find_root`, Brent `brentq`, TOMS 748 `toms748` — run with the stopping rule of every method (a bracket no wider than $\varepsilon_x$, the relative term at machine precision, no stopping on $|f|$) and counted by the same wrapper around $f$. ITP, which has no library implementation, is ours, run with $\epsilon=\varepsilon_x/2$ because it terminates on $b-a\le2\epsilon$, checked against its own theorem ($n_{1/2}+n_0$ iterations at most, on all 154 problems for $n_0\in\{0,1,2,5\}$), and reported both at the authors' constants and as a *per-problem oracle* over a $(\kappa_1,n_0)$ grid — not an achievable method, included so that it cannot be said to have been handicapped. Bisection and the false-position family are ours. **Competitors.** Where SciPy ships the method, the competitor is the library — Chandrupatla is `scipy.optimize.elementwise.find_root`, Brent `brentq`, TOMS 748 `toms748` — run with the stopping rule of every method (a bracket no wider than $\varepsilon_x$, the relative term at machine precision, no stopping on $|f|$) and counted by the same wrapper around $f$. ITP, which has no library implementation, is ours, run with $\epsilon=\varepsilon_x/2$ because it terminates on $b-a\le2\epsilon$, checked against its own theorem ($n_{1/2}+n_0$ iterations at most, on all 154 problems for $n_0\in\{0,1,2,5\}$), and reported both at the authors' constants and as a *per-problem oracle* over a $(\kappa_1,n_0)$ grid — not an achievable method, included so that it cannot be said to have been handicapped. Bisection and the false-position family are ours.
@@ -435,7 +435,7 @@ Several mechanisms of A′ coincide with classical ones, reached from similarity
| the tolerance step | Dekker [9], Brent [2] — borrowed and named as such | | the tolerance step | Dekker [9], Brent [2] — borrowed and named as such |
| the exit judgment of §6.2 (the value at a split against the line) | ModAB's linearity check at the midpoint [10, 20], $|y_m-y_3|<k\,(|y_3|+|y_m|)$ with $k$ from a symmetry factor; ours compares two predictions, the line and 0, without a factor | | the exit judgment of §6.2 (the value at a split against the line) | ModAB's linearity check at the midpoint [10, 20], $|y_m-y_3|<k\,(|y_3|+|y_m|)$ with $k$ from a symmetry factor; ours compares two predictions, the line and 0, without a factor |
What we found no counterpart for, in the literature we could reach, is the part that carries the gain in Table 6: the *decision* layer. A bracketing method that recognises the regime in which its model has lost by the similarity of the run to its own past — the recurrence of a dimensionless configuration against the linear prototype — reads the order from the analogous pair itself, distinguishes similarity by scaling from similarity by translation, and leaves the regime by a similarity judgment at a point the model did not choose; and a step whose length is set by a similarity function whose form, measured, is decisive. The methods for multiple roots we found are single-point iterations without a bracket, with the multiplicity assumed, estimated from derivatives, or removed by a transformation ([17, 23]; Roots.jl's `Order1B` and `Order2B` [12]); inside a bracket, [22] needs the multiplicity given and recognises a multiple root only by the number of evaluations spent, against a third of bisection's; Chandrupatla takes the decision to halve by an empirical acceptance band [3]. §7.1 puts this claim against the detectors a reader would try first, and against the transformation with the multiplicity known in advance or estimated during the run. What we found no counterpart for, in the literature we could reach, is the part that carries the gain in Table 6: the *decision* layer. A bracketing method that recognises the regime in which its model has lost by the similarity of the run to its own past — the recurrence of a dimensionless configuration against the linear prototype — reads the order from the analogous pair itself, distinguishes similarity by scaling from similarity by translation, and leaves the regime by a similarity judgment at a point the model did not choose; and a step whose length is set by a similarity function whose form, measured, is decisive. The methods for multiple roots we found are single-point iterations without a bracket, with the multiplicity assumed, estimated from derivatives, or removed by a transformation ([17, 23]; Roots.jl's `Order1B` and `Order2B` [12]); inside a bracket, [22] needs the multiplicity given and recognises a multiple root only by the number of evaluations spent, against a third of bisection's; Chandrupatla takes the decision to halve by an empirical acceptance band [3]. §7.1 tests this against the detectors a reader would try first and against the classical transformation with the multiplicity known or estimated, and the outcome is sharper than the claim: the order read by analogy transfers to the classical frame and works better there than in A′'s own split, while A′'s exit is what keeps simple roots cheap.
--- ---
@@ -525,17 +525,18 @@ With the multiplicity known the transformation is 2.4 to 6.8 times cheaper than
**The multiplicity estimated during the run.** Between A′ and the oracle lies the natural classical method: Chandrupatla's interpolation on $g=\operatorname{sign}(f)|f|^{1/\hat m}$ with $\hat m$ estimated during the run. Our implementation of Chandrupatla's method (identical to the library on all 199 problems, and to itself when $\hat m\equiv1$) keeps the true $f$ at its three points and reads $G(v)=\operatorname{sign}(v)|v|^{1/\hat m}$ only in the acceptance band and the interpolation; $\hat m$ starts at 1 and takes an order read from the last three evaluations whenever that order lies on a branch of §6.1 ($m\ge2$ or $0<m\le\tfrac23$), without reset — the hysteresis of the controls above. The order is read either by Lagouanelle's estimate or by A′'s own analogy (recorded in advance, `sweep_estimated.py`): **The multiplicity estimated during the run.** Between A′ and the oracle lies the natural classical method: Chandrupatla's interpolation on $g=\operatorname{sign}(f)|f|^{1/\hat m}$ with $\hat m$ estimated during the run. Our implementation of Chandrupatla's method (identical to the library on all 199 problems, and to itself when $\hat m\equiv1$) keeps the true $f$ at its three points and reads $G(v)=\operatorname{sign}(v)|v|^{1/\hat m}$ only in the acceptance band and the interpolation; $\hat m$ starts at 1 and takes an order read from the last three evaluations whenever that order lies on a branch of §6.1 ($m\ge2$ or $0<m\le\tfrac23$), without reset — the hysteresis of the controls above. The order is read either by Lagouanelle's estimate or by A′'s own analogy (recorded in advance, `sweep_estimated.py`):
**Table 12.** Chandrupatla on the transformed function: the multiplicity estimated during the run, against A′, the oracle of Table 11 and Chandrupatla on $f$. Totals, failures charged; the 148 multiple and fractional roots of Table 11, and the simple-root canonical set. **Table 12.** Chandrupatla on the transformed function: the multiplicity estimated during the run, against A′, the oracle of Table 11 and Chandrupatla on $f$. Totals, failures charged; the 148 multiple and fractional roots of Table 11, and the simple-root canonical set. The estimated forms have no exit: an order once taken is held to the end of the run. A′ leaves its regime by the exit of §6.2.
| method | corpus 4 | six corpora | multiple/fractional (148) | APS | | method | corpus 4 | six corpora | multiple/fractional (148) | APS |
|---|---|---|---|---| |---|---|---|---|---|
| **A′** | **3656** | **13032** | 4042 | **2259** | | **A′** | **3656** | **13032** | 4042 | **2259** |
| Chandrupatla on $g$, $\hat m$ by analogy | 3806 | 15733 | 2809 | 3453 | | Chandrupatla on $g$, $\hat m$ by analogy | 3806 | 15733 | 2809 | 3453 |
| Chandrupatla on $g$, $\hat m$ by Lagouanelle | 7526 | 30991 | 6041 | 7215 | | Chandrupatla on $g$, $\hat m$ by Lagouanelle | 7526 | 30991 | 6041 | 7215 |
| the same, branch $m\ge2$ only (post hoc) | 5568 | 18372 | 6540 | 2960 |
| Chandrupatla on $g$, $m^*$ known (Table 11) | — | — | **1355** | — | | Chandrupatla on $g$, $m^*$ known (Table 11) | — | — | **1355** | — |
| Chandrupatla on $f$ | 5717 | 16481 | 7011 | 2595 | | Chandrupatla on $f$ | 5717 | 16481 | 7011 | 2595 |
A′ is ahead of both estimated forms on corpus 4, in total and problem by problem (against the analogy form 126/38/76, $p=5\cdot10^{-4}$), and over the six corpora. The two estimators behave differently in this frame. Lagouanelle's reads the parabola through Chandrupatla's points, which alternate sides of the root; there it reports a fractional order on 115 of the 154 simple roots of the canonical set, holds it, and turns the method into little more than bisection. (A variant restricted to the branch $m\ge2$ was not among the controls recorded.) The analogy's order is the better one here too, and on the multiple and fractional roots it does better inside Chandrupatla's interpolation than inside A′'s split: it closes 59%, 58% and 31% of the gap from A′ to the oracle on the Chandrupatla set, corpus 1 and corpus 4 (322 against 654, 721 against 1229, 1766 against 2159). It pays on simple roots, where it has no exit to take a false reading back (3453 against 2595 on the canonical set). So the decision layer of A′ earns its place against the classical method with an estimated order as well; and the split (7), not the order estimate, is where A′ leaves most to the transformation. A′ is ahead of both estimated forms on corpus 4, in total and problem by problem (against the analogy form 126/38/76, $p=5\cdot10^{-4}$), and over the six corpora. The two estimators behave differently in this frame. Lagouanelle's reads the parabola through Chandrupatla's points, which alternate sides of the root; there it reports a fractional order on 115 of the 154 simple roots of the canonical set, holds it, and turns the method into little more than bisection. Restricted after the fact to the branch $m\ge2$ (post hoc, `POSTHOC_lag_branch_2026-09-22.md`), it no longer breaks, but in this frame it hardly sees a multiple root either: 6540 on the 148 multiple and fractional roots against 7011 for Chandrupatla on $f$, and 5568 on corpus 4, with A′ ahead 178/34/28. The analogy's order is the better one here too, and on the multiple and fractional roots it does better inside Chandrupatla's interpolation than inside A′'s split: it closes 59%, 58% and 31% of the gap from A′ to the oracle on the Chandrupatla set, corpus 1 and corpus 4 (322 against 654, 721 against 1229, 1766 against 2159). It pays on simple roots, where it has no exit to take a false reading back (3453 against 2595 on the canonical set). Two conclusions follow. First, the order read by analogy transfers to the classical frame and works better there than in A′'s own split: on the multiple and fractional roots Chandrupatla's interpolation with that order beats A′ itself (2809 against 4042). Second, A′'s decision layer as a whole — the detector together with its exit — earns its place: without an exit the transferred detector loses more on simple roots than it gains on multiple ones, and A′ stays ahead on corpus 4 and over the six corpora.
--- ---
@@ -545,7 +546,7 @@ A root finder can be derived from a similarity function alone, and the derivatio
The base rule is level with TOMS 748 and 1.5% behind Chandrupatla's method on the canonical set, and clearly behind Chandrupatla on wide brackets (1505 against 2357); the cause is not the order of the model but the decision to give it up. The base rule is level with TOMS 748 and 1.5% behind Chandrupatla's method on the canonical set, and clearly behind Chandrupatla on wide brackets (1505 against 2357); the cause is not the order of the model but the decision to give it up.
That decision is a similarity too, of the run to its own past: a pure power repeats its configuration under zoom, an exponential under translation, a simple root tends to the line, and the analogous pair reads the order that decides between the model and a split. The method built on it, A′, is ahead of Chandrupatla on both canonical sets, and on a corpus fixed before its code was 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, and Chandrupatla's method on the transformed function with the order estimated during the run; 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. `fun9`, near-cancelling terms and smooth applied equations are where it is behind or level. That decision is a similarity too, of the run to its own past: a pure power repeats its configuration under zoom, an exponential under translation, a simple root tends to the line, and the analogous pair reads the order that decides between the model and a split. The method built on it, A′, is ahead of Chandrupatla on both canonical sets, and on a corpus fixed before its code was frozen it needs 36% fewer evaluations and wins 177 problems to 28. Its detector, the order read by analogy, beats Lagouanelle's estimate, Chandrupatla's band and no detector put in its place, and it transfers: inside Chandrupatla's interpolation on the transformed function it does better on multiple roots than A′'s own split, while only A′'s exit keeps simple roots cheap; 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. `fun9`, near-cancelling terms and smooth applied equations are where it is behind or level.
### Future work ### Future work
+8
View File
@@ -0,0 +1,8 @@
10:04:29 up 20 days, 19:02, 5 users, load average: 0.19, 0.90, 0.86
7 passed in 1.37s
K3-L2 (post hoc): corpus 4 5568 six corpora 18372 multiple/fractional 6540 APS 2960
A' on corpus 4 against it: 178/34/28 p=6.8e-28
per corpus: APS 2960 CHA 1751 NEW 4698 RJL 1676 APP 1719 D 5568
violations 0
APS runs ending with m_hat != 1: 11 of 154
posthoc done
+5 -2
View File
@@ -17,7 +17,8 @@ import math
from .baselines import Counter, _Budget from .baselines import Counter, _Budget
from .pure import _analogy_order, _config3, _lagouanelle_raw from .pure import _analogy_order, _config3, _lagouanelle_raw
ESTIMATORS = ("none", "lag", "analogy") # "lag2": POSTHOC_lag_branch_2026-09-22 - Lagouanelle on the branch m >= 2 only (post hoc)
ESTIMATORS = ("none", "lag", "analogy", "lag2")
def _G(v, inv): def _G(v, inv):
@@ -49,8 +50,10 @@ def chandrupatla_transformed(f, a, b, xtol, maxfev, estimator="none", stats=None
if estimator == "none" or len(hist) < 3: if estimator == "none" or len(hist) < 3:
return return
(xp2, fp2), (xp, fp), (xc, fc) = hist[-3], hist[-2], hist[-1] (xp2, fp2), (xp, fp), (xc, fc) = hist[-3], hist[-2], hist[-1]
if estimator == "lag": if estimator in ("lag", "lag2"):
m = _lagouanelle_raw(xc, fc, xp, fp, xp2, fp2) m = _lagouanelle_raw(xc, fc, xp, fp, xp2, fp2)
if estimator == "lag2" and m is not None and m < 2.0:
m = None # the fractional branch is not used (post hoc variant)
else: else:
cur = _config3(xc, fc, xp, fp, xp2, fp2) cur = _config3(xc, fc, xp, fp, xp2, fp2)
m = _analogy_order(cur, configs, translation=True) m = _analogy_order(cur, configs, translation=True)
File diff suppressed because one or more lines are too long
+84
View File
@@ -0,0 +1,84 @@
"""POSTHOC_lag_branch_2026-09-22: Chandrupatla on sign(f)|f|^(1/m_hat) with Lagouanelle's estimate
restricted to the branch m >= 2 (K3-L2), post hoc, with the harness of sweep_fourth.py. The
registered K3 run (sweep_estimated.py / .json) is not touched.
python -u sweep_estimated_posthoc.py run -> sweep_estimated_posthoc.json, report
python -u sweep_estimated_posthoc.py --report
"""
from __future__ import annotations
import functools
import json
import sys
from multiprocessing import Pool
import sweep_fourth
import sweep_full
from simroot.transformed import chandrupatla_transformed
from sweep_controls import nonsimple
from sweep_oos import sign_p
sweep_full.EXTRA_BASELINES["k3_lag2"] = functools.partial(chandrupatla_transformed, estimator="lag2")
sweep_fourth.CONFIGS["k3_lag2"] = ("base", "k3_lag2")
SETS = ("APS", "CHA", "NEW", "RJL", "APP", "D")
def job(which):
return sweep_fourth.evaluate(("k3_lag2", which))[1]
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 held_on_simple_roots():
"""How many APS runs end holding m_hat != 1 (fresh)."""
n = 0
for s, pid, f, a, b, _r in sweep_full.load_problems():
if s != "APS":
continue
st = {}
chandrupatla_transformed(f, a, b, 1e-12, 200, estimator="lag2", stats=st)
n += st.get("m_hat", 1.0) != 1.0
return n
def report(R):
seen = json.load(open("sweep_fourth_seen.json", encoding="utf8"))
four = json.load(open("sweep_fourth_fourth.json", encoding="utf8"))
ap = dict(seen["Aprime"])
ap.update(four["Aprime"])
K1 = json.load(open("sweep_controls.json", encoding="utf8"))["K1"]
grp = sum(tot(R[s], lambda p, _m=K1[s]["mstar"]: nonsimple(_m[p])) for s in ("CHA", "NEW", "D"))
ps = list(ap["D"])
w = sum(cost(ap["D"][p]) < cost(R["D"][p]) for p in ps)
l = sum(cost(ap["D"][p]) > cost(R["D"][p]) for p in ps)
print(f"K3-L2 (post hoc): corpus 4 {tot(R['D'])} six corpora {sum(tot(R[s]) for s in SETS)} "
f"multiple/fractional {grp} APS {tot(R['APS'])}")
print(f" A' on corpus 4 against it: {w}/{len(ps) - w - l}/{l} p={sign_p(w, l):.2g}")
print(f" per corpus: " + " ".join(f"{s} {tot(R[s])}" for s in SETS))
print(f" violations {sum(v[2] for s in SETS for v in R[s].values())}")
print(f" APS runs ending with m_hat != 1: {held_on_simple_roots()} of 154")
def main():
if "--report" not in sys.argv:
with Pool(2) as pool:
parts = pool.map(job, ("seen", "fourth"))
R = {}
for p in parts:
R.update(p)
with open("sweep_estimated_posthoc.json", "w", encoding="utf8") as fh:
json.dump(R, fh)
with open("sweep_estimated_posthoc.json", encoding="utf8") as fh:
R = json.load(fh)
report(R)
print("posthoc done")
if __name__ == "__main__":
main()
+9
View File
@@ -23,6 +23,15 @@ def test_without_an_estimate_it_is_chandrupatla_bit_for_bit():
assert not bad, bad[:5] assert not bad, bad[:5]
def test_the_post_hoc_branch_never_holds_a_fractional_order():
"""POSTHOC_lag_branch_2026-09-22: 'lag2' takes only readings m >= 2."""
for s, pid, f, a, b, _r in PROBS:
st = {}
x, n, ok = chandrupatla_transformed(f, a, b, 1e-12, 200, estimator="lag2", stats=st)
assert min(a, b) <= x <= max(a, b), pid
assert st.get("m_hat", 1.0) == 1.0 or st["m_hat"] >= 2.0, (pid, st)
@pytest.mark.parametrize("estimator", ("lag", "analogy")) @pytest.mark.parametrize("estimator", ("lag", "analogy"))
def test_with_an_estimate_it_still_brackets(estimator): def test_with_an_estimate_it_still_brackets(estimator):
for s, pid, f, a, b, _r in PROBS: for s, pid, f, a, b, _r in PROBS:
+34
View File
@@ -223,6 +223,7 @@ def main():
verify_revision1() verify_revision1()
verify_controls() verify_controls()
verify_revision2() verify_revision2()
verify_revision3()
print() print()
if FAILS: if FAILS:
@@ -1180,5 +1181,38 @@ def verify_revision2():
check(f"Table 3 row {lb}", (c["total"], c["solved"], c["over"]), want_) check(f"Table 3 row {lb}", (c["total"], c["solved"], c["over"]), want_)
def verify_revision3():
"""Revision 3 (point 5): the post hoc branch m >= 2 and the reading of Table 12."""
print("\nRevision 3:")
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))
sets = ("APS", "CHA", "NEW", "RJL", "APP", "D")
P = _load("sweep_estimated_posthoc.json")
K = _load("sweep_estimated.json")["k3_analogy"]
seen, four = _load("sweep_fourth_seen.json"), _load("sweep_fourth_fourth.json")
ap = dict(seen["Aprime"])
ap.update(four["Aprime"])
K1 = _load("sweep_controls.json")["K1"]
nonsimple = lambda m: m >= 2.0 or m <= 2.0 / 3.0 # noqa: E731
def group(r):
return sum(tot(r[s], lambda p, _m=K1[s]["mstar"]: nonsimple(_m[p])) for s in ("CHA", "NEW", "D"))
check("Table 12 post hoc row", (tot(P["D"]), sum(tot(P[s]) for s in sets), group(P), tot(P["APS"])),
(5568, 18372, 6540, 2960))
ps = list(ap["D"])
w = sum(cost(ap["D"][p]) < cost(P["D"][p]) for p in ps)
l = sum(cost(ap["D"][p]) > cost(P["D"][p]) for p in ps)
check("A' vs the post hoc variant on corpus 4", (w, len(ps) - w - l, l), (178, 34, 28))
check("post hoc violations", sum(v[2] for s in sets for v in P[s].values()), 0)
gain = group(ap) - group(K)
loss = (sum(tot(K[s]) for s in sets) - group(K)) - (sum(tot(ap[s]) for s in sets) - group(ap))
check("transferred detector: gain on multiple roots < loss elsewhere (vs A')", (gain, loss, gain < loss),
(1233, 3934, True))
if __name__ == "__main__": if __name__ == "__main__":
main() main()