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>
This commit is contained in:
vvs
2026-09-21 10:53:00 +01:00
co-authored by Claude Opus 5
parent de90c7a089
commit 1b6c626f67
32 changed files with 950 additions and 186 deletions
+50 -53
View File
@@ -8,9 +8,9 @@ V. Sitnikov
Bisection places the next iterate at the midpoint because it uses no information about the function's value. We replace that rule with a *similarity* rule: the step is proportional to how *dissimilar* the current outcome $f(x)$ is to the expectation $0$, measured by a similarity function $S:\mathbb{R}\to[0,1]$ with $S(0)=1$. We derive the contraction factor of the resulting iteration in closed form; it predicts a linear rate to four digits when $1-S$ has a non-zero slope at the origin, and a sublinear $e_n\sim C/n$ with a predicted $C$ when it does not, both confirmed. Three consequences follow. First, with the *ratio* similarity scaled by the far end — and with that combination only, of the five tested — the rule reproduces the false-position formula identically; so does the truncated linear similarity scaled by a divided difference over the *bracket ends*, which shows that the boundary of novelty runs through which points the difference is taken over. Second, any similarity fixed in advance with the scale also fixed yields at best linear convergence at a rate that is a property of the problem rather than of the method; on the eight standard problems of §2.2, five to eight of them fail. Third, the rate vanishes exactly when the scale equals the local scale of the value axis; reading that scale off the data — a divided difference, not a fitted coefficient — turns the step into $|f|/|f'|$ and the convergence superlinear. Bisection places the next iterate at the midpoint because it uses no information about the function's value. We replace that rule with a *similarity* rule: the step is proportional to how *dissimilar* the current outcome $f(x)$ is to the expectation $0$, measured by a similarity function $S:\mathbb{R}\to[0,1]$ with $S(0)=1$. We derive the contraction factor of the resulting iteration in closed form; it predicts a linear rate to four digits when $1-S$ has a non-zero slope at the origin, and a sublinear $e_n\sim C/n$ with a predicted $C$ when it does not, both confirmed. Three consequences follow. First, with the *ratio* similarity scaled by the far end — and with that combination only, of the five tested — the rule reproduces the false-position formula identically; so does the truncated linear similarity scaled by a divided difference over the *bracket ends*, which shows that the boundary of novelty runs through which points the difference is taken over. Second, any similarity fixed in advance with the scale also fixed yields at best linear convergence at a rate that is a property of the problem rather than of the method; on the eight standard problems of §2.2, five to eight of them fail. Third, the rate vanishes exactly when the scale equals the local scale of the value axis; reading that scale off the data — a divided difference, not a fitted coefficient — turns the step into $|f|/|f'|$ and the convergence superlinear.
The derivation fixes the similarity and the scale before any benchmark is consulted — one whose approach to 1 is linear, and the finest available data-derived scale — and one requirement fixes the safeguard: it must carry a bound without a free constant, which leaves a projection of the step onto the admissible interval with the bisection budget *earned* step by step rather than scheduled in advance. What results is, explicitly, a Newton step with a three-point divided-difference derivative, clipped to the bracket and projected onto an earned bisection budget; the similarity function is scaffolding of the derivation and does not appear in the final rule. It carries a proven worst case of bisection plus one step and contains no free constant. On the 154 problems of Alefeld, Potra and Shi it needs 3568 evaluations of $f$, 29th of 264 configurations, against 2785 for Chandrupatla's method, 2820 for TOMS 748 and 2889 for Brent. The variant whose budget is scheduled at twice bisection needs 2806 — level with TOMS 748 and 0.75% behind Chandrupatla — but its multiplier is a free constant, and a load-bearing one: on the wide-bracket set its value moves the total from 2752 to 4006. On the 45 problems of Chandrupatla the proposed rule needs 3020, ahead of TOMS 748 (4000) and Brent (4099) and behind Chandrupatla (2423) and ITP (2857). For every data-derived scale, the ranking of the nine similarity functions matches the derived law without exception. The derivation fixes the method 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. What results is, explicitly, a Newton step with a three-point divided-difference derivative, clipped to the bracket and projected onto the bisection budget; the similarity function is scaffolding of the derivation and does not appear in the final rule. It carries a proven worst case of twice bisection plus one step, and exactly one free constant, which we declare rather than argue away: the multiplier of that budget. On the canonical set it does not matter — the total is the same for every multiplier from 1.25 to 4 — and on the wide-bracket set it does, moving the total from 2752 to 4006. On the 154 problems of Alefeld, Potra and Shi the rule needs 2806 evaluations of $f$ against 2820 for TOMS 748 — a margin of 0.5%, which is a tie — 2889 for Brent, and 2787 for Chandrupatla's method, 0.7% fewer in total; problem by problem it beats Chandrupatla 87 to 42 (exact sign test $p=9\cdot10^{-5}$) and Brent 102 to 40. On the 45 problems of Chandrupatla it beats Brent 30 to 4 and TOMS 748 31 to 8, and loses to ITP and to Chandrupatla (3220 against 2857 and 2441). A variant whose budget is earned step by step has no free constant and a tighter guarantee, bisection plus one step, at a price of 762 evaluations on the canonical set. For every data-derived scale, the ranking of the nine similarity functions 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 under the scheduled projection, below every method measured here including Chandrupatla's 2785; reading it instead on the choice of far end gives 5511 over both sets, and reading it in the decision to trust the model or halve lowers the canonical count further, to 2523. Those records carry the projection's multiplier, although on the canonical set they barely feel it — 2523 is the same for every multiplier from 1.25 to 4, 2527 for every one from 1.5 to 4 and 2541 at 1.25; under the constant-free earned budget the same readings gain far less (6494 over both sets against 6588). Where the similarity is read turns out to matter more than which similarity it is. The gap that remains on the wide-bracket set is taken apart on the same evaluations: a third of it is the order of the model — a convex two-point step cannot carry the negative weight of quadratic interpolation — and two thirds is not a model at all but the decision when to give the model up, which Chandrupatla takes by bisecting on up to all of his steps and which no reading measured here takes correctly for ours. 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 2787; reading it instead on the choice of far end gives 5511 over both sets, and reading it in the decision to trust the model or halve lowers the canonical count further, to 2523. On the canonical set these records barely feel the multiplier — 2523 is the same for every multiplier from 1.25 to 4, 2527 for every one from 1.5 to 4 and 2541 at 1.25; under the constant-free earned budget the same readings gain far less (6494 over both sets against 6588). Where the similarity is read turns out to matter more than which similarity it is. The gap that remains on the wide-bracket set is taken apart on the same evaluations: about a quarter of it is the order of the model — a convex two-point step cannot carry the negative weight of quadratic interpolation — and two thirds is not a model at all but the decision when to give the model up, which Chandrupatla takes by bisecting on up to all of his steps and which no reading measured here takes correctly for ours.
--- ---
@@ -90,7 +90,7 @@ Empirically, with $\sigma$ taken as $|f|$ at the starting point, five to eight o
## 3. The method ## 3. The method
Corollaries 1–3 fix the similarity and the scale before any benchmark is consulted; the safeguard is fixed by one requirement, that it carry a bound without a free constant (§3.1). We record the first two as a prediction, and Section 5.3 tests it against the full grid. Corollaries 1–3 fix every choice in the method before any benchmark is consulted. We record them here as a prediction, and Section 5.3 tests it against the full grid. One number in the safeguard is not fixed by them, and §3.1 declares it as the method's one free constant.
**Similarity: any with $c\neq0$.** The law makes the shape of $S$ almost irrelevant and its slope at the origin decisive. We take the plainest member, the **truncated linear** or tent form $S(z)=\max(0,1-|z|)$. It is not the ratio form $1/(1+|z|)$, which is a separate row of every table here; the two are distinguished throughout because Proposition 1 applies to the ratio form and Table 1's last row to the tent. **Similarity: any with $c\neq0$.** The law makes the shape of $S$ almost irrelevant and its slope at the origin decisive. We take the plainest member, the **truncated linear** or tent form $S(z)=\max(0,1-|z|)$. It is not the ratio form $1/(1+|z|)$, which is a separate row of every table here; the two are distinguished throughout because Proposition 1 applies to the ratio form and Table 1's last row to the tent.
@@ -110,7 +110,7 @@ We state this ourselves rather than leave it to be discovered, because it locate
evaluate fa = f(a), fb = f(b) evaluate fa = f(a), fb = f(b)
A, B <- a, b the proven bracket A, B <- a, b the proven bracket
x <- the endpoint with the smaller |f|; xp <- the other; xp2 <- none x <- the endpoint with the smaller |f|; xp <- the other; xp2 <- none
C <- 1 one bit: the promise "bisection + 1 step" n_max <- 2 * ceil(log2(|b-a| / xtol)); allow <- 2^n_max * xtol/2
while |B - A| > xtol: while |B - A| > xtol:
far <- the end of [A,B] further from x far <- the end of [A,B] further from x
f' <- derivative at x of the parabola through (x,xp,xp2) f' <- derivative at x of the parabola through (x,xp,xp2)
@@ -119,18 +119,15 @@ while |B - A| > xtol:
if sigma is 0 or not finite: xn <- midpoint(A,B) if sigma is 0 or not finite: xn <- midpoint(A,B)
else: else:
xn <- x + min(1, |f(x)|/sigma) * (far - x) # the step, eq (4) xn <- x + min(1, |f(x)|/sigma) * (far - x) # the step, eq (4)
if C < 1: # earned projection, eq (3') xn <- clamp(xn, hi - allow, lo + allow) # projection, eq (3)
allow <- |B - A| * 2^(C - 1)
xn <- clamp(xn, hi - allow, lo + allow) # the midpoint if empty
if xn outside (A,B) or already evaluated: xn <- midpoint(A,B) if xn outside (A,B) or already evaluated: xn <- midpoint(A,B)
evaluate fn = f(xn); w <- |B - A| evaluate fn = f(xn); allow <- allow / 2
xp2, xp, x <- xp, x, xn xp2, xp, x <- xp, x, xn
update the bracket: whichever of A,B keeps the sign change update the bracket: whichever of A,B keeps the sign change
C <- max(0, C + log2(w / |B - A|) - 1) # bank what beat a halving
return midpoint(A,B) return midpoint(A,B)
``` ```
Every quantity in it is either an evaluation of $f$, a divided difference of evaluations, or the halving that defines bisection. The one number in it, the endowment of one bit, is the bound being promised rather than a setting (§3.1). Every quantity in it is either an evaluation of $f$, a divided difference of evaluations, or the halving that defines bisection — with one exception, the factor 2 in `n_max`, which §3.1 declares as the method's one free constant. The constant-free variant of §3.1 replaces the two `allow` lines by the earned credit of (3').
### 3.1 The safeguard: project, do not replace ### 3.1 The safeguard: project, do not replace
@@ -142,7 +139,7 @@ $$\alpha_j = 2^{\,n_\max-j}\epsilon, \qquad x \;=\; \operatorname{clamp}\bigl(x_
The next bracket is at most $\max(x-\ell,\,h-x)$, so (3) gives $w_{j+1}\le\alpha_j$ directly, and induction bounds the run by $n_\max$ steps in exact arithmetic. In floating point the bracket can land exactly on the boundary, and the termination test then asks for one more; the guarantee stated and verified is therefore $2\lceil\log_2(w_0/\varepsilon_x)\rceil+3$ evaluations. The next bracket is at most $\max(x-\ell,\,h-x)$, so (3) gives $w_{j+1}\le\alpha_j$ directly, and induction bounds the run by $n_\max$ steps in exact arithmetic. In floating point the bracket can land exactly on the boundary, and the termination test then asks for one more; the guarantee stated and verified is therefore $2\lceil\log_2(w_0/\varepsilon_x)\rceil+3$ evaluations.
**The doubling is a free constant, and it is load-bearing.** An earlier version of this section argued that it is "the factor already claimed in the bound". That is circular — the bound is twice bisection because two was chosen — and measurement settles it (`measure_budget_factor.py`, Linux): **The multiplier is the method's one free constant.** An earlier version of this section argued that it is "the factor already claimed in the bound". That is circular — the bound is twice bisection because two was chosen — and measurement settles it (`measure_budget_factor.py`, Linux):
**Table 1a.** The scheduled projection at budget multiplier $c$, i.e. $n_\max=\lceil c\cdot\log_2(w_0/\varepsilon_x)\rceil$. Total evaluations, failures charged. **Table 1a.** The scheduled projection at budget multiplier $c$, i.e. $n_\max=\lceil c\cdot\log_2(w_0/\varepsilon_x)\rceil$. Total evaluations, failures charged.
@@ -157,7 +154,7 @@ The next bracket is at most $\max(x-\ell,\,h-x)$, so (3) gives $w_{j+1}\le\alpha
On the canonical set the projection never binds once $c\ge1.25$, so the total does not move. On the wide-bracket set it moves monotonically with $c$, and 2 is not even the best value. Nor is zero slack ($c=1$) the degenerate case it was claimed to be, "a worst case of exactly bisection" under which "the guard behaves as bisection": it costs 4817 and 2997, far from bisection's 7530 and 3090, and on the wide-bracket set it beats $c=2$. The multiplicative family has no member that the bound singles out, so its multiplier is a genuinely free constant. On the canonical set the projection never binds once $c\ge1.25$, so the total does not move. On the wide-bracket set it moves monotonically with $c$, and 2 is not even the best value. Nor is zero slack ($c=1$) the degenerate case it was claimed to be, "a worst case of exactly bisection" under which "the guard behaves as bisection": it costs 4817 and 2997, far from bisection's 7530 and 3090, and on the wide-bracket set it beats $c=2$. The multiplicative family has no member that the bound singles out, so its multiplier is a genuinely free constant.
**The budget earned rather than scheduled.** The proposed safeguard removes that constant. Let $s_j=\log_2(w_j/w_{j+1})$ be the shrink a step delivers in bits — bisection delivers exactly 1 — and keep a credit **The budget earned rather than scheduled: a constant-free variant.** The constant can be removed. Let $s_j=\log_2(w_j/w_{j+1})$ be the shrink a step delivers in bits — bisection delivers exactly 1 — and keep a credit
$$C \leftarrow \max(0,\;C+s_j-1),\qquad \alpha_j = w_j\,2^{\,C-1},\qquad x=\operatorname{clamp}\bigl(x_\mathrm{sim},\,h-\alpha_j,\,\ell+\alpha_j\bigr), \tag{3'}$$ $$C \leftarrow \max(0,\;C+s_j-1),\qquad \alpha_j = w_j\,2^{\,C-1},\qquad x=\operatorname{clamp}\bigl(x_\mathrm{sim},\,h-\alpha_j,\,\ell+\alpha_j\bigr), \tag{3'}$$
@@ -173,9 +170,9 @@ leaving the step alone while $C\ge1$. A step may under-deliver only by what earl
| 2 | bisection + 2 steps | 3692 | 2712 | 6404 | | 2 | bisection + 2 steps | 3692 | 2712 | 6404 |
| 5 | bisection + 5 steps | 3006 | 2615 | 5621 | | 5 | bisection + 5 steps | 3006 | 2615 | 5621 |
A looser promise buys evaluations, as it must; within one promise the cheapest endowment is determined. So the proposed method is the earned projection with $C_0=1$, whose guarantee — $\lceil\log_2(w_0/\varepsilon_x)\rceil+4$ evaluations, bisection plus one step with one more for the floating-point boundary — is verified on all 199 problems. The scheduled projection with $c=2$ is kept as the other end of a stated trade: one free constant, and on the canonical set 762 evaluations cheaper (2806 against 3568). A looser promise buys evaluations, as it must; within one promise the cheapest endowment is determined. The constant-free variant is therefore the earned projection with $C_0=1$, whose guarantee — $\lceil\log_2(w_0/\varepsilon_x)\rceil+4$ evaluations, bisection plus one step with one more for the floating-point boundary — is verified on all 199 problems.
This is the one choice in the method that was *not* fixed before a benchmark was consulted: the earned budget replaced the scheduled one after the question whether the multiplier was load-bearing. The reason for the replacement is structural — it removes a free constant — and not its cost, which on the canonical set is higher; a replacement chosen by the numbers would have gone the other way. We nonetheless propose the scheduled projection with $c=2$ and declare its multiplier. On the canonical set it is 762 evaluations cheaper than the earned variant (2806 against 3568), and there its total does not depend on the multiplier at all; on the wide-bracket set the earned variant is the cheaper (3020 against 3220). The two are the ends of a trade stated in numbers: one declared constant and the better canonical result, or no constant, a tighter bound and the better wide-bracket result. The value 2 was fixed before any benchmark was consulted; what changed after the question whether it was load-bearing is only that it is now declared as a constant rather than argued to be none.
Two remarks apply to both forms. Two remarks apply to both forms.
@@ -196,7 +193,7 @@ Two remarks apply to both forms.
2. ITP is reported both at the authors' recommended constants and as a *per-problem oracle*, the best of a $(\kappa_1,n_0)$ grid chosen separately for every problem — not an achievable method, included so that the state of the art cannot be said to have been handicapped. 2. ITP is reported both at the authors' recommended constants and as a *per-problem oracle*, the best of a $(\kappa_1,n_0)$ grid chosen separately for every problem — not an achievable method, included so that the state of the art cannot be said to have been handicapped.
3. Our implementation of ITP is validated against the method's own theorem, that it never needs more than $n_{1/2}+n_0$ iterations, on all 154 problems for $n_0\in\{0,1,2,5\}$. 3. Our implementation of ITP is validated against the method's own theorem, that it never needs more than $n_{1/2}+n_0$ iterations, on all 154 problems for $n_0\in\{0,1,2,5\}$.
4. Where SciPy ships the method, the competitor is run at **the better of SciPy's implementation and ours, problem by problem**. This is not a formality. Our implementations turned out to be slightly *cheaper* than the library's — 6991 evaluations against `brentq`'s 7004, and 5209 against `_chandrupatla`'s 5228 — so quoting the library figure alone would have flattered us by a fifth of a percent, which on a 0.5% margin is not nothing. The two agree on 191 of 199 problems for Brent and 179 of 199 for Chandrupatla, the differences being one or two evaluations from the relative-tolerance term in the stopping rules. ITP has no library implementation, which is why it is checked against its own theorem instead; bisection and the false-position family are not in SciPy either and are ours. 4. Where SciPy ships the method, the competitor **is the library**: Chandrupatla is `scipy.optimize.elementwise.find_root`, Brent is `brentq`, TOMS 748 is `toms748`, each run with the same stopping rule as every other method — a bracket no wider than $\varepsilon_x$, the relative term at machine precision, no stopping on $|f|$ — and each evaluation counted by the same wrapper around $f$. An earlier version gave each competitor the better of the library's implementation and ours, problem by problem. That concession backfired in the competitor's favour: our Chandrupatla stopped on a bracket twice as wide as everybody else's — the factor this protocol corrects for ITP in item 1 and had not corrected there — and the per-problem minimum let the looser run win. With the tolerance corrected our implementation and the library agree on all 199 problems exactly, and Chandrupatla's totals move from 2785 to 2787 on the canonical set and from 2423 to 2441 on the wide-bracket set. For Brent the library is slightly dearer than our implementation (7004 against 6991 over both sets); we use the library anyway, because it is the implementation a reader can check. ITP has no library implementation, which is why it is checked against its own theorem instead; bisection and the false-position family are not in SciPy either and are ours.
**Reproducibility.** All numbers come from a single Linux run. The test functions call `libm`, whose last digit differs between platforms; the same SciPy version scores 2819 for TOMS 748 on Windows and 2820 on Linux for the same set. Every number stated in this paper is checked against the run data by a script that is part of the test suite. **Reproducibility.** All numbers come from a single Linux run. The test functions call `libm`, whose last digit differs between platforms; the same SciPy version scores 2819 for TOMS 748 on Windows and 2820 on Linux for the same set. Every number stated in this paper is checked against the run data by a script that is part of the test suite.
@@ -204,45 +201,45 @@ Two remarks apply to both forms.
## 5. Results ## 5. Results
The grid is nine similarity functions × four scales × seven safeguards = 252 configurations, plus twelve baselines: 264 in all. The configuration proposed in Section 3 — tent similarity, three-point scale, earned projection — is one entry of that grid, fixed by the theory and by the requirement of §3.1 rather than chosen from the ranking; the scheduled projection is shown beside it as the other end of the trade. Chandrupatla's own method and Dekker's are among the baselines; an earlier version of these tables was drawn up before either was added, and did not show that Chandrupatla's method is first on the canonical set. The grid is nine similarity functions × four scales × seven safeguards = 252 configurations, plus twelve baselines: 264 in all. The configuration proposed in Section 3 — tent similarity, three-point scale, projection safeguard with its multiplier declared — is one entry of that grid, fixed by the theory rather than chosen from the ranking; the constant-free earned variant of §3.1 is shown beside it. Chandrupatla's own method and Dekker's are among the baselines; an earlier version of these tables was drawn up before either was added.
**Table 2.** Alefeld–Potra–Shi, 154 problems, out of 264 configurations. Places by failures first, then total. **Table 2.** Alefeld–Potra–Shi, 154 problems, out of 264 configurations. Places by failures first, then total.
| # | configuration | tuned | solved | total evals | $>2\times$ | | # | configuration | tuned | solved | total evals | $>2\times$ |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| 1 | Chandrupatla | no | 153/154 | 2785 | 0 | | 1 | Chandrupatla | no | 153/154 | 2787 | 0 |
| 2 | the same rule with no safeguard at all | no | 153/154 | 2806 | 0 | | 2 | the same rule with no safeguard at all | no | 153/154 | 2806 | 0 |
| 3 | scheduled projection, $c=2$ (one free constant) | no | 153/154 | 2806 | 0 | | **3** | **proposed: tent, 3-point, projection ($c=2$)** | **no** | 153/154 | **2806** | 0 |
| 4 | TOMS 748 | yes | 153/154 | 2820 | 0 | | 4 | TOMS 748 | yes | 153/154 | 2820 | 0 |
| 5 | Brent | yes | 153/154 | 2889 | 0 | | 5 | Brent | yes | 153/154 | 2889 | 0 |
| 12 | Dekker | no | 153/154 | 3158 | 0 | | 12 | Dekker | no | 153/154 | 3158 | 0 |
| 13 | ITP, per-problem oracle | yes | 153/154 | 3260 | 0 | | 13 | ITP, per-problem oracle | yes | 153/154 | 3260 | 0 |
| 20 | Ridders | no | 153/154 | 3313 | 0 | | 20 | Ridders | no | 153/154 | 3313 | 0 |
| **29** | **proposed: tent, 3-point, earned projection** | **no** | 153/154 | **3568** | 0 | | 29 | constant-free variant: earned projection | no | 153/154 | 3568 | 0 |
| 34 | ITP, recommended constants | yes | 153/154 | 3957 | 0 | | 34 | ITP, recommended constants | yes | 153/154 | 3957 | 0 |
| 73 | Illinois | yes | 153/154 | 7045 | 0 | | 73 | Illinois | yes | 153/154 | 7045 | 0 |
| 79 | bisection | no | 153/154 | 7530 | 0 | | 79 | bisection | no | 153/154 | 7530 | 0 |
| 244 | false position | no | 127/154 | 10560 | — | | 244 | false position | no | 127/154 | 10560 | — |
On the problems both solve, the proposed configuration goes 79/3/71 (wins/ties/losses) against Chandrupatla, 77/9/67 against TOMS 748, 78/5/70 against Brent, 103/10/40 against ITP at its recommended constants, 86/6/61 against the per-problem ITP oracle and 122/1/30 against bisection: it wins more problems than it loses against every competitor, and still loses on the total to Chandrupatla, TOMS 748, Brent and the ITP oracle, because its losses are the larger ones. The scheduled projection goes 86/25/42 against Chandrupatla, 86/16/51 against TOMS 748, 102/11/40 against Brent, 132/3/18 against ITP, 121/4/28 against the oracle and 152/1/0 against bisection. Its total, 2806 against TOMS 748's 2820, is a margin of 0.5% and should be read as a tie; against Chandrupatla's 2785 it is 0.75% behind. The proposed rule and the same rule without any safeguard tie at 2806: the projection never binds on this set. By total, Chandrupatla's method is first with 2787, 0.7% fewer — the same order of margin as the 0.5% by which the proposed rule leads TOMS 748, which we read as a tie. Problem by problem the picture is the other way round: on the problems both solve the proposed rule beats Chandrupatla 87/24/42 (wins/ties/losses; exact two-sided sign test $p=9\cdot10^{-5}$), TOMS 748 86/16/51, Brent 102/11/40, ITP at its recommended constants 132/3/18, the per-problem ITP oracle 121/4/28, and bisection 152/1/0. It wins twice as many problems as it loses against Chandrupatla and trails him in total because its losses are the larger ones. The constant-free variant goes 79/3/71 against Chandrupatla ($p=0.57$, a tie in problems), 77/9/67 against TOMS 748 and 78/5/70 against Brent, and loses on the total to all three.
**Table 3.** Chandrupatla, 45 problems, brackets up to $10^{12}$ wide, out of 264 configurations. **Table 3.** Chandrupatla, 45 problems, brackets up to $10^{12}$ wide, out of 264 configurations.
| # | configuration | tuned | solved | total evals | $>2\times$ | | # | configuration | tuned | solved | total evals | $>2\times$ |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| 1 | similarity, $1-\sqrt{\lvert z\rvert}$, bracket scale, projection | no | **41/45** | 4516 | 0 | | 1 | similarity, $1-\sqrt{\lvert z\rvert}$, bracket scale, projection | no | **41/45** | 4516 | 0 |
| 2 | Chandrupatla | no | 40/45 | 2423 | 0 | | 2 | Chandrupatla | no | 40/45 | 2441 | 0 |
| 3 | ITP, per-problem oracle | yes | 40/45 | 2611 | 0 | | 3 | ITP, per-problem oracle | yes | 40/45 | 2611 | 0 |
| 4 | similarity, $e^{-\lvert z\rvert}$, bracket scale, both guards | no | 40/45 | 2733 | 0 | | 4 | similarity, $e^{-\lvert z\rvert}$, bracket scale, both guards | no | 40/45 | 2733 | 0 |
| 6 | ITP, recommended constants | yes | 40/45 | 2857 | 0 | | 6 | ITP, recommended constants | yes | 40/45 | 2857 | 0 |
| **10** | **proposed: tent, 3-point, earned projection** | **no** | 40/45 | **3020** | 0 | | 10 | constant-free variant: earned projection | no | 40/45 | 3020 | 0 |
| 16 | bisection | no | 40/45 | 3090 | 0 | | 16 | bisection | no | 40/45 | 3090 | 0 |
| 47 | scheduled projection, $c=2$ | no | 40/45 | 3220 | 0 | | **47** | **proposed: tent, 3-point, projection ($c=2$)** | **no** | 40/45 | **3220** | 0 |
| 164 | TOMS 748 | yes | 40/45 | 4000 | 15 | | 164 | TOMS 748 | yes | 40/45 | 4000 | 15 |
| 226 | Brent | yes | 38/45 | 4099 | 18 | | 226 | Brent | yes | 38/45 | 4115 | 18 |
| 228 | the same rule with no safeguard at all | no | 35/45 | 4104 | 10 | | 228 | the same rule with no safeguard at all | no | 35/45 | 4104 | 10 |
The set's nominal winner is a similarity configuration that solves one problem more than anything else, 41 of 45, at a worse total; ranking by failures first puts it on top, and we report it as such rather than by the total that would flatter a proposal. The proposed configuration is tenth, and here the earned budget is the cheaper form: 3020 against the scheduled projection's 3220. Problem by problem it goes 24/0/16 against TOMS 748 and 18/0/20 against Brent — both of which exceed the $2\times$ bound on 15 and 18 problems — and loses clearly to Chandrupatla (5/0/35) and to the ITP oracle (2/3/35); to ITP it loses on the total but not on problems (12/20/8, and 3020 against 2857); and problem by problem it loses even to bisection (5/0/35), whose total of 3090 it nonetheless beats. Over both sets the proposed rule needs 6588 and the scheduled projection 6026, against 5208 for Chandrupatla, 5871 for the ITP oracle, 6820 for TOMS 748 and 6988 for Brent. The set's nominal winner is a similarity configuration that solves one problem more than anything else, 41 of 45, at a worse total; ranking by failures first puts it on top, and we report it as such rather than by the total that would flatter our proposal. Here Chandrupatla's method is genuinely the stronger: it beats the proposed rule 26 problems to 9 ($p=0.006$) and by 3220 against 2441 in total. The proposed rule beats Brent 30/4/4 and TOMS 748 31/1/8 — both of which exceed the $2\times$ bound on 18 and 15 problems — and loses to ITP 17/0/23 and to the ITP oracle 16/0/24. The wide-bracket set is where the multiplier matters (Table 1a) and where the constant-free variant is the cheaper form, 3020 against 3220. Over both sets the proposed rule needs 6026 and the constant-free variant 6588, against 5228 for Chandrupatla, 5871 for the ITP oracle, 6820 for TOMS 748 and 7004 for Brent.
### 5.1 What the guarantee costs ### 5.1 What the guarantee costs
@@ -251,23 +248,23 @@ It depends on which bound is bought. Against the identical rule with no safeguar
| | solved | total evals | runs over the $2\times$ bound | | | solved | total evals | runs over the $2\times$ bound |
|---|---|---|---| |---|---|---|---|
| APS, no safeguard | 153/154 | 2806 | 0 | | APS, no safeguard | 153/154 | 2806 | 0 |
| APS, scheduled projection ($2\times$ bisection) | 153/154 | 2806 | 0 | | APS, projection — proposed ($2\times$ bisection) | 153/154 | 2806 | 0 |
| APS, earned projection (bisection + 1 step) | 153/154 | 3568 | 0 | | APS, earned projection — constant-free (bisection + 1 step) | 153/154 | 3568 | 0 |
| Chandrupatla, no safeguard | 35/45 | 4104 | 10 | | Chandrupatla, no safeguard | 35/45 | 4104 | 10 |
| Chandrupatla, scheduled projection | 40/45 | 3220 | 0 | | Chandrupatla, projection — proposed | 40/45 | 3220 | 0 |
| Chandrupatla, earned projection | **40/45** | **3020** | **0** | | Chandrupatla, earned projection — constant-free | **40/45** | **3020** | **0** |
The scheduled projection never binds on the canonical set, so its totals are identical to the last evaluation; on the wide-bracket set it solves five more problems for 21% fewer evaluations. The earned projection promises twice as much — a worst case of bisection plus one step instead of twice bisection — and pays for it on the canonical set, 762 evaluations or 27%, while on the wide-bracket set it is the cheapest of the three. Both are the opposite of the usual trade on at least one set, and that is the point of projecting rather than replacing: the two safeguards that replace cost 7260 and 7239 on Alefeld–Potra–Shi, against 2806. The proposed projection never binds on the canonical set, so its totals are identical to the last evaluation; on the wide-bracket set it solves five more problems for 21% fewer evaluations. The earned projection promises twice as much — a worst case of bisection plus one step instead of twice bisection — and pays for it on the canonical set, 762 evaluations or 27%, while on the wide-bracket set it is the cheapest of the three. Both are the opposite of the usual trade on at least one set, and that is the point of projecting rather than replacing: the two safeguards that replace cost 7260 and 7239 on Alefeld–Potra–Shi, against 2806.
### 5.2 Wall-clock time, and why it is not the cost model ### 5.2 Wall-clock time, and why it is not the cost model
Evaluations of $f$ are the cost model because in any real problem $f$ is the expensive part. Wall-clock time measures something narrower — the method's own arithmetic per step — and it is implementation- and language-bound. We report it because a reader will ask, and because what it shows is instructive. Evaluations of $f$ are the cost model because in any real problem $f$ is the expensive part. Wall-clock time measures something narrower — the method's own arithmetic per step — and it is implementation- and language-bound. We report it because a reader will ask, and because what it shows is instructive.
On the 199 problems as they stand, every $f$ being a formula of a few operations, the scheduled projection (the configuration timed) is the **slowest** of the pure-Python methods compared, at 32.8 ms against 18.4 for Brent, 21.1 for ITP, 14.2 for Ridders and 22.9 for bisection — while using the fewest evaluations of all of them, 4991 against 5932, 5675, 6421 and 9474. Bisection, needing nearly twice the evaluations, finishes in two thirds of our time. On the 199 problems as they stand, every $f$ being a formula of a few operations, the proposed rule is the **slowest** of the pure-Python methods compared, at 32.8 ms against 18.4 for Brent, 21.1 for ITP, 14.2 for Ridders and 22.9 for bisection — while using the fewest evaluations of all of them, 4991 against 5932, 5675, 6421 and 9474. Bisection, needing nearly twice the evaluations, finishes in two thirds of our time.
Those evaluation counts are *raw*: they are what the timing loop actually spent, and a failed run contributes only the evaluations it made before the budget ran out. They are therefore not the totals of §5, where a failure is charged the full 200-evaluation budget so that no method can buy a low score by giving up — 4991 raw against 6026 charged for the scheduled projection, 5932 against 6991 for Brent, 9474 against 10620 for bisection. Wall-clock time can only be compared against what was really executed, so the raw counts are the right companions to the milliseconds; every comparison in §5 uses the charged ones. Both are stated to avoid the appearance of two incompatible sets of numbers for the same methods. Those evaluation counts are *raw*: they are what the timing loop actually spent, and a failed run contributes only the evaluations it made before the budget ran out. They are therefore not the totals of §5, where a failure is charged the full 200-evaluation budget so that no method can buy a low score by giving up — 4991 raw against 6026 charged for the proposed rule, 5932 against 6991 for Brent (our pure-Python implementation, which is what is timed; the tables use the library's `brentq`, 7004), 9474 against 10620 for bisection. Wall-clock time can only be compared against what was really executed, so the raw counts are the right companions to the milliseconds; every comparison in §5 uses the charged ones. Both are stated to avoid the appearance of two incompatible sets of numbers for the same methods.
That inversion is the point. Making $f$ progressively more expensive by a controlled amount of arithmetic and timing the sweep directly — nothing subtracted, nothing modelled — the scheduled projection overtakes ITP once one call to $f$ costs about 9 µs and Brent at about 20 µs, which on this machine is of the order of a hundred arithmetic operations. Below that the ranking is decided by per-step overhead; above it, by evaluations. That inversion is the point. Making $f$ progressively more expensive by a controlled amount of arithmetic and timing the sweep directly — nothing subtracted, nothing modelled — the proposed rule overtakes ITP once one call to $f$ costs about 9 µs and Brent at about 20 µs, which on this machine is of the order of a hundred arithmetic operations. Below that the ranking is decided by per-step overhead; above it, by evaluations.
Three caveats, all of them limitations of the measurement rather than of the methods. Timings vary by tens of percent between runs even on an idle machine. An attempt to separate our per-step cost into "algorithm" and "Python call overhead", by timing a flattened rewrite of the same rule, produced a variant that diverged from the original on 42 of the 199 problems and was therefore discarded rather than quoted; the question stays open. And an earlier version of this measurement estimated a per-evaluation overhead by subtracting the measured cost of $f$, which returned *negative* overheads for four of six methods — an impossibility that condemned the estimator, and the reason nothing is subtracted here. Three caveats, all of them limitations of the measurement rather than of the methods. Timings vary by tens of percent between runs even on an idle machine. An attempt to separate our per-step cost into "algorithm" and "Python call overhead", by timing a flattened rewrite of the same rule, produced a variant that diverged from the original on 42 of the 199 problems and was therefore discarded rather than quoted; the question stays open. And an earlier version of this measurement estimated a per-evaluation overhead by subtracting the measured cost of $f$, which returned *negative* overheads for four of six methods — an impossibility that condemned the estimator, and the reason nothing is subtracted here.
@@ -314,21 +311,21 @@ Where a safeguard other than the projection of §3.1 appears, it is one of the g
| configuration (all with the guarantee, 193/199) | APS-154 | CHA-45 | both | | configuration (all with the guarantee, 193/199) | APS-154 | CHA-45 | both |
|---|---|---|---| |---|---|---|---|
| proposed: tent, 3-point, earned projection (§3.1) | 3568 | 3020 | 6588 | | proposed: tent, 3-point, projection $c=2$ (§3) | 2806 | 3220 | 6026 |
| scheduled projection, $c=2$ (§3.1) | 2806 | 3220 | 6026 | | constant-free variant: earned projection (§3.1) | 3568 | 3020 | 6588 |
| (1) trust before the step, projection | 2848 | 3074 | 5922 | | (1) trust before the step, projection | 2848 | 3074 | 5922 |
| (1)+(2) points chosen by shape, *safe* | **2527** | 3108 | 5635 | | (1)+(2) points chosen by shape, *safe* | **2527** | 3108 | 5635 |
| (1)+(3) far end by dissimilarity, projection | 2726 | **2785** | **5511** | | (1)+(3) far end by dissimilarity, projection | 2726 | **2785** | **5511** |
| (1)+(2)+(3), *safe* | 2884 | 2859 | 5743 | | (1)+(2)+(3), *safe* | 2884 | 2859 | 5743 |
| Chandrupatla | 2785 | 2423 | 5208 | | Chandrupatla | 2787 | 2441 | 5228 |
| TOMS 748 | 2820 | 4000 | 6820 | | TOMS 748 | 2820 | 4000 | 6820 |
| ITP, per-problem oracle | 3260 | 2611 | 5871 | | ITP, per-problem oracle | 3260 | 2611 | 5871 |
Two results here are worth stating plainly. First, the canonical set improves from 2806 to **2527**, which is below every method measured, including Chandrupatla's 2785, with the worst-case guarantee intact. It is obtained under the scheduled projection, whose multiplier §3.1 shows to be a free constant, although this total hardly feels it: 2527 for every multiplier from 1.5 to 4, 2541 at 1.25 (`measure_records_vs_multiplier.py`). 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 (reading (3) under *safe*). Two results here are worth stating plainly. First, the canonical set improves from 2806 to **2527**, which is below every method measured, including Chandrupatla's 2787, with the worst-case guarantee intact. It hardly feels the declared multiplier of §3.1: 2527 for every multiplier from 1.5 to 4, 2541 at 1.25 (`measure_records_vs_multiplier.py`). Second, on the two sets together the 5511 of reading (3) passes ITP's oracle (5871) but not Chandrupatla (5228): the remaining gap is entirely on the wide-bracket families, where Chandrupatla needs 2441 against our 2683 (reading (3) under *safe*).
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. 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.
Under the constant-free earned projection the same readings gain far less. Reading (1) gives 3507 and 2997, (1)+(2) 3755 and 3021, (1)+(3) 3857 and 3002, all three together 4354 and 3037, against 3568 and 3020 for the proposed rule itself: the earned budget binds early on the canonical problems, and what the readings buy under the scheduled projection is mostly room that the earned budget does not grant. Under the constant-free earned projection the same readings gain far less. Reading (1) gives 3507 and 2997, (1)+(2) 3755 and 3021, (1)+(3) 3857 and 3002, all three together 4354 and 3037, against 3568 and 3020 for the constant-free variant itself: the earned budget binds early on the canonical problems, and what the readings buy under the projection is mostly room that the earned budget does not grant.
### 7.1 The similarity function belongs to the place, not to the method ### 7.1 The similarity function belongs to the place, not to the method
@@ -360,9 +357,9 @@ Three places have now been measured: the step length, the far end, and the point
Three readings of that place were measured, each on quantities already in hand and each without a constant. Two are ours: refuse when the configuration is less like a linear one than the iterate is off-centre, $|\varphi-\xi|>\min(\xi,1-\xi)$ ("defect"); and refuse when the configuration's similarity to linear falls below the iterate's similarity to the root, $S(|\varphi-\xi|)<S(z)$, with the *same* $S$ in both places ("simpair"). The third is *Chandrupatla's own band*, placed inside our step rule. It is not a proposal; it is an instrument, and it answers whether the remaining gap lives in this place. Three readings of that place were measured, each on quantities already in hand and each without a constant. Two are ours: refuse when the configuration is less like a linear one than the iterate is off-centre, $|\varphi-\xi|>\min(\xi,1-\xi)$ ("defect"); and refuse when the configuration's similarity to linear falls below the iterate's similarity to the root, $S(|\varphi-\xi|)<S(z)$, with the *same* $S$ in both places ("simpair"). The third is *Chandrupatla's own band*, placed inside our step rule. It is not a proposal; it is an instrument, and it answers whether the remaining gap lives in this place.
In total, no. With Chandrupatla's band in our step, the wide-bracket set costs 3066 under the projection and 2899 under *credit*, against his 2423, while the canonical set rises from 2806 to 5432. The band is not idle — it refuses 63.6% of steps — so this is a decision that was taken and did not pay *in total*. Per family it did pay, and where: it closes almost the whole gap on the flat families and gives back more than that on the families where our step is good. §7.4 takes this apart; the short form is that the decision place does hold most of the gap, but no reading measured so far makes that decision correctly for *our* step. In total, no. With Chandrupatla's band in our step, the wide-bracket set costs 3066 under the projection and 2899 under *credit*, against his 2441, while the canonical set rises from 2806 to 5432. The band is not idle — it refuses 63.6% of steps — so this is a decision that was taken and did not pay *in total*. Per family it did pay, and where: it closes almost the whole gap on the flat families and gives back more than that on the families where our step is good. §7.4 takes this apart; the short form is that the decision place does hold most of the gap, but no reading measured so far makes that decision correctly for *our* step.
Our own readings do not close it either (best 2868 on the wide-bracket set), but "defect" sets a new best on the canonical set: **2523** at 153/154 under the scheduled projection, below the 2527 of reading (2) and below every method measured, with the guarantee intact — and, unlike 2527, the same for every multiplier from 1.25 to 4. Under the earned projection it gives 3545 and 2949, the best total over both sets of any constant-free configuration measured (6494 against 6588). It refuses only 7.0% of steps, the fewest of the three. Over both sets it costs 5733 against the 5511 of reading (3), because it gives back on the wide brackets (3210) what it gains on the canonical problems — so it is a second point on the same trade, not a replacement. Our own readings do not close it either (best 2868 on the wide-bracket set), but "defect" sets a new best on the canonical set: **2523** at 153/154 under the proposed projection, below the 2527 of reading (2) and below every method measured, with the guarantee intact — and, unlike 2527, the same for every multiplier from 1.25 to 4. Under the earned projection it gives 3545 and 2949, the best total over both sets of any constant-free configuration measured (6494 against 6588). It refuses only 7.0% of steps, the fewest of the three. Over both sets it costs 5733 against the 5511 of reading (3), because it gives back on the wide brackets (3210) what it gains on the canonical problems — so it is a second point on the same trade, not a replacement.
### 7.3 Six problems of the two sets are scored unsolved for every method ### 7.3 Six problems of the two sets are scored unsolved for every method
@@ -370,31 +367,31 @@ Chasing the remaining gap to Chandrupatla led to a property of the benchmarks th
The consequence is arithmetic. Each such problem charges the full budget of 200 evaluations to every method: about 1150 evaluations of every reported two-set total — ours, Chandrupatla's, TOMS 748's, ITP's, Brent's, bisection's — are this artefact, and the "153 of 154" and "40 of 45" that every method reports are statements about the scoring rather than about the methods. Re-scored with the criterion "a root is a root" ($|x-\text{root}|\le10^{-6}$ *or* $f(x)=0$), the same runs solve 199 of 199 for every method except Brent's 197, and the totals fall as follows. The consequence is arithmetic. Each such problem charges the full budget of 200 evaluations to every method: about 1150 evaluations of every reported two-set total — ours, Chandrupatla's, TOMS 748's, ITP's, Brent's, bisection's — are this artefact, and the "153 of 154" and "40 of 45" that every method reports are statements about the scoring rather than about the methods. Re-scored with the criterion "a root is a root" ($|x-\text{root}|\le10^{-6}$ *or* $f(x)=0$), the same runs solve 199 of 199 for every method except Brent's 197, and the totals fall as follows.
**Table 7.** The same runs under the harness criterion and under "a root is a root". The ranking is unchanged; the magnitudes are not, and neither is the size of the remaining gap on the wide-bracket set: 253 evaluations, or 17%, not 11%. **Table 7.** The same runs under the harness criterion and under "a root is a root". The ranking is unchanged; the magnitudes are not, and neither is the size of the remaining gap on the wide-bracket set: 235 evaluations, or 16%, not 10%.
| method | APS as scored | APS re-scored | CHA as scored | CHA re-scored | | method | APS as scored | APS re-scored | CHA as scored | CHA re-scored |
|---|---|---|---|---| |---|---|---|---|---|
| ours, (1)+(2) points by shape | **2527** | **2337** | 3108 | 2159 | | ours, (1)+(2) points by shape | **2527** | **2337** | 3108 | 2159 |
| ours, scheduled projection | 2806 | 2634 | 3220 | 2357 | | ours, proposed (projection) | 2806 | 2634 | 3220 | 2357 |
| Chandrupatla | 2785 | 2593 | **2423** | **1487** | | Chandrupatla | 2787 | 2595 | **2441** | **1505** |
| TOMS 748 | 2820 | 2636 | 4000 | 3088 | | TOMS 748 | 2820 | 2636 | 4000 | 3088 |
| Brent | 2889 | 2707 | 4099 | 3222 | | Brent | 2889 | 2707 | 4115 | 3238 |
| ITP | 3957 | 3767 | 2857 | 1907 | | ITP | 3957 | 3767 | 2857 | 1907 |
| bisection | 7530 | 7338 | 3090 | 2136 | | bisection | 7530 | 7338 | 3090 | 2136 |
### 7.4 Where the wide-bracket limit actually is ### 7.4 Where the wide-bracket limit actually is
The remaining gap looks diffuse — per family +57, +54, +46, +43, +35 and smaller — but part of it has one address. The `fun1` family is the same function with brackets of growing width, and our cost against Chandrupatla's across them is 7/8, 12/12, 19/15, 59/24, 63/44: we tie or win while the bracket is narrow and lose progressively as it widens. The endgame is not the cause — our observed order on those very problems is the higher one (3.63 against 2.02 on the $2\times10^4$-wide bracket). The cause is the rate of progress far from the root: 59 evaluations on a $2\times10^4$-wide bracket is what bisection itself needs ($\lceil\log_2(2\times10^{16})\rceil\approx54$), i.e. there we advance by about one halving per evaluation while Chandrupatla advances by about 2.3. The remaining gap looks diffuse — per family +57, +49, +42, +39, +30 and smaller — but part of it has one address. The `fun1` family is the same function with brackets of growing width, and our cost against Chandrupatla's across them is 7/8, 12/12, 19/15, 59/24, 63/44: we tie or win while the bracket is narrow and lose progressively as it widens. The endgame is not the cause — our observed order on those very problems is the higher one (3.63 against 2.02 on the $2\times10^4$-wide bracket). The cause is the rate of progress far from the root: 59 evaluations on a $2\times10^4$-wide bracket is what bisection itself needs ($\lceil\log_2(2\times10^{16})\rceil\approx54$), i.e. there we advance by about one halving per evaluation while Chandrupatla advances by about 2.3.
It is not the guarantee that caps us. With the safeguard removed entirely the same problems cost 46, 57, 61 and 68 against bisection's 57, 77, 45 and 45 — better on the wide brackets, *worse than bisection* on `fun3.1` and `fun5.1` — and still far from Chandrupatla's 24 and 44. It is not the guarantee that caps us. With the safeguard removed entirely the same problems cost 46, 57, 61 and 68 against bisection's 57, 77, 45 and 45 — better on the wide brackets, *worse than bisection* on `fun3.1` and `fun5.1` — and still far from Chandrupatla's 24 and 44.
Nor is it that the step fails to be relative to the bracket. With the tent similarity $1-S=\min(1,|f|/(|\hat f'|D))$, so by (4) the step is exactly $\min(D,|f|/|\hat f'|)$: a *fraction* of the distance to the far end, and the similarity is that fraction. Nor does the iterate creep along one side without moving the proven bracket: measured probe by probe, 91.5% of our evaluations on the $2\times10^4$-wide bracket do shrink the bracket, slightly *more* often than Chandrupatla's 87.5%. Nor is it that the step fails to be relative to the bracket. With the tent similarity $1-S=\min(1,|f|/(|\hat f'|D))$, so by (4) the step is exactly $\min(D,|f|/|\hat f'|)$: a *fraction* of the distance to the far end, and the similarity is that fraction. Nor does the iterate creep along one side without moving the proven bracket: measured probe by probe, 91.5% of our evaluations on the $2\times10^4$-wide bracket do shrink the bracket, slightly *more* often than Chandrupatla's 87.5%.
What differs on `fun1` is how much each probe removes. Ours shrinks the bracket by 0.93 halvings per evaluation there, Chandrupatla's by 2.26. On a wide bracket of a convex function the Newton step systematically undershoots, because a linear model of the value axis cannot see the curvature that separates the iterate from the root; a model that carries the curvature — inverse quadratic interpolation — lands far closer. What differs on `fun1` is how much each probe removes. Ours shrinks the bracket by 0.93 halvings per evaluation there, Chandrupatla's by 2.30. On a wide bracket of a convex function the Newton step systematically undershoots, because a linear model of the value axis cannot see the curvature that separates the iterate from the root; a model that carries the curvature — inverse quadratic interpolation — lands far closer.
That mechanism is right for `fun1` and *wrong as a statement about the whole gap*. Replaying our own trajectories on the 33 problems where we lose, and placing both models on the *same* evaluations — one extra evaluation each, from the same state — splits the 262 evaluations of the gap in two. On `fun1` (58 of them) inverse quadratic interpolation from our own three points removes far more of the bracket than our step did: 10.37 halvings against 0.62 on the $2\times10^4$-wide bracket. There the gap is the order of the model, and the reason is structural: our step $x+(1-S)(x_\mathrm{far}-x)=S\,x+(1-S)\,x_\mathrm{far}$ is a convex combination of two points, while the quadratic interpolant's Lagrange weights at 0 always include exactly one *negative* weight when the triple brackets the root (verified on all 428 non-degenerate states; in the other five the negative weight underflowed to $-0$). A similarity in $[0,1]$ cannot produce a negative weight — Proposition 1 extended from two points to three. That mechanism is right for `fun1` and *wrong as a statement about the whole gap*. Replaying our own trajectories on the 33 problems where we lose, and placing both models on the *same* evaluations — one extra evaluation each, from the same state — splits the 244 evaluations of the gap in two. On `fun1` (58 of them, about a quarter) inverse quadratic interpolation from our own three points removes far more of the bracket than our step did: 10.37 halvings against 0.62 on the $2\times10^4$-wide bracket. There the gap is the order of the model, and the reason is structural: our step $x+(1-S)(x_\mathrm{far}-x)=S\,x+(1-S)\,x_\mathrm{far}$ is a convex combination of two points, while the quadratic interpolant's Lagrange weights at 0 always include exactly one *negative* weight when the triple brackets the root (verified on all 428 non-degenerate states; in the other five the negative weight underflowed to $-0$). A similarity in $[0,1]$ cannot produce a negative weight — Proposition 1 extended from two points to three.
But 178 of the 262 — two thirds — sit on `fun3`–`fun6`, which are pure powers of order 3, 5, 9 and 19, and there *no* interpolating model works: our step removes 0.41–0.81 halvings of the bracket per evaluation, inverse quadratic interpolation from the same points only 0.01–0.17. Chandrupatla does not win those families with his model — his traced run bisects 93, 96, 100 and 100% of his steps. We lose them because our step is slower than bisection there and our guarantee, at most twice bisection, licenses exactly that. His band inside our step confirms it family by family: under the projection it takes `fun4`, `fun5`, `fun6` from 573, 456, 456 to 296, 239, 235 — against his own 293, 228, 228 — and costs +164, +111, +170, +144 on `fun1`, `fun2`, `fun8`, `fun9`, where it refuses steps that were good. But 160 of the 244 — two thirds — sit on `fun3`–`fun6`, which are pure powers of order 3, 5, 9 and 19, and there *no* interpolating model works: our step removes 0.41–0.81 halvings of the bracket per evaluation, inverse quadratic interpolation from the same points only 0.01–0.17. Chandrupatla does not win those families with his model — his traced run bisects 92, 96, 100 and 100% of his steps. We lose them because our step is slower than bisection there and our guarantee, at most twice bisection, licenses exactly that. His band inside our step confirms it family by family: under the projection it takes `fun4`, `fun5`, `fun6` from 573, 456, 456 to 296, 239, 235 — against his own 297, 233, 233 — and costs +164, +111, +170, +144 on `fun1`, `fun2`, `fun8`, `fun9`, where it refuses steps that were good.
So most of what remains is not the order of the model but the decision *when to give the model up*, and it is open because every detector measured so far confounds "the model is useless here" with something else: the band with "the configuration is not shaped for inverse quadratic interpolation", bracket progress with "the step has not moved the bracket yet" (4449 on the canonical set, §8), and the local order with nothing at all — $m\ge2$ is exactly the condition under which a tangent step loses to a halving, since it lands at $e(1-1/m)$, and it is clean (0.2% of canonical states) but blind: it reports $m\ge2$ on only 5.8% of states of families whose order is by construction 3 to 19. For the `fun1` part, three parameter-free ways round the model order have been measured and all three fail in the same way, buying the wide brackets at the price of the canonical set: a fixed fraction of the bracket (the cube-root similarity), a logarithmic change of variable, and the curvature-corrected scale of §8 item 5. The fourth way is to model the curvature directly, which is inverse quadratic interpolation — a different method rather than another reading of this one. So most of what remains is not the order of the model but the decision *when to give the model up*, and it is open because every detector measured so far confounds "the model is useless here" with something else: the band with "the configuration is not shaped for inverse quadratic interpolation", bracket progress with "the step has not moved the bracket yet" (4449 on the canonical set, §8), and the local order with nothing at all — $m\ge2$ is exactly the condition under which a tangent step loses to a halving, since it lands at $e(1-1/m)$, and it is clean (0.2% of canonical states) but blind: it reports $m\ge2$ on only 5.8% of states of families whose order is by construction 3 to 19. For the `fun1` part, three parameter-free ways round the model order have been measured and all three fail in the same way, buying the wide brackets at the price of the canonical set: a fixed fraction of the bracket (the cube-root similarity), a logarithmic change of variable, and the curvature-corrected scale of §8 item 5. The fourth way is to model the curvature directly, which is inverse quadratic interpolation — a different method rather than another reading of this one.
@@ -410,9 +407,9 @@ Every statement in §§7.3–7.4 is reproduced by `diag_rescore_cha.py`, `diag_r
2. **Every safeguard that replaces the step destroys the order.** Bracket-based and step-based replacement both drop the three-point rule from order 1.94 to 1.00 and cost 7260 and 7239 evaluations on Alefeld–Potra–Shi. Only projection preserves both. We record this because it is the trap the construction naturally falls into. 2. **Every safeguard that replaces the step destroys the order.** Bracket-based and step-based replacement both drop the three-point rule from order 1.94 to 1.00 and cost 7260 and 7239 evaluations on Alefeld–Potra–Shi. Only projection preserves both. We record this because it is the trap the construction naturally falls into.
3. **Zero slack in the projection is degenerate.** A worst case of exactly bisection demands a halving at every step and leaves the projection no freedom at all. 3. **Zero slack in the projection is not the degenerate case it was said to be.** An earlier version reported that a worst case of exactly bisection leaves the projection no freedom and makes the guard behave as bisection. Measured (Table 1a), multiplier 1 costs 4817 and 2997, against bisection's 7530 and 3090 — and on the wide-bracket set it beats the multiplier of 2. It remains the most expensive multiplier on the canonical set, which is all that survives of the claim.
4. **Regimes exist, at the level of a function family, and cannot be exploited by a switch.** The cube-root similarity beats the scheduled projection on all five members of Chandrupatla's `fun4` — 32/32/40/47/59 against 89/93/113/125/153, a factor of 2.6 — and consistency across a family at different parameters is the signature of structure rather than luck. But the same similarity over the whole Chandrupatla set costs 4099 against 3220, so a switch chosen per benchmark is worse than no switch at all: 6905 against 6026 over both sets. The scale axis offers only 5.3% headroom even to an oracle handed the best scale per problem, two thirds of it inside a single problem family, and the winners are not separated by anything the method can see before it commits — the proposed scale wins on bracket widths from $10^0$ to $10^{12}$. 4. **Regimes exist, at the level of a function family, and cannot be exploited by a switch.** The cube-root similarity beats the proposed rule on all five members of Chandrupatla's `fun4` — 32/32/40/47/59 against 89/93/113/125/153, a factor of 2.6 — and consistency across a family at different parameters is the signature of structure rather than luck. But the same similarity over the whole Chandrupatla set costs 4099 against 3220, so a switch chosen per benchmark is worse than no switch at all: 6905 against 6026 over both sets. The scale axis offers only 5.3% headroom even to an oracle handed the best scale per problem, two thirds of it inside a single problem family, and the winners are not separated by anything the method can see before it commits — the proposed scale wins on bracket widths from $10^0$ to $10^{12}$.
5. **The phase detector: derivation sound, estimator not.** Redoing the contraction law without the assumption $|f|\approx|f'(r)|e$ — taking $|f|\approx K e^m$ instead — does produce the march exponent on its own, $p = L/(L-\ln m)$ with $L=\ln(e/D)$, giving $p=1$ at a simple root and dropping to 0.50 and 0.23 as the approach flattens, which is the neighbourhood of the cube root that wins those families. Substituted back it collapses to the multiplier $m$, measurable as $m = 1/(1-\theta)$ with $\theta = f f''/(f')^2$ from three divided differences and no constant. It preserves the order (2.001) and improves the eight standard problems (82 against 84) — and loses on the full benchmark, 6304 against 6026. The estimator is the failure: on Alefeld–Potra–Shi, where almost every root is simple and the truth is $m=1$, it reports $m>1$ on 27.9% of steps with excursions to $m=34.8$, against 35.8% on the flat families. 27.9 against 35.8 is no separation. A cruder variant, correcting by the fraction of its own promise the previous step kept, fails everything: the order collapses to 0.904 and the standard problems double to 161. 5. **The phase detector: derivation sound, estimator not.** Redoing the contraction law without the assumption $|f|\approx|f'(r)|e$ — taking $|f|\approx K e^m$ instead — does produce the march exponent on its own, $p = L/(L-\ln m)$ with $L=\ln(e/D)$, giving $p=1$ at a simple root and dropping to 0.50 and 0.23 as the approach flattens, which is the neighbourhood of the cube root that wins those families. Substituted back it collapses to the multiplier $m$, measurable as $m = 1/(1-\theta)$ with $\theta = f f''/(f')^2$ from three divided differences and no constant. It preserves the order (2.001) and improves the eight standard problems (82 against 84) — and loses on the full benchmark, 6304 against 6026. The estimator is the failure: on Alefeld–Potra–Shi, where almost every root is simple and the truth is $m=1$, it reports $m>1$ on 27.9% of steps with excursions to $m=34.8$, against 35.8% on the flat families. 27.9 against 35.8 is no separation. A cruder variant, correcting by the fraction of its own promise the previous step kept, fails everything: the order collapses to 0.904 and the standard problems double to 161.
@@ -426,15 +423,15 @@ Every statement in §§7.3–7.4 is reproduced by `diag_rescore_cha.py`, `diag_r
10. **Reading the similarity at the measured order does not cure false position.** Since false position's stall is a similarity read at order one (§7.1), the obvious hope is that reading it at the measured order is the parameter-free version of the classical fix. It is not. Unguarded, the correction takes the far-end reading from 127/154 and 10676 to 128/154 and 10043 — one problem and six per cent — where Illinois, at the price of one constant (halve the stale ordinate), reaches 153/154 and 7045. A sixth of the gap is not the gap. The correction is real and it is in the right place; it is simply much smaller than a constant that was chosen for exactly this failure. *Our* 10676 and the 10560 of Table 2 are not the same algorithm — our iterate starts at the midpoint of the bracket, plain false position at an end — and the two must not be differenced. 10. **Reading the similarity at the measured order does not cure false position.** Since false position's stall is a similarity read at order one (§7.1), the obvious hope is that reading it at the measured order is the parameter-free version of the classical fix. It is not. Unguarded, the correction takes the far-end reading from 127/154 and 10676 to 128/154 and 10043 — one problem and six per cent — where Illinois, at the price of one constant (halve the stale ordinate), reaches 153/154 and 7045. A sixth of the gap is not the gap. The correction is real and it is in the right place; it is simply much smaller than a constant that was chosen for exactly this failure. *Our* 10676 and the 10560 of Table 2 are not the same algorithm — our iterate starts at the midpoint of the bracket, plain false position at an end — and the two must not be differenced.
11. **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 looked like the right diagnosis of the flat families — for the same similarity, scale and safeguard ($(1+|z|)^{-2}$, two-point, *safe*) the wide-bracket set improves from 3643 to 3474 — but the canonical set gets worse, 5822 to 6940, against the 2806 of the scheduled projection. An earlier version compared the 3474 with 4516, which is the total of a different configuration. A march needs a *ratio* of distances while an identity on narrow brackets needs an *absolute* unit, and no parameter-free unit provides both. §7.4 shows the diagnosis itself was wrong: on the flat families the right move is to halve, not to march. 11. **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 looked like the right diagnosis of the flat families — for the same similarity, scale and safeguard ($(1+|z|)^{-2}$, two-point, *safe*) the wide-bracket set improves from 3643 to 3474 — but the canonical set gets worse, 5822 to 6940, against the 2806 of the proposed rule. An earlier version compared the 3474 with 4516, which is the total of a different configuration. A march needs a *ratio* of distances while an identity on narrow brackets needs an *absolute* unit, and no parameter-free unit provides both. §7.4 shows the diagnosis itself was wrong: on the flat families the right move is to halve, not to march.
--- ---
## 9. Conclusion ## 9. Conclusion
A root finder can be derived from a similarity function alone, and the derivation shows where the content of such a method actually sits. It is not in the shape of $S$ — nine shapes were tried and their ranking is dictated by a single number, the slope of $1-S$ at the origin. It is not in making the similarity change faster — that overshoots exactly as readily as flatness stagnates. It is in the *scale* against which similarity is judged: chosen, the method is brittle and merely linear; taken from the bracket, it is false position and three centuries old; read off the data, it is superlinear. And the safeguard that makes such a method safe must project the step, not replace it: replacing costs the order. Projecting onto a budget scheduled in advance costs nothing on one benchmark and improves the method on the other, but the schedule carries a free, load-bearing multiplier; projecting onto a budget the steps earn removes it, and the price of removing it is measured rather than hidden. A root finder can be derived from a similarity function alone, and the derivation shows where the content of such a method actually sits. It is not in the shape of $S$ — nine shapes were tried and their ranking is dictated by a single number, the slope of $1-S$ at the origin. It is not in making the similarity change faster — that overshoots exactly as readily as flatness stagnates. It is in the *scale* against which similarity is judged: chosen, the method is brittle and merely linear; taken from the bracket, it is false position and three centuries old; read off the data, it is superlinear. And the safeguard that makes such a method safe must project the step, not replace it: replacing costs the order, projecting costs nothing on one benchmark and improves the method on the other. The projection's budget multiplier is a free constant, and we declare it: it does not move the canonical result and it does move the wide-bracket one; projecting onto a budget the steps earn instead removes it, at a measured price.
The resulting rule has no free constant and carries a worst case of bisection plus one step. It is not level with the best methods. On the canonical Alefeld–Potra–Shi set it needs 3568 evaluations, 29th of 264, against 2785 for Chandrupatla's method, 2820 for TOMS 748 and 2889 for Brent — though it wins more problems than it loses against each of them. On the wide-bracket set it needs 3020, ahead of TOMS 748 and Brent and behind Chandrupatla and ITP. Accept one free constant, the scheduled budget of twice bisection, and the same rule needs 2806 on the canonical set, a tie with TOMS 748 (0.5%) and 0.75% behind Chandrupatla. The claim the measurements support is therefore a trade stated in numbers: parity with the tuned methods at the price of one constant, or a constant-free rule with a tighter guarantee that trails them on the canonical set — not superiority either way. The resulting rule has one declared constant and carries a worst case of twice bisection plus one step. On the canonical Alefeld–Potra–Shi set it is level with TOMS 748 — 2806 evaluations against 2820 is a margin of 0.5% and should be read as a tie — ahead of Brent by 2.9%, and level with Chandrupatla's method in the same sense: 0.7% more in total, while winning 87 of the problems to his 42 ($p=9\cdot10^{-5}$). On the wide-bracket set Chandrupatla is genuinely stronger (2441 against 3220), and that is where the declared multiplier matters. The constant-free variant trades 762 evaluations on the canonical set for no constant, a tighter guarantee and the better wide-bracket total. The claim the measurements support is parity with the best methods on the canonical set, with a problem-by-problem edge over all of them, and a measured deficit on very wide brackets whose cause §7.4 locates — not superiority overall.
This is the same movement as in parameter-free similarity control [6], where membership functions are eliminated by normalising the input space from the data rather than by choosing widths. Here the object normalised is the value axis, and the price of choosing it by hand is measured rather than asserted. This is the same movement as in parameter-free similarity control [6], where membership functions are eliminated by normalising the input space from the data rather than by choosing widths. Here the object normalised is the value axis, and the price of choosing it by hand is measured rather than asserted.
+2 -1
View File
@@ -13,11 +13,12 @@ from __future__ import annotations
import math import math
from collections import defaultdict from collections import defaultdict
from simroot.baselines import CHANDRUPATLA_BASELINE
from simroot.baselines import best_of, chandrupatla, chandrupatla_scipy from simroot.baselines import best_of, chandrupatla, chandrupatla_scipy
from simroot.pure import pure_root from simroot.pure import pure_root
from sweep_full import ACC, MAXFEV, XTOL, load_problems from sweep_full import ACC, MAXFEV, XTOL, load_problems
CHA_BASE = best_of(chandrupatla, chandrupatla_scipy) CHA_BASE = CHANDRUPATLA_BASELINE # the library alone (2026-09-21)
def ours(f, a, b): def ours(f, a, b):
+2 -1
View File
@@ -16,6 +16,7 @@ from __future__ import annotations
import math import math
from collections import defaultdict from collections import defaultdict
from simroot.baselines import CHANDRUPATLA_BASELINE
from simroot.baselines import chandrupatla, chandrupatla_scipy from simroot.baselines import chandrupatla, chandrupatla_scipy
from simroot.pure import pure_root from simroot.pure import pure_root
from stage4_why import phase_boundary, traced from stage4_why import phase_boundary, traced
@@ -26,7 +27,7 @@ from sweep_full import ACC, MAXFEV, XTOL, load_problems
# total as 2993 instead of 1487 and manufactured a 1299-evaluation "endgame win" for us # total as 2993 instead of 1487 and manufactured a 1299-evaluation "endgame win" for us
# (caught 2026-09-21). Each implementation is traced separately and the cheaper split kept, # (caught 2026-09-21). Each implementation is traced separately and the cheaper split kept,
# which is what best_of means for cost. # which is what best_of means for cost.
CHA_IMPLS = (chandrupatla, chandrupatla_scipy) CHA_IMPLS = (CHANDRUPATLA_BASELINE,) # the library alone (2026-09-21)
def ours(f, a, b, xtol, maxfev): def ours(f, a, b, xtol, maxfev):
+2 -1
View File
@@ -14,6 +14,7 @@ import math
from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS_DICTS from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS_DICTS
from simroot.baselines import CHANDRUPATLA_BASELINE
from simroot.baselines import chandrupatla, chandrupatla_scipy from simroot.baselines import chandrupatla, chandrupatla_scipy
from simroot.pure import pure_root from simroot.pure import pure_root
from stage4_why import observed_order from stage4_why import observed_order
@@ -48,7 +49,7 @@ def main():
m = multiplicity_probe(f, root) m = multiplicity_probe(f, root)
x_o, n_o, ok_o = ours(f, a, b, XTOL, MAXFEV) x_o, n_o, ok_o = ours(f, a, b, XTOL, MAXFEV)
best = None best = None
for impl in (chandrupatla, chandrupatla_scipy): for impl in (CHANDRUPATLA_BASELINE,): # the library alone (2026-09-21)
try: try:
x_c, n_c, ok_c = impl(f, a, b, XTOL, MAXFEV) x_c, n_c, ok_c = impl(f, a, b, XTOL, MAXFEV)
except Exception: except Exception:
+2 -1
View File
@@ -13,11 +13,12 @@ import math
from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS_DICTS from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS_DICTS
from simroot.baselines import CHANDRUPATLA_BASELINE
from simroot.baselines import best_of, chandrupatla, chandrupatla_scipy, toms748 from simroot.baselines import best_of, chandrupatla, chandrupatla_scipy, toms748
from simroot.pure import pure_root from simroot.pure import pure_root
from sweep_full import ACC, XTOL from sweep_full import ACC, XTOL
CHA = best_of(chandrupatla, chandrupatla_scipy) CHA = CHANDRUPATLA_BASELINE # the library alone (2026-09-21)
def main(): def main():
+2 -1
View File
@@ -16,11 +16,12 @@ import math
from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS_DICTS from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS_DICTS
from simroot.baselines import CHANDRUPATLA_BASELINE
from simroot.baselines import best_of, chandrupatla, chandrupatla_scipy from simroot.baselines import best_of, chandrupatla, chandrupatla_scipy
from simroot.pure import pure_root from simroot.pure import pure_root
from sweep_full import MAXFEV, XTOL from sweep_full import MAXFEV, XTOL
CHA = best_of(chandrupatla, chandrupatla_scipy) CHA = CHANDRUPATLA_BASELINE # the library alone (2026-09-21)
def main(): def main():
+2 -1
View File
@@ -34,6 +34,7 @@ import math
from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS_DICTS from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS_DICTS
from simroot.baselines import CHANDRUPATLA_BASELINE
from simroot.baselines import chandrupatla, chandrupatla_scipy from simroot.baselines import chandrupatla, chandrupatla_scipy
from simroot.pure import pure_root from simroot.pure import pure_root
@@ -72,7 +73,7 @@ def halvings(e_before, landing, r):
def ch_cost(f, a, b, r): def ch_cost(f, a, b, r):
best = None best = None
for impl in (chandrupatla, chandrupatla_scipy): for impl in (CHANDRUPATLA_BASELINE,): # the library alone (2026-09-21)
try: try:
x, n, c = impl(f, a, b, XTOL, MAXFEV) x, n, c = impl(f, a, b, XTOL, MAXFEV)
except Exception: except Exception:
+3 -2
View File
@@ -17,6 +17,7 @@ from __future__ import annotations
import math import math
from simroot.baselines import BRENT_BASELINE, CHANDRUPATLA_BASELINE
from simroot.baselines import (best_of, bisection, brent, brent_scipy, chandrupatla, from simroot.baselines import (best_of, bisection, brent, brent_scipy, chandrupatla,
chandrupatla_scipy, itp, toms748) chandrupatla_scipy, itp, toms748)
from simroot.pure import pure_root from simroot.pure import pure_root
@@ -32,10 +33,10 @@ METHODS = {
"ours (published proj)": lambda f, a, b: pure_root(f, a, b, XTOL, MAXFEV, "ours (published proj)": lambda f, a, b: pure_root(f, a, b, XTOL, MAXFEV,
similarity="ratio", scale="derived3", similarity="ratio", scale="derived3",
guard="project"), guard="project"),
"chandrupatla": lambda f, a, b: best_of(chandrupatla, chandrupatla_scipy)(f, a, b, XTOL, "chandrupatla": lambda f, a, b: CHANDRUPATLA_BASELINE(f, a, b, XTOL,
MAXFEV), MAXFEV),
"toms748": lambda f, a, b: toms748(f, a, b, XTOL, MAXFEV), "toms748": lambda f, a, b: toms748(f, a, b, XTOL, MAXFEV),
"brent": lambda f, a, b: best_of(brent, brent_scipy)(f, a, b, XTOL, MAXFEV), "brent": lambda f, a, b: BRENT_BASELINE(f, a, b, XTOL, MAXFEV),
"itp": lambda f, a, b: itp(f, a, b, XTOL, MAXFEV), "itp": lambda f, a, b: itp(f, a, b, XTOL, MAXFEV),
"bisection": lambda f, a, b: bisection(f, a, b, XTOL, MAXFEV), "bisection": lambda f, a, b: bisection(f, a, b, XTOL, MAXFEV),
} }
+3 -2
View File
@@ -17,6 +17,7 @@ from __future__ import annotations
import math import math
from simroot.baselines import BRENT_BASELINE, CHANDRUPATLA_BASELINE
from simroot.baselines import (best_of, bisection, brent, brent_scipy, chandrupatla, from simroot.baselines import (best_of, bisection, brent, brent_scipy, chandrupatla,
chandrupatla_scipy, itp, toms748) chandrupatla_scipy, itp, toms748)
from simroot.pure import pure_root from simroot.pure import pure_root
@@ -32,10 +33,10 @@ METHODS = {
"ours (published proj)": lambda f, a, b: pure_root(f, a, b, XTOL, MAXFEV, "ours (published proj)": lambda f, a, b: pure_root(f, a, b, XTOL, MAXFEV,
similarity="ratio", scale="derived3", similarity="ratio", scale="derived3",
guard="project"), guard="project"),
"chandrupatla": lambda f, a, b: best_of(chandrupatla, chandrupatla_scipy)(f, a, b, XTOL, "chandrupatla": lambda f, a, b: CHANDRUPATLA_BASELINE(f, a, b, XTOL,
MAXFEV), MAXFEV),
"toms748": lambda f, a, b: toms748(f, a, b, XTOL, MAXFEV), "toms748": lambda f, a, b: toms748(f, a, b, XTOL, MAXFEV),
"brent": lambda f, a, b: best_of(brent, brent_scipy)(f, a, b, XTOL, MAXFEV), "brent": lambda f, a, b: BRENT_BASELINE(f, a, b, XTOL, MAXFEV),
"itp": lambda f, a, b: itp(f, a, b, XTOL, MAXFEV), "itp": lambda f, a, b: itp(f, a, b, XTOL, MAXFEV),
"bisection": lambda f, a, b: bisection(f, a, b, XTOL, MAXFEV), "bisection": lambda f, a, b: bisection(f, a, b, XTOL, MAXFEV),
} }
+2 -1
View File
@@ -22,6 +22,7 @@ import math
from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS_DICTS from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS_DICTS
from simroot.baselines import CHANDRUPATLA_BASELINE
from simroot.baselines import chandrupatla, chandrupatla_scipy from simroot.baselines import chandrupatla, chandrupatla_scipy
from simroot.pure import pure_root from simroot.pure import pure_root
from sweep_full import ACC, MAXFEV, XTOL from sweep_full import ACC, MAXFEV, XTOL
@@ -88,7 +89,7 @@ def main():
direction="dissimilar"), direction="dissimilar"),
f, a, b, root) f, a, b, root)
best = None best = None
for impl in (chandrupatla, chandrupatla_scipy): for impl in (CHANDRUPATLA_BASELINE,): # the library alone (2026-09-21)
r = run(impl, f, a, b, root) r = run(impl, f, a, b, root)
if r and (best is None or r[0] < best[0]): if r and (best is None or r[0] < best[0]):
best = r best = r
+2 -1
View File
@@ -21,6 +21,7 @@ import math
from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS_DICTS from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS_DICTS
from simroot.baselines import CHANDRUPATLA_BASELINE
from simroot.baselines import bisection, chandrupatla, chandrupatla_scipy from simroot.baselines import bisection, chandrupatla, chandrupatla_scipy
from simroot.pure import pure_root from simroot.pure import pure_root
from sweep_full import ACC, MAXFEV, XTOL from sweep_full import ACC, MAXFEV, XTOL
@@ -42,7 +43,7 @@ def main():
and (abs(float(x) - root) <= ACC or f(float(x)) == 0.0)) and (abs(float(x) - root) <= ACC or f(float(x)) == 0.0))
_xb, nb, cb = bisection(f, a, b, XTOL, MAXFEV) _xb, nb, cb = bisection(f, a, b, XTOL, MAXFEV)
best = None best = None
for impl in (chandrupatla, chandrupatla_scipy): for impl in (CHANDRUPATLA_BASELINE,): # the library alone (2026-09-21)
try: try:
xc, nc, cc = impl(f, a, b, XTOL, MAXFEV) xc, nc, cc = impl(f, a, b, XTOL, MAXFEV)
except Exception: except Exception:
+36
View File
@@ -0,0 +1,36 @@
"""Which rows of sweep_full.json changed when the baselines moved to the library?
Only 'chandrupatla' and 'brent' may change: every configuration of ours and every
other baseline is deterministic and untouched by the switch, so any other difference
is a regression, not a consequence.
python -u diff_sweeps.py OLD.json NEW.json
"""
from __future__ import annotations
import json
import sys
def main(old_path, new_path):
old = {r["label"]: r for r in json.load(open(old_path, encoding="utf8"))}
new = {r["label"]: r for r in json.load(open(new_path, encoding="utf8"))}
print(f"rows: old {len(old)}, new {len(new)}; only in old: {sorted(set(old) - set(new))}"
f"; only in new: {sorted(set(new) - set(old))}")
changed = []
for lbl in sorted(set(old) & set(new)):
for s in ("APS", "CHA"):
if old[lbl][s]["per"] != new[lbl][s]["per"]:
changed.append((lbl, s, old[lbl][s]["total"], new[lbl][s]["total"],
old[lbl][s]["solved"], new[lbl][s]["solved"],
old[lbl][s]["over"], new[lbl][s]["over"]))
for c in changed:
print(f" CHANGED {c[0]:<14} {c[1]}: total {c[2]} -> {c[3]}, solved {c[4]} -> {c[5]}, "
f"over2x {c[6]} -> {c[7]}")
unexpected = [c for c in changed if c[0] not in ("chandrupatla", "brent")]
print(f"unexpected changes: {len(unexpected)}")
return changed, unexpected
if __name__ == "__main__":
main(sys.argv[1], sys.argv[2])
+1 -1
View File
@@ -63,7 +63,7 @@ def chandrupatla_traced(f, a, b, xtol, maxfev):
xm, fm = (x1, f1) if abs(f1) < abs(f2) else (x2, f2) xm, fm = (x1, f1) if abs(f1) < abs(f2) else (x2, f2)
if fm == 0.0: if fm == 0.0:
return xm, g.n, True, trace return xm, g.n, True, trace
tol = 2.0 * 2.22e-16 * abs(xm) + xtol tol = 2.0 * 2.22e-16 * abs(xm) + 0.5 * xtol # mirrors the 2026-09-21 fix
span = abs(x2 - x1) span = abs(x2 - x1)
if span == 0.0: if span == 0.0:
return xm, g.n, True, trace return xm, g.n, True, trace
+10 -5
View File
@@ -19,12 +19,17 @@ BASE = {"bisection", "regula_falsi", "illinois", "pegasus", "anderson_bjorck",
# ranking is looked at: # ranking is looked at:
# similarity a nonzero slope of (1 - S) at 0 -> the ratio form 1 - |z| # similarity a nonzero slope of (1 - S) at 0 -> the ratio form 1 - |z|
# scale the finest data-derived one -> the three-point difference # scale the finest data-derived one -> the three-point difference
# safeguard the one that carries a bound without destroying the order # safeguard the one that carries a bound without destroying the order ->
# AND without a free constant -> the credit projection, whose # the scheduled projection, twice the bisection budget
# endowment is a licence of one unconstrained step rather than # Valeri's decision, 2026-09-21 (variant a): the projection is proposed with its
# a number (see simroot.pure.project_credit) # multiplier DECLARED as the method's one free constant - on the canonical set the
# total is the same for every multiplier from 1.25 to 4, on the wide-bracket set it
# is not (measure_budget_factor.py). The earned projection (credit) is kept as the
# constant-free variant; it was PROPOSED here for a while, a choice recorded in code
# but never taken by Valeri.
# The grid is evidence for the theory, not the method's selection procedure. # The grid is evidence for the theory, not the method's selection procedure.
PROPOSED = "pure.ratio.derived3.credit" PROPOSED = "pure.ratio.derived3.project"
CREDIT = "pure.ratio.derived3.credit" # the constant-free variant
TUNED = {"illinois", "pegasus", "anderson_bjorck", "brent", "toms748", TUNED = {"illinois", "pegasus", "anderson_bjorck", "brent", "toms748",
"itp", "itp_oracle"} "itp", "itp_oracle"}
+253
View File
@@ -0,0 +1,253 @@
=== diag_model_order
fun3.1 64 45 45 0.41 0.17 0.17
fun3.2 56 47 29 0.59 0.13 0.19
fun3.3 66 60 31 0.57 0.10 0.14
fun3.4 73 69 33 0.63 0.13 0.16
fun3.5 85 84 28 0.56 0.06 0.14
fun4.1 54 45 27 0.45 0.10 0.10
fun4.2 56 47 28 0.47 0.10 0.10
fun4.3 70 56 38 0.49 0.06 0.08
fun4.4 73 68 34 0.61 0.13 0.15
fun4.5 86 81 35 0.60 0.16 0.17
fun5.1 57 45 31 0.44 0.03 0.03
fun5.2 54 45 28 0.58 0.10 0.11
fun5.3 55 46 27 0.63 0.01 0.02
fun5.4 57 48 30 0.64 0.04 0.05
fun5.5 59 49 32 0.63 0.04 0.06
fun6.1 52 45 36 0.63 0.04 0.04
fun6.2 54 45 39 0.55 0.01 0.01
fun6.3 50 46 35 0.80 0.03 0.03
fun6.4 52 48 35 0.81 0.03 0.03
fun6.5 55 49 38 0.79 0.03 0.03
fun8.1 13 10 7 7.59 10.28 10.41
fun8.4 16 14 12 5.68 7.72 11.15
fun8.5 18 15 13 4.98 7.24 9.41
fun9.2 12 9 7 5.02 0.66 0.66
fun9.3 14 11 10 0.94 3.08 3.05
fun9.4 15 13 7 7.54 9.23 3.31
fun9.5 19 14 5 7.39 3.79 5.02
problems where we lose to Chandrupatla: 33, states 822
Q1 triples bracketing zero with exactly ONE negative Lagrange weight: 428/433 -> IDENTITY FAILS
model defined and landing strictly inside the proven bracket: ours 86%, iqiR 77%, iqiB 77%
Q2/Q3 mean halvings per ONE evaluation from the same state (bracket | error), valid states only
approach (error >= 1/2 bracket) ours 1.32 | 3.39 (n=40) iqiR 0.57 | 2.28 (n=6) iqiB 0.53 | 2.14 (n=6)
endgame ours 1.24 | 0.90 (n=669) iqiR 0.83 | 1.27 (n=628) iqiB 0.88 | 1.31 (n=631)
PAIRED on states where both are valid: mean (IQI - ours) bracket halvings
iqiR: -0.34 per evaluation (n=628)
iqiB: -0.23 per evaluation (n=631)
=== diag_model_order_checks
C1 Chandrupatla on the problems where we lose, by family
family probs ours CH gap CH bisect share
fun1 3 141 83 58 74%
fun2 3 99 94 5 84%
fun3 5 344 305 39 92%
fun4 5 339 297 42 96%
fun5 5 282 233 49 100%
fun6 5 263 233 30 100%
fun8 3 47 39 8 50%
fun9 4 60 47 13 40%
total gap on the losing problems: 244
C2 the 5 states breaking 'exactly one negative weight'
fun6.1 f = [1.000e-175, -1.460e-160, -1.153e-150] w = [1.000e+00, 0.000e+00, -0.000e+00] zero weights: 2 min|f| 1.0e-175
fun6.1 f = [4.634e-163, 1.000e-175, -1.460e-160] w = [-0.000e+00, 1.000e+00, 0.000e+00] zero weights: 2 min|f| 1.0e-175
fun6.3 f = [-2.588e-172, 2.588e-153, 2.588e-153] w = [1.000e+00, 0.000e+00, -0.000e+00] zero weights: 2 min|f| 2.6e-172
fun6.3 f = [6.667e-160, -2.588e-172, 2.588e-153] w = [0.000e+00, 1.000e+00, -0.000e+00] zero weights: 2 min|f| 2.6e-172
fun6.3 f = [5.745e-156, 6.667e-160, -2.588e-172] w = [-0.000e+00, 0.000e+00, 1.000e+00] zero weights: 2 min|f| 2.6e-172
=== diag_rescore_aps
APS problems: 154
method strict solved/total honest solved/total disagreements
ours (shape) 153/154 2527 154/154 2337 1
ours (dissimilar) 153/154 3123 154/154 2934 1
ours (published proj) 153/154 2806 154/154 2634 1
chandrupatla 153/154 2787 154/154 2595 1
toms748 153/154 2820 154/154 2636 1
brent 153/154 2889 154/154 2707 1
itp 153/154 3957 154/154 3767 1
bisection 153/154 7530 154/154 7338 1
problems where the two criteria disagree (a root found, scored a failure):
ours (shape) aps.13.00 x=+0.008638489218 root=+0 f(x)=+0.000e+00 after 10 evals
ours (dissimilar) aps.13.00 x=+0.0248491106 root=+0 f(x)=+0.000e+00 after 11 evals
ours (published proj) aps.13.00 x=-0.01020453457 root=+0 f(x)=+0.000e+00 after 28 evals
chandrupatla aps.13.00 x=-0.008282706313 root=+0 f(x)=+0.000e+00 after 8 evals
toms748 aps.13.00 x=-0.0002236853352 root=+0 f(x)=+0.000e+00 after 16 evals
brent aps.13.00 x=+0.01542133486 root=+0 f(x)=+0.000e+00 after 18 evals
itp aps.13.00 x=+0.03079215104 root=+0 f(x)=+0.000e+00 after 10 evals
bisection aps.13.00 x=+0.015625 root=+0 f(x)=+0.000e+00 after 8 evals
problems no method solved under the strict rule:
aps.13.00 solved by 0 of 8 methods
aps.01.00 solved by 8 of 8 methods
aps.02.00 solved by 8 of 8 methods
aps.02.01 solved by 8 of 8 methods
aps.02.02 solved by 8 of 8 methods
aps.02.03 solved by 8 of 8 methods
diagnosis of aps.13.00: bracket [-1, 4] declared root 0
f(a)=-3.678794e-01 f(b)=+3.757652e+00 f(root)=+0.000000e+00
f(root+1e-12) = +0.000000e+00 f(root-1e-12) = +0.000000e+00
f(root+1e-09) = +0.000000e+00 f(root-1e-09) = +0.000000e+00
f(root+1e-06) = +0.000000e+00 f(root-1e-06) = +0.000000e+00
f(root+0.001) = +0.000000e+00 f(root-0.001) = +0.000000e+00
ours returns x=+0.0248491106033 after 11 evals, f(x)=+0.000e+00, |x-root|=0.0248
|f(x)| below smallest normal? True
=== diag_rescore_cha
method strict: solved/total honest: solved/total fun7 strict fun7 honest
ours (shape) 40/45 3108 45/45 2159 0/5 5/5
ours (dissimilar) 40/45 2683 45/45 1740 0/5 5/5
ours (published proj) 40/45 3220 45/45 2357 0/5 5/5
chandrupatla 40/45 2441 45/45 1505 0/5 5/5
toms748 40/45 4000 45/45 3088 0/5 5/5
brent 38/45 4115 43/45 3238 0/5 5/5
itp 40/45 2857 45/45 1907 0/5 5/5
bisection 40/45 3090 45/45 2136 0/5 5/5
ranking under the HONEST criterion (fewest unsolved, then evaluations):
chandrupatla 45/45 1505
ours (dissimilar) 45/45 1740
itp 45/45 1907
bisection 45/45 2136
ours (shape) 45/45 2159
ours (published proj) 45/45 2357
toms748 45/45 3088
brent 43/45 3238
ranking under the STRICT criterion, for comparison:
chandrupatla 40/45 2441
ours (dissimilar) 40/45 2683
itp 40/45 2857
bisection 40/45 3090
ours (shape) 40/45 3108
ours (published proj) 40/45 3220
toms748 40/45 4000
brent 38/45 4115
=== diag_cha_gap
fun7.3 200 200 +0 43.3 9.1% 18.2% OURS FAIL
fun7.4 200 200 +0 45.6 0.0% 0.0% OURS FAIL
fun7.5 200 200 +0 46.6 0.0% 0.0% OURS FAIL
fun8.1 13 10 +3 40.9 0.0% 15.4%
fun8.2 11 11 +0 41.4 0.0% 18.2%
fun8.3 12 13 -1 43.0 0.0% 16.7%
fun8.4 16 14 +2 44.6 0.0% 18.8%
fun8.5 18 15 +3 46.2 0.0% 22.2%
fun9.1 8 8 +0 39.9 0.0% 0.0%
fun9.2 12 9 +3 41.4 8.3% 8.3%
fun9.3 14 11 +3 43.0 14.3% 14.3%
fun9.4 15 13 +2 44.6 13.3% 6.7%
fun9.5 19 14 +5 46.2 5.3% 5.3%
totals: ours 2683, Chandrupatla 2441, gap +242
per family (ours / theirs / gap):
fun1 n=5 160 / 103 gap +57
fun5 n=5 282 / 233 gap +49
fun4 n=5 339 / 297 gap +42
fun3 n=5 344 / 305 gap +39
fun6 n=5 263 / 233 gap +30
fun9 n=5 68 / 55 gap +13
fun8 n=5 70 / 63 gap +7
fun2 n=5 157 / 152 gap +5
fun7 n=5 1000 / 1000 gap +0
the five problems that cost us most:
fun1.4 ours 59 CH 24 gap +35
fun3.1 ours 64 CH 45 gap +19
fun1.5 ours 63 CH 44 gap +19
fun4.3 ours 70 CH 56 gap +14
fun5.1 ours 57 CH 45 gap +12
the five where we are ahead:
fun1.1 ours 7 CH 8 gap -1
fun8.3 ours 12 CH 13 gap -1
fun1.2 ours 12 CH 12 gap +0
fun2.1 ours 9 CH 9 gap +0
fun2.5 ours 49 CH 49 gap +0
=== diag_fun1_endgame
problem bracket root m ours n ord CH n ord
fun1.1 [ 2, 3] 2.0946 1.00 7 4.28 8 1.57
|f| at root+1e-5 = 1.116e-04, root+1e-2 = 1.122e-01, ours x=+2.094551482 ok=True
fun1.2 [ 1, 10] 2.0946 1.00 12 1.87 12 1.81
|f| at root+1e-5 = 1.116e-04, root+1e-2 = 1.122e-01, ours x=+2.094551482 ok=True
fun1.3 [ 1, 100] 2.0946 1.00 19 2.46 15 1.67
|f| at root+1e-5 = 1.116e-04, root+1e-2 = 1.122e-01, ours x=+2.094551482 ok=True
fun1.4 [ -1e+04, 1e+04] 2.0946 1.00 59 3.63 24 2.02
|f| at root+1e-5 = 1.116e-04, root+1e-2 = 1.122e-01, ours x=+2.094551482 ok=True
fun1.5 [ -1e+10, 1e+10] 2.0946 1.00 63 1.85 44 2.20
|f| at root+1e-5 = 1.116e-04, root+1e-2 = 1.122e-01, ours x=+2.094551482 ok=True
=== diag_who_caps_us
problem width bisect CH none step project safe credit
fun1.1 1 42 8 7 7 7 7 19
fun1.2 9 46 12 12 12 12 12 45
fun1.3 99 49 15 30 19 30 19 50
fun1.4 2e+04 57 24 46 59 46 59 58
fun1.5 2e+10 77 44 57 63 57 63 76
fun3.1 5 45 45 61 64 61 64 46
fun4.3 2e+04 57 56 73 70 73 70 58
fun5.1 5 45 45 68 57 68 57 46
legend: ✗ = the run did not reach the root (an unguarded method may not)
if guard='none' marches where 'safe'/'project' crawl, the GUARANTEE is the cap;
if 'none' crawls too, the STEP RULE is the cap.
=== diag_sign_flips
problem method evals probes that shrank share halvings/eval
fun1.3 ours 19 15 78.9% 2.498
fun1.3 chandrupatla 15 13 86.7% 3.166
fun1.4 ours 59 54 91.5% 0.930
fun1.4 chandrupatla 24 21 87.5% 2.298
fun1.5 ours 63 60 95.2% 1.179
fun1.5 chandrupatla 44 41 93.2% 1.706
fun3.1 ours 64 61 95.3% 0.672
fun3.1 chandrupatla 45 43 95.6% 0.956
fun5.1 ours 57 47 82.5% 0.741
fun5.1 chandrupatla 45 43 95.6% 0.956
=== diag_fun7_root
fun7.1 declared root +0
ours returned x=+0.0248491106 after 11 evals f(x)=+0.000e+00 |x-root|=0.0248 f IS ZERO THERE
chandrupatla returned x=-0.008282706313 after 8 evals f(x)=-0.000e+00 |x-root|=0.00828 f IS ZERO THERE
f is identically zero at least up to root + 0.0316
fun7.2 declared root +0
ours returned x=-0.02750094689 after 11 evals f(x)=-0.000e+00 |x-root|=0.0275 f IS ZERO THERE
chandrupatla returned x=-0.00805799861 after 8 evals f(x)=-0.000e+00 |x-root|=0.00806 f IS ZERO THERE
f is identically zero at least up to root + 0.0316
fun7.3 declared root +0
ours returned x=+0.005929390833 after 11 evals f(x)=+0.000e+00 |x-root|=0.00593 f IS ZERO THERE
chandrupatla returned x=-0.02563062503 after 11 evals f(x)=-0.000e+00 |x-root|=0.0256 f IS ZERO THERE
f is identically zero at least up to root + 0.0316
fun7.4 declared root +0
ours returned x=+0.01889661575 after 11 evals f(x)=+0.000e+00 |x-root|=0.0189 f IS ZERO THERE
chandrupatla returned x=-0.001938145806 after 18 evals f(x)=-0.000e+00 |x-root|=0.00194 f IS ZERO THERE
f is identically zero at least up to root + 0.0316
fun7.5 declared root +0
ours returned x=+0.008107544271 after 13 evals f(x)=+0.000e+00 |x-root|=0.00811 f IS ZERO THERE
chandrupatla returned x=+0.02036778628 after 19 evals f(x)=+0.000e+00 |x-root|=0.0204 f IS ZERO THERE
f is identically zero at least up to root + 0.0316
=== diag_band_by_family
guard=project: family no-gate band delta
fun1 99 263 +164
fun2 141 252 +111
fun3 338 310 -28
fun4 573 296 -277
fun5 456 239 -217
fun6 456 235 -221
fun7 1000 1000 +0
fun8 52 222 +170
fun9 105 249 +144
guard=credit: family no-gate band delta
fun1 258 239 -19
fun2 292 271 -21
fun3 274 237 -37
fun4 294 234 -60
fun5 238 238 +0
fun6 238 235 -3
fun7 1000 1000 +0
fun8 220 218 -2
fun9 206 227 +21
DIAGS_DONE
+5
View File
@@ -0,0 +1,5 @@
rows: old 264, new 264; only in old: []; only in new: []
CHANGED brent CHA: total 4099 -> 4115, solved 38 -> 38, over2x 18 -> 18
CHANGED chandrupatla APS: total 2785 -> 2787, solved 153 -> 153, over2x 0 -> 0
CHANGED chandrupatla CHA: total 2423 -> 2441, solved 40 -> 40, over2x 0 -> 0
unexpected changes: 0
+14 -14
View File
@@ -3,15 +3,15 @@ configurations in the sweep: 264
APS, 154 problems APS, 154 problems
| # | configuration | tuned | solved | total evals | $>2\times$ | | # | configuration | tuned | solved | total evals | $>2\times$ |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| 1 | Chandrupatla | no | 153/154 | 2785 | 0 | | 1 | Chandrupatla | no | 153/154 | 2787 | 0 |
| 2 | the same rule with no safeguard at all | no | 153/154 | 2806 | 0 | | 2 | the same rule with no safeguard at all | no | 153/154 | 2806 | 0 |
| 3 | projection variant (tent, 3-point, projection) | no | 153/154 | 2806 | 0 | | 3 | **proposed: tent, 3-point, projection ($c=2$)** | no | 153/154 | 2806 | 0 |
| 4 | TOMS 748 | yes | 153/154 | 2820 | 0 | | 4 | TOMS 748 | yes | 153/154 | 2820 | 0 |
| 5 | Brent | yes | 153/154 | 2889 | 0 | | 5 | Brent | yes | 153/154 | 2889 | 0 |
| 12 | Dekker | no | 153/154 | 3158 | 0 | | 12 | Dekker | no | 153/154 | 3158 | 0 |
| 13 | ITP, per-problem oracle | yes | 153/154 | 3260 | 0 | | 13 | ITP, per-problem oracle | yes | 153/154 | 3260 | 0 |
| 20 | Ridders | no | 153/154 | 3313 | 0 | | 20 | Ridders | no | 153/154 | 3313 | 0 |
| 29 | **proposed: tent, 3-point, credit** | no | 153/154 | 3568 | 0 | | 29 | constant-free variant: earned projection | no | 153/154 | 3568 | 0 |
| 34 | ITP, recommended constants | yes | 153/154 | 3957 | 0 | | 34 | ITP, recommended constants | yes | 153/154 | 3957 | 0 |
| 73 | Illinois | yes | 153/154 | 7045 | 0 | | 73 | Illinois | yes | 153/154 | 7045 | 0 |
| 79 | bisection | no | 153/154 | 7530 | 0 | | 79 | bisection | no | 153/154 | 7530 | 0 |
@@ -21,27 +21,27 @@ CHA, 45 problems
| # | configuration | tuned | solved | total evals | $>2\times$ | | # | configuration | tuned | solved | total evals | $>2\times$ |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| 1 | similarity, $1-\sqrt{|z|}$, bracket scale, projection | no | 41/45 | 4516 | 0 | | 1 | similarity, $1-\sqrt{|z|}$, bracket scale, projection | no | 41/45 | 4516 | 0 |
| 2 | Chandrupatla | no | 40/45 | 2423 | 0 | | 2 | Chandrupatla | no | 40/45 | 2441 | 0 |
| 3 | ITP, per-problem oracle | yes | 40/45 | 2611 | 0 | | 3 | ITP, per-problem oracle | yes | 40/45 | 2611 | 0 |
| 4 | similarity, $e^{-|z|}$, bracket scale, both guards | no | 40/45 | 2733 | 0 | | 4 | similarity, $e^{-|z|}$, bracket scale, both guards | no | 40/45 | 2733 | 0 |
| 6 | ITP, recommended constants | yes | 40/45 | 2857 | 0 | | 6 | ITP, recommended constants | yes | 40/45 | 2857 | 0 |
| 10 | **proposed: tent, 3-point, credit** | no | 40/45 | 3020 | 0 | | 10 | constant-free variant: earned projection | no | 40/45 | 3020 | 0 |
| 16 | bisection | no | 40/45 | 3090 | 0 | | 16 | bisection | no | 40/45 | 3090 | 0 |
| 47 | projection variant (tent, 3-point, projection) | no | 40/45 | 3220 | 0 | | 47 | **proposed: tent, 3-point, projection ($c=2$)** | no | 40/45 | 3220 | 0 |
| 164 | TOMS 748 | yes | 40/45 | 4000 | 15 | | 164 | TOMS 748 | yes | 40/45 | 4000 | 15 |
| 226 | Brent | yes | 38/45 | 4099 | 18 | | 226 | Brent | yes | 38/45 | 4115 | 18 |
| 228 | the same rule with no safeguard at all | no | 35/45 | 4104 | 10 | | 228 | the same rule with no safeguard at all | no | 35/45 | 4104 | 10 |
head to head, pure.ratio.derived3.project (wins/ties/losses on problems both solve)
APS: chandrupatla 87/24/42, toms748 86/16/51, brent 102/11/40, itp 132/3/18, itp_oracle 121/4/28, bisection 152/1/0
CHA: chandrupatla 9/5/26, toms748 31/1/8, brent 30/4/4, itp 17/0/23, itp_oracle 16/0/24, bisection 21/0/19
head to head, pure.ratio.derived3.credit (wins/ties/losses on problems both solve) head to head, pure.ratio.derived3.credit (wins/ties/losses on problems both solve)
APS: chandrupatla 79/3/71, toms748 77/9/67, brent 78/5/70, itp 103/10/40, itp_oracle 86/6/61, bisection 122/1/30 APS: chandrupatla 79/3/71, toms748 77/9/67, brent 78/5/70, itp 103/10/40, itp_oracle 86/6/61, bisection 122/1/30
CHA: chandrupatla 5/0/35, toms748 24/0/16, brent 18/0/20, itp 12/20/8, itp_oracle 2/3/35, bisection 5/0/35 CHA: chandrupatla 5/0/35, toms748 24/0/16, brent 18/0/20, itp 12/20/8, itp_oracle 2/3/35, bisection 5/0/35
head to head, pure.ratio.derived3.project (wins/ties/losses on problems both solve)
APS: chandrupatla 86/25/42, toms748 86/16/51, brent 102/11/40, itp 132/3/18, itp_oracle 121/4/28, bisection 152/1/0
CHA: chandrupatla 9/5/26, toms748 31/1/8, brent 30/4/4, itp 17/0/23, itp_oracle 16/0/24, bisection 21/0/19
both sets pure.ratio.derived3.credit 6588
both sets pure.ratio.derived3.project 6026 both sets pure.ratio.derived3.project 6026
both sets chandrupatla 5208 both sets pure.ratio.derived3.credit 6588
both sets chandrupatla 5228
both sets toms748 6820 both sets toms748 6820
both sets brent 6988 both sets brent 7004
both sets itp_oracle 5871 both sets itp_oracle 5871
+318
View File
@@ -0,0 +1,318 @@
OK number of configurations got 264 want 264
The proposed method (projection, multiplier declared):
OK APS place got 3 want 3
OK APS total got 2806 want 2806
OK APS solved got 153 want 153
OK CHA place got 47 want 47
OK CHA total got 3220 want 3220
OK CHA solved got 40 want 40
OK both sets got 6026 want 6026
Head to head of the proposed method:
OK APS vs chandrupatla got (87, 24, 42) want (87, 24, 42)
OK APS vs toms748 got (86, 16, 51) want (86, 16, 51)
OK APS vs brent got (102, 11, 40) want (102, 11, 40)
OK APS vs itp got (132, 3, 18) want (132, 3, 18)
OK APS vs itp_oracle got (121, 4, 28) want (121, 4, 28)
OK APS vs bisection got (152, 1, 0) want (152, 1, 0)
OK CHA vs chandrupatla got (9, 5, 26) want (9, 5, 26)
OK CHA vs toms748 got (31, 1, 8) want (31, 1, 8)
OK CHA vs brent got (30, 4, 4) want (30, 4, 4)
OK CHA vs itp got (17, 0, 23) want (17, 0, 23)
OK CHA vs itp_oracle got (16, 0, 24) want (16, 0, 24)
OK APS vs Chandrupatla sign test p < 1e-4 got True want True
OK CHA vs Chandrupatla sign test p (rounded) got 0.006 want 0.006
The constant-free variant (earned projection):
OK credit APS place got 29 want 29
OK credit APS total got 3568 want 3568
OK credit CHA place got 10 want 10
OK credit CHA total got 3020 want 3020
OK credit both sets got 6588 want 6588
OK credit costs 762 more on the canonical set got 762 want 762
OK credit APS vs chandrupatla got (79, 3, 71) want (79, 3, 71)
OK credit APS vs toms748 got (77, 9, 67) want (77, 9, 67)
OK credit APS vs brent got (78, 5, 70) want (78, 5, 70)
OK credit APS vs Chandrupatla sign test p (rounded) got 0.57 want 0.57
The guards that replace the step instead of projecting it:
OK APS unguarded got 2806 want 2806
OK CHA unguarded total got 4104 want 4104
OK CHA unguarded solved got 35 want 35
OK CHA unguarded bound violations got 10 want 10
OK APS bracket-guard got 7260 want 7260
OK APS both-guard got 7239 want 7239
Alefeld-Potra-Shi:
OK APS winner got 'chandrupatla' want 'chandrupatla'
OK APS chandrupatla place got 1 want 1
OK APS chandrupatla total got 2787 want 2787
OK APS toms748 place got 4 want 4
OK APS toms748 total got 2820 want 2820
OK APS brent place got 5 want 5
OK APS brent total got 2889 want 2889
OK APS dekker place got 12 want 12
OK APS dekker total got 3158 want 3158
OK APS itp_oracle place got 13 want 13
OK APS itp_oracle total got 3260 want 3260
OK APS ridders place got 20 want 20
OK APS ridders total got 3313 want 3313
OK APS itp place got 34 want 34
OK APS itp total got 3957 want 3957
OK APS illinois place got 73 want 73
OK APS illinois total got 7045 want 7045
OK APS bisection place got 79 want 79
OK APS bisection total got 7530 want 7530
OK APS regula_falsi place got 244 want 244
OK APS regula_falsi total got 10560 want 10560
OK APS regula falsi solved got 127 want 127
Chandrupatla:
OK CHA rank 1 label got 'pure.sqrt.bracket.project' want 'pure.sqrt.bracket.project'
OK CHA rank 1 solves one more than anyone else got 41 want 41
OK CHA chandrupatla place got 2 want 2
OK CHA chandrupatla total got 2441 want 2441
OK CHA itp_oracle place got 3 want 3
OK CHA itp_oracle total got 2611 want 2611
OK CHA itp place got 6 want 6
OK CHA itp total got 2857 want 2857
OK CHA bisection place got 16 want 16
OK CHA bisection total got 3090 want 3090
OK CHA toms748 place got 164 want 164
OK CHA toms748 total got 4000 want 4000
OK CHA brent place got 226 want 226
OK CHA brent total got 4115 want 4115
OK CHA brent solved got 38 want 38
OK chandrupatla, both sets got 5228 want 5228
OK Chandrupatla beats us on both sets got True want True
Negative results, the regime claims:
OK CHA cbrt over the whole set got 4099 want 4099
OK single fixed choice, both sets got 6026 want 6026
OK switch by benchmark set, both sets got 6905 want 6905
OK switching is worse than not switching got True want True
OK fun4.1 projection variant got 89 want 89
OK fun4.1 cbrt got 32 want 32
OK fun4.2 projection variant got 93 want 93
OK fun4.2 cbrt got 32 want 32
OK fun4.3 projection variant got 113 want 113
OK fun4.3 cbrt got 40 want 40
OK fun4.4 projection variant got 125 want 125
OK fun4.4 cbrt got 47 want 47
OK fun4.5 projection variant got 153 want 153
OK fun4.5 cbrt got 59 want 59
The law across the grid:
OK mean total, scale=derived3, lin got 6809 want 6809
OK mean total, scale=derived3, quad got 22294 want 22294
OK mean total, scale=derived, lin got 6906 want 6906
OK mean total, scale=derived, quad got 22167 want 22167
OK mean total, scale=bracket, lin got 10263 want 10263
OK mean total, scale=bracket, quad got 19137 want 19137
OK mean total, scale=start, lin got 16102 want 16102
OK mean total, scale=start, quad got 15345 want 15345
OK every c=1 similarity beats every c=0 similarity got True want True
OK similarity ranking got ['ratio', 'exp_abs', 'rational', 'ratio_soft', 'sqrt', 'cbrt', 'exp_sq', 'rat_sq', 'cosine'] want ['ratio', 'exp_abs', 'rational', 'ratio_soft', 'sqrt', 'cbrt', 'exp_sq', 'rat_sq', 'cosine']
OK similarity ratio best got 6496 want 6496
OK similarity exp_abs best got 6822 want 6822
OK similarity rational best got 7038 want 7038
OK similarity ratio_soft best got 9502 want 9502
OK similarity sqrt best got 10497 want 10497
OK similarity cbrt best got 11941 want 11941
OK similarity exp_sq best got 14314 want 14314
OK similarity rat_sq best got 15102 want 15102
OK similarity cosine best got 16394 want 16394
OK scale ranking got ['derived3', 'derived', 'bracket', 'start'] want ['derived3', 'derived', 'bracket', 'start']
OK scale derived3 best got 6496 want 6496
OK scale derived best got 6628 want 6628
OK scale bracket best got 9081 want 9081
OK scale start best got 14540 want 14540
The placement modes (each in its own sweep file):
OK slope.ratio.derived3.project APS got 2848 want 2848
OK slope.ratio.derived3.project CHA got 3074 want 3074
OK slope.ratio.derived3.project solved got 193 want 193
OK slope.ratio.derived3.project guarantee (over 2x) got 0 want 0
OK pure+shape+slope.ratio.derived3.safe APS got 2527 want 2527
OK pure+shape+slope.ratio.derived3.safe CHA got 3108 want 3108
OK pure+shape+slope.ratio.derived3.safe solved got 193 want 193
OK pure+shape+slope.ratio.derived3.safe guarantee (over 2x) got 0 want 0
OK pure+dissim+slope.ratio.derived3.project APS got 2726 want 2726
OK pure+dissim+slope.ratio.derived3.project CHA got 2785 want 2785
OK pure+dissim+slope.ratio.derived3.project solved got 193 want 193
OK pure+dissim+slope.ratio.derived3.project guarantee (over 2x) got 0 want 0
OK pure+shape+dissim+slope.ratio.derived3.safe APS got 2884 want 2884
OK pure+shape+dissim+slope.ratio.derived3.safe CHA got 2859 want 2859
OK pure+shape+dissim+slope.ratio.derived3.safe solved got 193 want 193
OK pure+shape+dissim+slope.ratio.derived3.safe guarantee (over 2x) got 0 want 0
OK gate.ratio.derived.project APS got 4449 want 4449
OK gate.ratio.derived.project CHA got 2991 want 2991
OK gate.ratio.derived.project solved got 193 want 193
OK gate.ratio.derived.project guarantee (over 2x) got 0 want 0
OK log.ratio_soft.derived.safe APS got 6940 want 6940
OK log.ratio_soft.derived.safe CHA got 3474 want 3474
OK log.ratio_soft.derived.safe solved got 193 want 193
OK log.ratio_soft.derived.safe guarantee (over 2x) got 0 want 0
OK pure+dissim+slope.ratio.derived3.safe CHA per-set got 2683 want 2683
OK gate.ratio_soft.derived.safe CHA per-set got 2851 want 2851
The per-place reading (sweep_order.json):
OK far+rational both APS m=1 got 4522 want 4522
OK far+rational both APS order got 3675 want 3675
OK far+rational both CHA m=1 got 3387 want 3387
OK far+rational both CHA order got 3216 want 3216
OK far+rational bracket APS m=1 got 4606 want 4606
OK far+rational bracket APS order got 3542 want 3542
OK far+rational bracket CHA m=1 got 3571 want 3571
OK far+rational bracket CHA order got 3303 want 3303
OK far+rational credit APS m=1 got 5190 want 5190
OK far+rational credit APS order got 4686 want 4686
OK far+rational credit CHA m=1 got 3013 want 3013
OK far+rational credit CHA order got 2884 want 2884
OK far+rational project APS m=1 got 6932 want 6932
OK far+rational project APS order got 6364 want 6364
OK far+rational project CHA m=1 got 4794 want 4794
OK far+rational project CHA order got 4559 want 4559
OK far+rational safe APS m=1 got 5987 want 5987
OK far+rational safe APS order got 5229 want 5229
OK far+rational safe CHA m=1 got 4108 want 4108
OK far+rational safe CHA order got 3923 want 3923
OK far+rational step APS m=1 got 6435 want 6435
OK far+rational step APS order got 5551 want 5551
OK far+rational step CHA m=1 got 4257 want 4257
OK far+rational step CHA order got 4022 want 4022
OK far+rational none APS m=1 got 10676 want 10676
OK far+rational none APS order got 10043 want 10043
OK far+rational none CHA m=1 got 8265 want 8265
OK far+rational none CHA order got 7794 want 7794
OK cheaper in 7 of 7 safeguards got 7 want 7
OK false position none solved got 127 want 127
OK false position none APS got 10676 want 10676
OK false position order solved got 128 want 128
OK false position order APS got 10043 want 10043
OK slope reading none APS got 2806 want 2806
OK slope reading order APS got 2854 want 2854
OK guarantee family violations got 0 want 0
The decision place (sweep_decision.json):
OK ratio.band.project APS got 5432 want 5432
OK ratio.band.project CHA got 3066 want 3066
OK ratio.band.credit CHA got 2899 want 2899
OK ratio.none.project APS got 2806 want 2806
OK ratio.defect.project APS got 2523 want 2523
OK ratio.defect.project CHA got 3210 want 3210
OK ratio_soft.simpair.project CHA got 2868 want 2868
OK defect APS solved got 153 want 153
OK defect both sets got 5733 want 5733
OK defect is the new canonical best (< 2527) got True want True
OK decision grid guarantee violations got 0 want 0
OK band refusal rate % got 63.6 want 63.6
OK defect refusal rate % got 7.0 want 7.0
The gap decomposition (fresh runs of the diagnosis scripts):
OK problems where we lose got 33 want 33
OK fun1.4 our bracket halvings/eval got 0.62 want 0.62
OK fun1.4 IQI from our points got 10.37 want 10.37
OK flat: ours min got 0.41 want 0.41
OK flat: ours max got 0.81 want 0.81
OK flat: IQI min got 0.01 want 0.01
OK flat: IQI max got 0.17 want 0.17
OK non-degenerate states with one negative weight got np.int64(428) want 428
OK bracketing states in total got 433 want 433
OK every exception is an underflowed zero weight got True want True
OK total gap on the losing problems got 244 want 244
OK flat families' gap got 160 want 160
OK fun1 gap got 58 want 58
OK Chandrupatla bisect share fun3 % got 92 want 92
OK Chandrupatla bisect share fun4 % got 96 want 96
OK Chandrupatla bisect share fun5 % got 100 want 100
OK Chandrupatla bisect share fun6 % got 100 want 100
OK Chandrupatla total fun4 got 297 want 297
OK Chandrupatla total fun5 got 233 want 233
OK Chandrupatla total fun6 got 233 want 233
OK m>=2 on canonical states % got np.float64(0.2) want 0.2
OK m>=2 on flat states % got 5.8 want 5.8
OK band fun4 no-gate got 573 want 573
OK band fun4 with band got 296 want 296
OK band fun5 no-gate got 456 want 456
OK band fun5 with band got 239 want 239
OK band fun6 no-gate got 456 want 456
OK band fun6 with band got 235 want 235
OK band cost on fun1 got 164 want 164
OK band cost on fun2 got 111 want 111
OK band cost on fun8 got 170 want 170
OK band cost on fun9 got 144 want 144
OK log item: x-space control APS got 5822 want 5822
OK log item: x-space control CHA got 3643 want 3643
§3.1: the multiplier and the endowment (fresh runs):
OK multiplier 1.0 APS got 4817 want 4817
OK multiplier 1.0 CHA got 2997 want 2997
OK multiplier 1.25 APS got 2806 want 2806
OK multiplier 1.25 CHA got 2752 want 2752
OK multiplier 1.5 APS got 2806 want 2806
OK multiplier 1.5 CHA got 2889 want 2889
OK multiplier 2.0 APS got 2806 want 2806
OK multiplier 2.0 CHA got 3220 want 3220
OK multiplier 3.0 APS got 2806 want 2806
OK multiplier 3.0 CHA got 3756 want 3756
OK multiplier 4.0 APS got 2806 want 2806
OK multiplier 4.0 CHA got 4006 want 4006
OK endowment 0.1 APS got 5344 want 5344
OK endowment 0.1 CHA got 3051 want 3051
OK endowment 0.5 APS got 4656 want 4656
OK endowment 0.5 CHA got 3002 want 3002
OK endowment 1.0 APS got 3568 want 3568
OK endowment 1.0 CHA got 3020 want 3020
OK endowment 2.0 APS got 3692 want 3692
OK endowment 2.0 CHA got 2712 want 2712
OK endowment 5.0 APS got 3006 want 3006
OK endowment 5.0 CHA got 2615 want 2615
OK C0=1 cheapest within the one-step promise got 1.0 want 1.0
OK 2527 at multiplier 1.25 got 2541 want 2541
OK 2527 flat from 1.5 to 4 got {2527} want {2527}
OK 2523 flat from 1.25 to 4 got {2523} want {2523}
Tables 2-3 regenerated:
OK APS pure.ratio.derived3.project place got 3 want 3
OK APS pure.ratio.derived3.none place got 2 want 2
OK CHA pure.ratio.derived3.project place got 47 want 47
OK CHA pure.ratio.derived3.none place got 228 want 228
OK CHA pure.exp_abs.bracket.both place got 4 want 4
OK CHA exp_abs.bracket.both total got 2733 want 2733
OK CHA TOMS 748 over the 2x bound got 15 want 15
OK CHA Brent over the 2x bound got 18 want 18
OK credit APS vs itp_oracle got (86, 6, 61) want (86, 6, 61)
OK credit CHA vs itp_oracle got (2, 3, 35) want (2, 3, 35)
OK credit CHA vs bisection got (5, 0, 35) want (5, 0, 35)
OK project APS vs chandrupatla got (87, 24, 42) want (87, 24, 42)
OK project APS vs itp got (132, 3, 18) want (132, 3, 18)
OK project APS vs itp_oracle got (121, 4, 28) want (121, 4, 28)
OK project APS vs bisection got (152, 1, 0) want (152, 1, 0)
OK project CHA vs toms748 got (31, 1, 8) want (31, 1, 8)
OK project CHA vs brent got (30, 4, 4) want (30, 4, 4)
OK project CHA vs itp got (17, 0, 23) want (17, 0, 23)
OK project CHA vs itp_oracle got (16, 0, 24) want (16, 0, 24)
OK both sets pure.ratio.derived3.credit got 6588 want 6588
OK both sets pure.ratio.derived3.project got 6026 want 6026
OK both sets chandrupatla got 5228 want 5228
OK both sets itp_oracle got 5871 want 5871
OK both sets toms748 got 6820 want 6820
OK both sets brent got 7004 want 7004
§7 under the earned projection:
OK slope.ratio.derived3.credit APS got 3507 want 3507
OK slope.ratio.derived3.credit CHA got 2997 want 2997
OK pure+shape+slope.ratio.derived3.credit APS got 3755 want 3755
OK pure+shape+slope.ratio.derived3.credit CHA got 3021 want 3021
OK pure+dissim+slope.ratio.derived3.credit APS got 3857 want 3857
OK pure+dissim+slope.ratio.derived3.credit CHA got 3002 want 3002
OK pure+shape+dissim+slope.ratio.derived3.credit APS got 4354 want 4354
OK pure+shape+dissim+slope.ratio.derived3.credit CHA got 3037 want 3037
OK defect.ratio.derived3.credit APS got 3545 want 3545
OK defect.ratio.derived3.credit CHA got 2949 want 2949
OK best constant-free configuration got 'defect.ratio.derived3.credit' want 'defect.ratio.derived3.credit'
OK its total over both sets got 6494 want 6494
every number stated in the paper matches the sweep data.
+38 -1
View File
@@ -348,7 +348,13 @@ def chandrupatla(f, a, b, xtol, maxfev):
xm, fm = (x1, f1) if abs(f1) < abs(f2) else (x2, f2) xm, fm = (x1, f1) if abs(f1) < abs(f2) else (x2, f2)
if fm == 0.0: if fm == 0.0:
return xm, g.n, True return xm, g.n, True
tol = 2.0 * 2.22e-16 * abs(xm) + xtol # tl > 1/2 stops once span < 2*tol, so the absolute term must be xtol/2 for
# this method to stop on the same bracket width as every other one (<= xtol).
# Until 2026-09-21 it was xtol, a bracket twice as wide - the factor the
# protocol corrects for ITP and had not corrected here. With xtol/2 this
# code and SciPy's elementwise.find_root agree on all 199 problems exactly
# (audit_chandrupatla_library.py).
tol = 2.0 * 2.22e-16 * abs(xm) + 0.5 * xtol
span = abs(x2 - x1) span = abs(x2 - x1)
if span == 0.0: if span == 0.0:
return xm, g.n, True return xm, g.n, True
@@ -455,6 +461,37 @@ def chandrupatla_scipy(f, a, b, xtol, maxfev):
return 0.5 * (a + b), g.n, False return 0.5 * (a + b), g.n, False
def chandrupatla_lib(f, a, b, xtol, maxfev):
"""Chandrupatla from the library's PUBLIC interface, the benchmark's baseline since
2026-09-21 (Valeri: remove the risk of our own implementation). Same stopping rule
as every other method - bracket narrower than xtol, relative term at machine
precision, no stopping on |f| - and evaluations counted by the same Counter."""
import numpy as np
from scipy.optimize import elementwise
g = Counter(f, maxfev)
def vec(x):
return np.asarray([g(float(xi)) for xi in np.atleast_1d(x)]).reshape(np.shape(x))
try:
res = elementwise.find_root(vec, (a, b), maxiter=maxfev,
tolerances=dict(xatol=xtol, xrtol=8.9e-16,
fatol=0.0, frtol=0.0))
return (float(np.asarray(res.x).ravel()[0]), g.n,
bool(np.all(np.asarray(res.success))))
except _Budget:
return 0.5 * (a + b), g.n, False
except Exception:
return 0.5 * (a + b), g.n, False
# The competitors as benchmarked: the library alone wherever the library ships the
# method. Until 2026-09-21 they were best_of(ours, library) problem by problem, which
# let a tolerance slip in OUR Chandrupatla leak into the reported numbers.
CHANDRUPATLA_BASELINE = chandrupatla_lib
BRENT_BASELINE = brent_scipy
def best_of(*methods): def best_of(*methods):
"""Give the competitor the cheaper of its implementations on every """Give the competitor the cheaper of its implementations on every
problem. The same concession the paper already makes to ITP by reporting problem. The same concession the paper already makes to ITP by reporting
+3 -2
View File
@@ -32,6 +32,7 @@ import time
from scipy.optimize._tstutils import (_APS_TESTS_DICTS, from scipy.optimize._tstutils import (_APS_TESTS_DICTS,
_CHANDRUPATLA_TESTS_DICTS) _CHANDRUPATLA_TESTS_DICTS)
from simroot.baselines import BRENT_BASELINE, CHANDRUPATLA_BASELINE
from simroot.baselines import (anderson_bjorck, best_of, bisection, brent, from simroot.baselines import (anderson_bjorck, best_of, bisection, brent,
brent_scipy, chandrupatla, chandrupatla_scipy, brent_scipy, chandrupatla, chandrupatla_scipy,
dekker, itp, ridders, toms748) dekker, itp, ridders, toms748)
@@ -54,9 +55,9 @@ def ours(f, a, b, xtol, maxfev, budget_factor=2.0):
# theorem instead; the false-position family and bisection are not in SciPy. # theorem instead; the false-position family and bisection are not in SciPy.
METHODS = [ METHODS = [
("OURS (similarity)", ours), ("OURS (similarity)", ours),
("chandrupatla", best_of(chandrupatla, chandrupatla_scipy)), ("chandrupatla", CHANDRUPATLA_BASELINE),
("toms748 (scipy)", toms748), ("toms748 (scipy)", toms748),
("brent", best_of(brent, brent_scipy)), ("brent", BRENT_BASELINE),
("itp", itp), ("itp", itp),
("ridders", ridders), ("ridders", ridders),
("anderson_bjorck", anderson_bjorck), ("anderson_bjorck", anderson_bjorck),
+3 -2
View File
@@ -30,6 +30,7 @@ import time
from scipy.optimize._tstutils import (_APS_TESTS_DICTS, from scipy.optimize._tstutils import (_APS_TESTS_DICTS,
_CHANDRUPATLA_TESTS_DICTS) _CHANDRUPATLA_TESTS_DICTS)
from simroot.baselines import BRENT_BASELINE, CHANDRUPATLA_BASELINE
from simroot.baselines import (best_of, bisection, brent, brent_scipy, from simroot.baselines import (best_of, bisection, brent, brent_scipy,
chandrupatla, chandrupatla_scipy, itp, ridders, chandrupatla, chandrupatla_scipy, itp, ridders,
toms748) toms748)
@@ -48,9 +49,9 @@ def ours(f, a, b, xtol, maxfev):
OURS = "OURS" OURS = "OURS"
FIELD = [ FIELD = [
(OURS, ours), (OURS, ours),
("chandrupatla", best_of(chandrupatla, chandrupatla_scipy)), ("chandrupatla", CHANDRUPATLA_BASELINE),
("toms748", toms748), ("toms748", toms748),
("brent", best_of(brent, brent_scipy)), ("brent", BRENT_BASELINE),
("itp", itp), ("itp", itp),
("ridders", ridders), ("ridders", ridders),
("bisection", bisection), ("bisection", bisection),
+1 -1
View File
@@ -158,7 +158,7 @@ def chandrupatla_blend(f, a, b, xtol, maxfev, mode="blend"):
xm, fm = (x1, f1) if abs(f1) < abs(f2) else (x2, f2) xm, fm = (x1, f1) if abs(f1) < abs(f2) else (x2, f2)
if fm == 0.0: if fm == 0.0:
return xm, g.n, True return xm, g.n, True
tol = 2.0 * 2.22e-16 * abs(xm) + xtol tol = 2.0 * 2.22e-16 * abs(xm) + 0.5 * xtol # 2026-09-21 fix, see baselines.chandrupatla
span = abs(x2 - x1) span = abs(x2 - x1)
if span == 0.0: if span == 0.0:
return xm, g.n, True return xm, g.n, True
+1 -1
View File
@@ -77,7 +77,7 @@ def chandrupatla_guard(f, a, b, xtol, maxfev, guard="own"):
xm, fm = (x1, f1) if abs(f1) < abs(f2) else (x2, f2) xm, fm = (x1, f1) if abs(f1) < abs(f2) else (x2, f2)
if fm == 0.0: if fm == 0.0:
return xm, g.n, True return xm, g.n, True
tol = 2.0 * 2.22e-16 * abs(xm) + xtol tol = 2.0 * 2.22e-16 * abs(xm) + 0.5 * xtol # 2026-09-21 fix, see baselines.chandrupatla
span = abs(x2 - x1) span = abs(x2 - x1)
if span == 0.0: if span == 0.0:
return xm, g.n, True return xm, g.n, True
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -2
View File
@@ -41,6 +41,7 @@ from simroot.baselines import (anderson_bjorck, best_of, bisection, brent,
brent_scipy, chandrupatla, chandrupatla_scipy, brent_scipy, chandrupatla, chandrupatla_scipy,
dekker, illinois, itp, pegasus, regula_falsi, dekker, illinois, itp, pegasus, regula_falsi,
ridders, toms748) ridders, toms748)
from simroot.baselines import BRENT_BASELINE, CHANDRUPATLA_BASELINE
from simroot.pure import PURE_SIMS, pure_root from simroot.pure import PURE_SIMS, pure_root
XTOL = 1e-12 XTOL = 1e-12
@@ -93,8 +94,8 @@ BASELINES = {
"illinois": illinois, "pegasus": pegasus, "illinois": illinois, "pegasus": pegasus,
"anderson_bjorck": anderson_bjorck, "ridders": ridders, "anderson_bjorck": anderson_bjorck, "ridders": ridders,
"dekker": dekker, "dekker": dekker,
"brent": best_of(brent, brent_scipy), "brent": BRENT_BASELINE, # the library alone (2026-09-21)
"chandrupatla": best_of(chandrupatla, chandrupatla_scipy), "chandrupatla": CHANDRUPATLA_BASELINE, # scipy elementwise.find_root
"toms748": toms748, "toms748": toms748,
"itp": itp, "itp_oracle": itp_oracle, "itp": itp, "itp_oracle": itp_oracle,
} }
+10 -9
View File
@@ -2,7 +2,9 @@
The published tables predate Chandrupatla's own method as a baseline (it is first on The published tables predate Chandrupatla's own method as a baseline (it is first on
Alefeld-Potra-Shi) and name the projection variant as proposed, while the code and Alefeld-Potra-Shi) and name the projection variant as proposed, while the code and
verify_article.py have since fixed the credit safeguard as the proposed method. This the proposed method is the scheduled projection with its multiplier declared (Valeri,
2026-09-21, variant a), the earned projection the constant-free variant. Baselines are
the library alone (Chandrupatla = scipy elementwise.find_root, Brent = brentq). This
prints the rows and head-to-head counts from the data, with the SAME ranking function the prints the rows and head-to-head counts from the data, with the SAME ranking function the
verifier uses, so nothing in the new tables is transcribed by hand. verifier uses, so nothing in the new tables is transcribed by hand.
@@ -10,12 +12,11 @@ verifier uses, so nothing in the new tables is transcribed by hand.
""" """
from __future__ import annotations from __future__ import annotations
from report_sweep import PROPOSED, TUNED, head_to_head, load, place from report_sweep import CREDIT, PROPOSED, TUNED, head_to_head, load, place
PROJ = "pure.ratio.derived3.project"
NONE = "pure.ratio.derived3.none" NONE = "pure.ratio.derived3.none"
NAMES = {PROPOSED: "**proposed: tent, 3-point, credit**", NAMES = {PROPOSED: "**proposed: tent, 3-point, projection ($c=2$)**",
PROJ: "projection variant (tent, 3-point, projection)", CREDIT: "constant-free variant: earned projection",
NONE: "the same rule with no safeguard at all", NONE: "the same rule with no safeguard at all",
"chandrupatla": "Chandrupatla", "toms748": "TOMS 748", "brent": "Brent", "chandrupatla": "Chandrupatla", "toms748": "TOMS 748", "brent": "Brent",
"dekker": "Dekker", "itp_oracle": "ITP, per-problem oracle", "dekker": "Dekker", "itp_oracle": "ITP, per-problem oracle",
@@ -24,10 +25,10 @@ NAMES = {PROPOSED: "**proposed: tent, 3-point, credit**",
"regula_falsi": "false position", "regula_falsi": "false position",
"pure.sqrt.bracket.project": "similarity, $1-\\sqrt{|z|}$, bracket scale, projection", "pure.sqrt.bracket.project": "similarity, $1-\\sqrt{|z|}$, bracket scale, projection",
"pure.exp_abs.bracket.both": "similarity, $e^{-|z|}$, bracket scale, both guards"} "pure.exp_abs.bracket.both": "similarity, $e^{-|z|}$, bracket scale, both guards"}
APS_ROWS = ("chandrupatla", PROJ, NONE, "toms748", "brent", "dekker", "itp_oracle", APS_ROWS = ("chandrupatla", CREDIT, NONE, "toms748", "brent", "dekker", "itp_oracle",
"ridders", PROPOSED, "itp", "illinois", "bisection", "regula_falsi") "ridders", PROPOSED, "itp", "illinois", "bisection", "regula_falsi")
CHA_ROWS = ("pure.sqrt.bracket.project", "chandrupatla", "itp_oracle", CHA_ROWS = ("pure.sqrt.bracket.project", "chandrupatla", "itp_oracle",
"pure.exp_abs.bracket.both", "itp", PROPOSED, "bisection", PROJ, NONE, "pure.exp_abs.bracket.both", "itp", PROPOSED, "bisection", CREDIT, NONE,
"toms748", "brent") "toms748", "brent")
OPPONENTS = ("chandrupatla", "toms748", "brent", "itp", "itp_oracle", "bisection") OPPONENTS = ("chandrupatla", "toms748", "brent", "itp", "itp_oracle", "bisection")
@@ -47,7 +48,7 @@ def main():
over = "—" if k in ("regula_falsi",) else str(d["over"]) over = "—" if k in ("regula_falsi",) else str(d["over"])
print(f"| {place(rows, setname, k)} | {NAMES[k]} | {tuned} | " print(f"| {place(rows, setname, k)} | {NAMES[k]} | {tuned} | "
f"{d['solved']}/{n} | {d['total']} | {over} |") f"{d['solved']}/{n} | {d['total']} | {over} |")
for lbl in (PROPOSED, PROJ): for lbl in (PROPOSED, CREDIT):
print(f"\nhead to head, {lbl} (wins/ties/losses on problems both solve)") print(f"\nhead to head, {lbl} (wins/ties/losses on problems both solve)")
for setname in ("APS", "CHA"): for setname in ("APS", "CHA"):
cells = [] cells = []
@@ -55,7 +56,7 @@ def main():
w, t, l, _ = head_to_head(rows, lbl, opp, setname) w, t, l, _ = head_to_head(rows, lbl, opp, setname)
cells.append(f"{opp} {w}/{t}/{l}") cells.append(f"{opp} {w}/{t}/{l}")
print(f" {setname}: " + ", ".join(cells)) print(f" {setname}: " + ", ".join(cells))
for lbl in (PROPOSED, PROJ, "chandrupatla", "toms748", "brent", "itp_oracle"): for lbl in (PROPOSED, CREDIT, "chandrupatla", "toms748", "brent", "itp_oracle"):
d = by[lbl] d = by[lbl]
print(f" both sets {lbl:34s} {d['APS']['total'] + d['CHA']['total']}") print(f" both sets {lbl:34s} {d['APS']['total'] + d['CHA']['total']}")
+41 -26
View File
@@ -16,8 +16,8 @@ import pytest
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# the other end of the trade the paper states: better average, one constant # the constant-free variant (the proposed rule is the projection, variant a)
PROJ_LABEL = "pure.ratio.derived3.project" CREDIT_LABEL = "pure.ratio.derived3.credit"
@pytest.mark.skipif(not os.path.exists(os.path.join(ROOT, "sweep_full.json")), @pytest.mark.skipif(not os.path.exists(os.path.join(ROOT, "sweep_full.json")),
@@ -40,16 +40,21 @@ def test_the_headline_claim_is_what_the_paper_says_it_is():
It caught exactly that. Until Chandrupatla's own method was added - a It caught exactly that. Until Chandrupatla's own method was added - a
reader asked why the owner of half the benchmark was missing - this test reader asked why the owner of half the benchmark was missing - this test
asserted that a configuration of ours came first, and it passed only asserted that a configuration of ours came first, and it passed only
because the method that beats us was absent from the field. The claim it because the method that beats us was absent from the field. The claims it
now guards is the corrected one: we are not first, Chandrupatla is, and now guards (variant a, Valeri 2026-09-21): by TOTAL Chandrupatla is first
our margin behind it on the canonical set is under one percent.""" on the canonical set and we are within one percent of it; problem by
problem we beat it with significance; on the wide-bracket set and over
both sets it beats us. And the constant-free variant may not borrow the
projection's canonical numbers."""
import json import json
import math
from report_sweep import PROPOSED, rank from report_sweep import PROPOSED, head_to_head, rank
with open(os.path.join(ROOT, "sweep_full.json"), encoding="utf8") as fh: with open(os.path.join(ROOT, "sweep_full.json"), encoding="utf8") as fh:
rows = json.load(fh) rows = json.load(fh)
by = {r["label"]: r for r in rows} by = {r["label"]: r for r in rows}
assert "chandrupatla" in by, "the sweep predates the missing baselines" assert "chandrupatla" in by, "the sweep predates the missing baselines"
assert PROPOSED == "pure.ratio.derived3.project"
aps = rank(rows, "APS") aps = rank(rows, "APS")
assert aps[0]["label"] == "chandrupatla", aps[0]["label"] assert aps[0]["label"] == "chandrupatla", aps[0]["label"]
@@ -57,33 +62,43 @@ def test_the_headline_claim_is_what_the_paper_says_it_is():
def both(lbl): def both(lbl):
return by[lbl]["APS"]["total"] + by[lbl]["CHA"]["total"] return by[lbl]["APS"]["total"] + by[lbl]["CHA"]["total"]
# What the paper may claim for the credit method, and nothing more. def tot(lbl, s):
# It is NOT within a percent of the best on Alefeld-Potra-Shi - that was return by[lbl][s]["total"]
# true of the projection variant, which carries a constant. Buying
# constant-freedom and a near-bisection worst case costs average # canonical set: parity by total with the best, a tie with TOMS 748
# performance, and the test pins the direction of that trade so the assert tot(PROPOSED, "APS") > tot("chandrupatla", "APS")
# abstract cannot quietly reclaim the projection variant's numbers. assert tot(PROPOSED, "APS") < 1.01 * tot("chandrupatla", "APS")
assert tot(PROPOSED, "APS") < tot("toms748", "APS") < 1.01 * tot(PROPOSED, "APS")
# ... and an edge problem by problem, with significance
w, _t, l, _ = head_to_head(rows, PROPOSED, "chandrupatla", "APS")
p = min(1.0, 2.0 * sum(math.comb(w + l, i) for i in range(min(w, l) + 1))
/ 2.0 ** (w + l))
assert w > 2 * l - 1 and p < 1e-3, (w, l, p)
# wide brackets and both sets: Chandrupatla is stronger, and the paper says so
assert tot(PROPOSED, "CHA") > tot("chandrupatla", "CHA")
assert both(PROPOSED) > both("chandrupatla"), "behind Chandrupatla" assert both(PROPOSED) > both("chandrupatla"), "behind Chandrupatla"
for weaker in ("toms748", "brent", "itp", "ridders", "bisection"): for weaker in ("toms748", "brent", "itp", "ridders", "bisection"):
assert both(PROPOSED) < both(weaker), weaker assert both(PROPOSED) < both(weaker), weaker
# On the canonical set alone it is behind the classical methods; saying # the constant-free variant may not borrow the projection's canonical numbers
# otherwise would be quoting the projection variant. assert tot(CREDIT_LABEL, "APS") > tot("toms748", "APS")
assert by[PROPOSED]["APS"]["total"] > by["toms748"]["APS"]["total"] assert tot(CREDIT_LABEL, "APS") > tot("brent", "APS")
assert by[PROPOSED]["APS"]["total"] > by["brent"]["APS"]["total"] assert tot(CREDIT_LABEL, "CHA") < tot(PROPOSED, "CHA")
# The claim it does own: the worst single problem, over the problems it # the claim the constant-free variant does own: the worst single problem,
# solves, is close to bisection's and far below the classical methods'. # over the problems it solves, is close to bisection's and far below the
# classical methods'
def worst(lbl): def worst(lbl):
w = 0 wst = 0
for setname in ("APS", "CHA"): for setname in ("APS", "CHA"):
for n, ok in by[lbl][setname]["per"].values(): for n, ok in by[lbl][setname]["per"].values():
if ok: if ok:
w = max(w, n) wst = max(wst, n)
return w return wst
assert worst(PROPOSED) < 100, worst(PROPOSED) assert worst(CREDIT_LABEL) < 100, worst(CREDIT_LABEL)
assert worst(PROPOSED) < worst("brent") assert worst(CREDIT_LABEL) < worst("brent")
assert worst(PROPOSED) < worst("toms748") assert worst(CREDIT_LABEL) < worst("toms748")
assert worst(PROPOSED) < worst(PROJ_LABEL), (worst(PROPOSED), assert worst(CREDIT_LABEL) < worst(PROPOSED), (worst(CREDIT_LABEL), worst(PROPOSED))
worst(PROJ_LABEL))
+58
View File
@@ -0,0 +1,58 @@
"""Pins for the 2026-09-21 switch of the competitors to the library.
REGRESSION: our Chandrupatla stopped on a bracket twice as wide as every other method
(tl = tol/span > 1/2 with tol = xtol), and best_of let that looser run into the
reported numbers. The benchmark now uses the library alone, and our implementation -
kept for the timing section and the traced mirror - must agree with it exactly.
"""
from __future__ import annotations
import math
import pytest
from scipy.optimize._tstutils import _APS_TESTS_DICTS, _CHANDRUPATLA_TESTS_DICTS
from simroot.baselines import (BRENT_BASELINE, CHANDRUPATLA_BASELINE, brent_scipy,
chandrupatla, chandrupatla_lib)
XTOL, MAXFEV = 1e-12, 200
def _problems():
out = []
for d in _APS_TESTS_DICTS[::7]:
f = (lambda ff, aa: lambda t: ff(t, *aa))(d["f"], d.get("args", ()))
out.append((d["ID"], f, float(d["bracket"][0]), float(d["bracket"][1])))
for d in _CHANDRUPATLA_TESTS_DICTS[::3]:
out.append((d["ID"], d["f"], float(d["bracket"][0]), float(d["bracket"][1])))
return out
def test_the_benchmark_competitors_are_the_library():
assert CHANDRUPATLA_BASELINE is chandrupatla_lib
assert BRENT_BASELINE is brent_scipy
def test_the_sweep_uses_them():
import sweep_full
assert sweep_full.BASELINES["chandrupatla"] is chandrupatla_lib
assert sweep_full.BASELINES["brent"] is brent_scipy
@pytest.mark.parametrize("pid,f,a,b", _problems())
def test_our_chandrupatla_spends_exactly_what_the_library_spends(pid, f, a, b):
"""The tolerance fix, pinned: same evaluation count and same verdict."""
x1, n1, c1 = chandrupatla(f, a, b, XTOL, MAXFEV)
x2, n2, c2 = chandrupatla_lib(f, a, b, XTOL, MAXFEV)
assert (n1, bool(c1)) == (n2, bool(c2)), (pid, n1, n2)
if c1 and c2 and math.isfinite(x1) and math.isfinite(x2):
assert abs(x1 - x2) <= 2 * XTOL + 1e-15 * max(1.0, abs(x1))
def test_our_chandrupatla_no_longer_stops_on_a_double_width_bracket():
"""On a smooth problem the looser rule saved an evaluation; with the fix our count
must equal the library's rather than undercut it."""
f = lambda t: math.exp(t) - 2.0 # noqa: E731
_x, n_ours, _c = chandrupatla(f, 0.0, 3.0, XTOL, MAXFEV)
_x, n_lib, _c = chandrupatla_lib(f, 0.0, 3.0, XTOL, MAXFEV)
assert n_ours == n_lib
+2 -1
View File
@@ -147,7 +147,8 @@ def test_one_bit_is_the_cheapest_endowment_that_promises_one_step():
def test_the_scheduled_multiplier_has_no_such_determination(): def test_the_scheduled_multiplier_has_no_such_determination():
"""The contrast that decides which safeguard the paper should propose. """The contrast behind the paper's declaring the multiplier a free constant
(and offering the earned projection as the constant-free variant).
The multiplicative family has no smallest non-trivial member: 1 is The multiplicative family has no smallest non-trivial member: 1 is
degenerate and everything above it is admissible with a different cost, so degenerate and everything above it is admissible with a different cost, so
the multiplier is genuinely free.""" the multiplier is genuinely free."""
+11 -3
View File
@@ -52,8 +52,16 @@ def test_the_guarded_worst_case_is_at_bisection_level(name, rule):
def test_the_similarity_method_is_subsumed_by_chandrupatla(): def test_the_similarity_method_is_subsumed_by_chandrupatla():
"""Stated as a test so the paper cannot drift back to claiming otherwise. """Stated as a test so the paper cannot drift back to claiming otherwise.
Chandrupatla is better on total, on the worst case, and on stability, Chandrupatla is better on total, with the same number of constants (none)
with the same number of constants (none) and the same 193 solved.""" and the same 193 solved.
CORRECTED 2026-09-21. This test used to assert that he is also better on
the worst single problem. That held only while our implementation of his
method stopped on a bracket twice as wide as every other method's; with
the tolerance corrected (it now matches scipy's elementwise.find_root on
all 199 problems) his worst problem costs 84 evaluations against the
credit rule's 83. The worst cases are level, to one evaluation, and the
test now says exactly that."""
from simroot.pure import pure_root from simroot.pure import pure_root
XTOL, MAXFEV, ACC = 1e-12, 200, 1e-6 XTOL, MAXFEV, ACC = 1e-12, 200, 1e-6
ours_t = ours_w = ours_s = 0 ours_t = ours_w = ours_s = 0
@@ -79,4 +87,4 @@ def test_the_similarity_method_is_subsumed_by_chandrupatla():
cha_w = max(cha_w, n) if ok else cha_w cha_w = max(cha_w, n) if ok else cha_w
assert cha_s == ours_s == 193 assert cha_s == ours_s == 193
assert cha_t < ours_t, (cha_t, ours_t) assert cha_t < ours_t, (cha_t, ours_t)
assert cha_w <= ours_w, (cha_w, ours_w) assert abs(cha_w - ours_w) <= 1, (cha_w, ours_w)
+7 -4
View File
@@ -168,10 +168,13 @@ def test_charged_totals_are_what_section_5_reports():
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
with open(os.path.join(root, "sweep_full.json"), encoding="utf8") as fh: with open(os.path.join(root, "sweep_full.json"), encoding="utf8") as fh:
rows = {r["label"]: r for r in json.load(fh)} rows = {r["label"]: r for r in json.load(fh)}
# brent is now run at the better of ours and scipy.brentq per problem, # since 2026-09-21 the competitors are the library alone: brent is
# which is why it is 6988 rather than the 6991 of our implementation alone # scipy.brentq (7004; our pure-Python implementation, timed in section 5.2,
# is 6991) and chandrupatla is scipy's elementwise.find_root (5228; the
# earlier 5208 came from a per-problem best of ours and the library, with
# ours stopping on a bracket twice as wide)
for lbl, want in (("pure.ratio.derived3.project", 6026), for lbl, want in (("pure.ratio.derived3.project", 6026),
("brent", 6988), ("bisection", 10620), ("brent", 7004), ("bisection", 10620),
("chandrupatla", 5208)): ("chandrupatla", 5228)):
got = rows[lbl]["APS"]["total"] + rows[lbl]["CHA"]["total"] got = rows[lbl]["APS"]["total"] + rows[lbl]["CHA"]["total"]
assert got == want, (lbl, got, want) assert got == want, (lbl, got, want)
+62 -48
View File
@@ -4,9 +4,11 @@ A paper is a claim about data; transcribing its tables by hand is how a claim
stops matching its data. A failure here means the paper is wrong, not the stops matching its data. A failure here means the paper is wrong, not the
test. test.
The proposed method is now the CREDIT safeguard - the only configuration with The proposed method is the scheduled projection with its multiplier DECLARED as
no free constant. The projection variant is kept in the paper as the other the one free constant (Valeri, 2026-09-21, variant a); the earned projection
end of a stated trade (better average, one constant), so both are checked. (credit) is the constant-free variant. Both are checked. Baselines are the
library alone since 2026-09-21: Chandrupatla = scipy elementwise.find_root,
Brent = brentq.
""" """
from __future__ import annotations from __future__ import annotations
@@ -14,10 +16,11 @@ import json
import os import os
import sys import sys
from report_sweep import PROPOSED, head_to_head, place, rank from report_sweep import CREDIT, PROPOSED, head_to_head, place, rank
FAILS = [] FAILS = []
PROJ = "pure.ratio.derived3.project" PROJ = "pure.ratio.derived3.project"
assert PROPOSED == PROJ, "the paper proposes the projection (variant a)"
def check(label, got, want): def check(label, got, want):
@@ -49,47 +52,58 @@ def main():
check("number of configurations", len(rows), 264) check("number of configurations", len(rows), 264)
# ---- the proposed method: credit safeguard, no free constant ---------- # ---- the proposed method: projection, one declared constant ----------
print("\nThe proposed method (credit safeguard):") print("\nThe proposed method (projection, multiplier declared):")
check("its label", PROPOSED, "pure.ratio.derived3.credit") for setname, pl, tot, sol in (("APS", 3, 2806, 153),
for setname, pl, tot, sol in (("APS", 29, 3568, 153), ("CHA", 47, 3220, 40)):
("CHA", 10, 3020, 40)):
d = row(rows, setname, PROPOSED) d = row(rows, setname, PROPOSED)
check(f"{setname} place", place(rows, setname, PROPOSED), pl) check(f"{setname} place", place(rows, setname, PROPOSED), pl)
check(f"{setname} total", d["total"], tot) check(f"{setname} total", d["total"], tot)
check(f"{setname} solved", d["solved"], sol) check(f"{setname} solved", d["solved"], sol)
check("both sets", row(rows, "APS", PROPOSED)["total"] check("both sets", row(rows, "APS", PROPOSED)["total"]
+ row(rows, "CHA", PROPOSED)["total"], 6588) + row(rows, "CHA", PROPOSED)["total"], 6026)
print("\nHead to head of the proposed method:") print("\nHead to head of the proposed method:")
for setname, opp, want in [ for setname, opp, want in [
("APS", "chandrupatla", (79, 3, 71)), ("APS", "chandrupatla", (87, 24, 42)),
("APS", "toms748", (77, 9, 67)), ("APS", "toms748", (86, 16, 51)),
("APS", "brent", (78, 5, 70)), ("APS", "brent", (102, 11, 40)),
("APS", "itp", (103, 10, 40)), ("APS", "itp", (132, 3, 18)),
("APS", "bisection", (122, 1, 30)), ("APS", "itp_oracle", (121, 4, 28)),
("CHA", "chandrupatla", (5, 0, 35)), ("APS", "bisection", (152, 1, 0)),
("CHA", "toms748", (24, 0, 16)), ("CHA", "chandrupatla", (9, 5, 26)),
("CHA", "brent", (18, 0, 20)), ("CHA", "toms748", (31, 1, 8)),
("CHA", "itp", (12, 20, 8))]: ("CHA", "brent", (30, 4, 4)),
("CHA", "itp", (17, 0, 23)),
("CHA", "itp_oracle", (16, 0, 24))]:
w, t, l, _ = head_to_head(rows, PROPOSED, opp, setname) w, t, l, _ = head_to_head(rows, PROPOSED, opp, setname)
check(f"{setname} vs {opp}", (w, t, l), want) check(f"{setname} vs {opp}", (w, t, l), want)
# ---- the trade against the projection variant ------------------------- def sign_p(w, l):
print("\nThe stated trade: credit against the projection variant:") import math as _m
check("projection APS", row(rows, "APS", PROJ)["total"], 2806) k, n = min(w, l), w + l
check("projection CHA", row(rows, "CHA", PROJ)["total"], 3220) return min(1.0, 2.0 * sum(_m.comb(n, i) for i in range(k + 1)) / 2.0 ** n)
check("projection both", row(rows, "APS", PROJ)["total"] w, _t, l, _ = head_to_head(rows, PROPOSED, "chandrupatla", "APS")
+ row(rows, "CHA", PROJ)["total"], 6026) check("APS vs Chandrupatla sign test p < 1e-4", sign_p(w, l) < 1e-4, True)
check("credit costs more in total", w, _t, l, _ = head_to_head(rows, PROPOSED, "chandrupatla", "CHA")
(row(rows, "APS", PROPOSED)["total"] check("CHA vs Chandrupatla sign test p (rounded)", round(sign_p(w, l), 3), 0.006)
+ row(rows, "CHA", PROPOSED)["total"])
> (row(rows, "APS", PROJ)["total"] + row(rows, "CHA", PROJ)["total"]), # ---- the constant-free variant ---------------------------------------
True) print("\nThe constant-free variant (earned projection):")
for setname, opp, want in [("APS", "brent", (102, 11, 40)), for setname, pl, tot in (("APS", 29, 3568), ("CHA", 10, 3020)):
("APS", "toms748", (86, 16, 51))]: check(f"credit {setname} place", place(rows, setname, CREDIT), pl)
w, t, l, _ = head_to_head(rows, PROJ, opp, setname) check(f"credit {setname} total", row(rows, setname, CREDIT)["total"], tot)
check(f"projection {setname} vs {opp}", (w, t, l), want) check("credit both sets", row(rows, "APS", CREDIT)["total"]
+ row(rows, "CHA", CREDIT)["total"], 6588)
check("credit costs 762 more on the canonical set",
row(rows, "APS", CREDIT)["total"] - row(rows, "APS", PROPOSED)["total"], 762)
for setname, opp, want in [("APS", "chandrupatla", (79, 3, 71)),
("APS", "toms748", (77, 9, 67)),
("APS", "brent", (78, 5, 70))]:
w, t, l, _ = head_to_head(rows, CREDIT, opp, setname)
check(f"credit {setname} vs {opp}", (w, t, l), want)
w, _t, l, _ = head_to_head(rows, CREDIT, "chandrupatla", "APS")
check("credit APS vs Chandrupatla sign test p (rounded)", round(sign_p(w, l), 2), 0.57)
# ---- what the earlier, order-destroying guards cost -------------------- # ---- what the earlier, order-destroying guards cost --------------------
print("\nThe guards that replace the step instead of projecting it:") print("\nThe guards that replace the step instead of projecting it:")
@@ -107,7 +121,7 @@ def main():
# ---- the field -------------------------------------------------------- # ---- the field --------------------------------------------------------
print("\nAlefeld-Potra-Shi:") print("\nAlefeld-Potra-Shi:")
check("APS winner", rank(rows, "APS")[0]["label"], "chandrupatla") check("APS winner", rank(rows, "APS")[0]["label"], "chandrupatla")
for lbl, pl, tot in [("chandrupatla", 1, 2785), ("toms748", 4, 2820), for lbl, pl, tot in [("chandrupatla", 1, 2787), ("toms748", 4, 2820),
("brent", 5, 2889), ("dekker", 12, 3158), ("brent", 5, 2889), ("dekker", 12, 3158),
("itp_oracle", 13, 3260), ("ridders", 20, 3313), ("itp_oracle", 13, 3260), ("ridders", 20, 3313),
("itp", 34, 3957), ("illinois", 73, 7045), ("itp", 34, 3957), ("illinois", 73, 7045),
@@ -121,9 +135,9 @@ def main():
top = rank(rows, "CHA")[0] top = rank(rows, "CHA")[0]
check("CHA rank 1 label", top["label"], "pure.sqrt.bracket.project") check("CHA rank 1 label", top["label"], "pure.sqrt.bracket.project")
check("CHA rank 1 solves one more than anyone else", top["CHA"]["solved"], 41) check("CHA rank 1 solves one more than anyone else", top["CHA"]["solved"], 41)
for lbl, pl, tot in [("chandrupatla", 2, 2423), ("itp_oracle", 3, 2611), for lbl, pl, tot in [("chandrupatla", 2, 2441), ("itp_oracle", 3, 2611),
("itp", 6, 2857), ("bisection", 16, 3090), ("itp", 6, 2857), ("bisection", 16, 3090),
("toms748", 164, 4000), ("brent", 226, 4099)]: ("toms748", 164, 4000), ("brent", 226, 4115)]:
check(f"CHA {lbl} place", place(rows, "CHA", lbl), pl) check(f"CHA {lbl} place", place(rows, "CHA", lbl), pl)
check(f"CHA {lbl} total", row(rows, "CHA", lbl)["total"], tot) check(f"CHA {lbl} total", row(rows, "CHA", lbl)["total"], tot)
check("CHA brent solved", row(rows, "CHA", "brent")["solved"], 38) check("CHA brent solved", row(rows, "CHA", "brent")["solved"], 38)
@@ -132,7 +146,7 @@ def main():
+ row(rows, "CHA", PROPOSED)["total"]) + row(rows, "CHA", PROPOSED)["total"])
cha_both = (row(rows, "APS", "chandrupatla")["total"] cha_both = (row(rows, "APS", "chandrupatla")["total"]
+ row(rows, "CHA", "chandrupatla")["total"]) + row(rows, "CHA", "chandrupatla")["total"])
check("chandrupatla, both sets", cha_both, 5208) check("chandrupatla, both sets", cha_both, 5228)
check("Chandrupatla beats us on both sets", cha_both < ours_both, True) check("Chandrupatla beats us on both sets", cha_both < ours_both, True)
# ---- the regime findings --------------------------------------------- # ---- the regime findings ---------------------------------------------
@@ -387,13 +401,13 @@ def verify_gap_decomposition():
all(any(w == 0.0 for w in ws) for _p, _f, ws in ck["exceptions"]), True) all(any(w == 0.0 for w in ws) for _p, _f, ws in ck["exceptions"]), True)
fam = ck["fam"] fam = ck["fam"]
gap = {k: v[2] - v[3] for k, v in fam.items()} gap = {k: v[2] - v[3] for k, v in fam.items()}
check("total gap on the losing problems", sum(gap.values()), 262) check("total gap on the losing problems", sum(gap.values()), 244)
check("flat families' gap", sum(gap[k] for k in ("fun3", "fun4", "fun5", "fun6")), 178) check("flat families' gap", sum(gap[k] for k in ("fun3", "fun4", "fun5", "fun6")), 160)
check("fun1 gap", gap["fun1"], 58) check("fun1 gap", gap["fun1"], 58)
for k, want in (("fun3", 93), ("fun4", 96), ("fun5", 100), ("fun6", 100)): for k, want in (("fun3", 92), ("fun4", 96), ("fun5", 100), ("fun6", 100)):
b, q = fam[k][0], fam[k][1] b, q = fam[k][0], fam[k][1]
check(f"Chandrupatla bisect share {k} %", round(100.0 * b / (b + q)), want) check(f"Chandrupatla bisect share {k} %", round(100.0 * b / (b + q)), want)
for k, want in (("fun4", 293), ("fun5", 228), ("fun6", 228)): for k, want in (("fun4", 297), ("fun5", 233), ("fun6", 233)):
check(f"Chandrupatla total {k}", fam[k][3], want) check(f"Chandrupatla total {k}", fam[k][3], want)
check("m>=2 on canonical states %", round(fl["APS (canonical)"][2], 1), 0.2) check("m>=2 on canonical states %", round(fl["APS (canonical)"][2], 1), 0.2)
check("m>=2 on flat states %", round(fl["CHA flat fun3-6"][2], 1), 5.8) check("m>=2 on flat states %", round(fl["CHA flat fun3-6"][2], 1), 5.8)
@@ -462,10 +476,10 @@ def verify_safeguard_and_tables():
check("CHA TOMS 748 over the 2x bound", by["toms748"]["CHA"]["over"], 15) check("CHA TOMS 748 over the 2x bound", by["toms748"]["CHA"]["over"], 15)
check("CHA Brent over the 2x bound", by["brent"]["CHA"]["over"], 18) check("CHA Brent over the 2x bound", by["brent"]["CHA"]["over"], 18)
for lbl, setname, opp, want in ( for lbl, setname, opp, want in (
(PROPOSED, "APS", "itp_oracle", (86, 6, 61)), (CREDIT, "APS", "itp_oracle", (86, 6, 61)),
(PROPOSED, "CHA", "itp_oracle", (2, 3, 35)), (CREDIT, "CHA", "itp_oracle", (2, 3, 35)),
(PROPOSED, "CHA", "bisection", (5, 0, 35)), (CREDIT, "CHA", "bisection", (5, 0, 35)),
(PROJ, "APS", "chandrupatla", (86, 25, 42)), (PROJ, "APS", "chandrupatla", (87, 24, 42)),
(PROJ, "APS", "itp", (132, 3, 18)), (PROJ, "APS", "itp", (132, 3, 18)),
(PROJ, "APS", "itp_oracle", (121, 4, 28)), (PROJ, "APS", "itp_oracle", (121, 4, 28)),
(PROJ, "APS", "bisection", (152, 1, 0)), (PROJ, "APS", "bisection", (152, 1, 0)),
@@ -475,8 +489,8 @@ def verify_safeguard_and_tables():
(PROJ, "CHA", "itp_oracle", (16, 0, 24))): (PROJ, "CHA", "itp_oracle", (16, 0, 24))):
w, t, l, _ = head_to_head(rows, lbl, opp, setname) w, t, l, _ = head_to_head(rows, lbl, opp, setname)
check(f"{lbl.split('.')[-1]} {setname} vs {opp}", (w, t, l), want) check(f"{lbl.split('.')[-1]} {setname} vs {opp}", (w, t, l), want)
for lbl, want in ((PROPOSED, 6588), (PROJ, 6026), ("chandrupatla", 5208), for lbl, want in ((CREDIT, 6588), (PROJ, 6026), ("chandrupatla", 5228),
("itp_oracle", 5871), ("toms748", 6820), ("brent", 6988)): ("itp_oracle", 5871), ("toms748", 6820), ("brent", 7004)):
check(f"both sets {lbl}", by[lbl]["APS"]["total"] + by[lbl]["CHA"]["total"], want) check(f"both sets {lbl}", by[lbl]["APS"]["total"] + by[lbl]["CHA"]["total"], want)
print("\n§7 under the earned projection:") print("\n§7 under the earned projection:")