Sign tests by product underflowed: FIXED everywhere (branch, not master - the paper is not yet updated). FA * fnew < 0.0 with FA ~ -1e-200, fnew ~ 1e-150 is -0.0 and '-0.0 < 0.0' is False, so on flat functions (x^19) the bracket update moved the wrong end, the root left the bracket and the run still reported convergence - the proposed rule returned 4.9e-9, bisection 3.5e-9, ITP 1.6e-8 with a claimed 1e-12 bracket; the 1e-6 harness scored them solved. Same defect in our bisection, false-position family, Ridders, Brent, ITP and twelve copies in stage scripts, simroot/sim.py and one test; all replaced by _opposite / _same_sign, pinned by test_sign_underflow.

Impact, every sweep regenerated: the paper's key configurations are unchanged to the evaluation (proposed 2806/3220, credit 3568/3020, Chandrupatla, TOMS 748, Brent, bisection); 46 stated numbers move (ITP CHA 2857 -> 2896, ITP oracle 2611 -> 2647, record CHA figures by 14-18, the grid means by ~1%) and one finding weakens: the measured-order exponent of section 7.1 was cheaper in 29 of 60 far cells and 7 of 7 safeguards; with the fix 12 of 61 and 6 of 7, and on the wide-bracket set it now loses in every safeguard and drops 5 problems.

PREREG G1-G3 outcome on the fixed code: G2 (repeating step pattern, period from the data) PASSES Y1-Y4 and is ACCEPTED by the stop rule - proposed rule APS 2806 -> 2782 (below Chandrupatla's 2787), CHA 3220 -> 3169, both 6026 -> 5951, per problem vs Chandrupatla on APS 95/20/38 p = 8e-7. G1 fails Y1/Y2 (its Y3 miss was the sign bug), G3 fails Y2, A fails Y2. Regression 887 pass; the two red tests are the paper-number check (awaiting the paper update) and test_blend (fixed since, copy in stage16).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
vvs
2026-09-21 12:49:01 +01:00
co-authored by Claude Opus 5
parent 29ab49599c
commit 68df1d25db
34 changed files with 680 additions and 64 deletions
+80
View File
@@ -0,0 +1,80 @@
"""Verdicts Y1-Y6 of PREREG_extrapolation_gates_2026-09-21.md from sweep_extrap2.json,
plus the check that mode 'steps' (A) reproduces sweep_extrap.json bit for bit.
python -u analyze_extrap2.py
"""
from __future__ import annotations
import json
import math
from analyze_extrap import FAMILY, FLAT, fam_sum, h2h, load, sign_p
MODES = ("none", "steps", "confirmed", "pattern", "steps+results")
NAME = {"none": "no lengthening", "steps": "A", "confirmed": "G1 confirmed",
"pattern": "G2 pattern", "steps+results": "G3 steps+results"}
def key(r):
s = r["spec"]
return (s[1], s[2], s[3], s[5], s[6], s[7])
def main():
rows = load("sweep_extrap2.json")
old = {r["label"]: r for r in load("sweep_extrap.json")}
cha = {r["label"]: r for r in load("sweep_full.json")}["chandrupatla"]
cells = {}
for r in rows:
cells.setdefault(key(r), {})[r["spec"][9]] = r
# A reproduces its own earlier sweep?
same = diff = 0
for r in rows:
if r["spec"][9] in ("none", "steps") and r["label"] in old:
o = old[r["label"]]
if o["APS"]["per"] == r["APS"]["per"] and o["CHA"]["per"] == r["CHA"]["per"]:
same += 1
else:
diff += 1
print(f"A and no-lengthening cells identical to sweep_extrap.json: {same} same, {diff} differ\n")
prop = cells[("ratio", "derived3", "project", "none", "recent", "distance")]
base = prop["none"]
print("PROPOSED RULE (tent, 3-point, projection), by mode:")
print(f" {'mode':<18} {'APS':>5} {'CHA':>5} {'both':>5} {'flat':>5} Y1 Y2 Y3 Y4 "
f"vs Chandrupatla APS (w/t/l, p)")
for m in MODES:
r = prop[m]
fam = [c[m] for k, c in cells.items() if k[2] in FAMILY]
viol = sum(x["APS"]["over"] + x["CHA"]["over"] for x in fam)
low = sum(1 for x in fam if x["APS"]["solved"] + x["CHA"]["solved"] < 193)
y1 = r["CHA"]["total"] < 3220
y2 = r["APS"]["total"] <= 2806
y3 = viol == 0 and low == 0
y4 = fam_sum(r, FLAT) < fam_sum(base, FLAT)
w, t, l = h2h(r, cha, "APS")
mark = lambda b: "✓" if b else "✗" # noqa: E731
print(f" {NAME[m]:<18} {r['APS']['total']:5d} {r['CHA']['total']:5d} "
f"{r['APS']['total'] + r['CHA']['total']:5d} {fam_sum(r, FLAT):5d} "
f"{mark(y1) if m != 'none' else '-':>2} {mark(y2) if m != 'none' else '-':>2} "
f"{mark(y3):>2} {mark(y4) if m != 'none' else '-':>2} {w}/{t}/{l} p={sign_p(w, l):.2g}"
+ (" ACCEPTED" if m != "none" and y1 and y2 and y3 else ""))
print("\nY6 best cell over both sets, per mode (guarantee family, >= 193 solved):")
for m in MODES:
cand = [c[m] for k, c in cells.items() if k[2] in FAMILY
and c[m]["APS"]["solved"] + c[m]["CHA"]["solved"] >= 193]
b = min(cand, key=lambda x: x["APS"]["total"] + x["CHA"]["total"])
print(f" {NAME[m]:<18} {b['label']:<52} {b['APS']['total']}+{b['CHA']['total']}="
f"{b['APS']['total'] + b['CHA']['total']}")
print("\nreadings under the projection, APS/CHA by mode:")
for rd, k in (("slope", ("slope", "recent", "distance")),
("shape+slope", ("slope", "shape", "distance")),
("dissim+slope", ("slope", "recent", "dissimilar")),
("defect", ("defect", "recent", "distance"))):
c = cells[("ratio", "derived3", "project") + k]
print(f" {rd:<13} " + " ".join(f"{NAME[m].split()[0]} {c[m]['APS']['total']}/"
f"{c[m]['CHA']['total']}" for m in MODES))
if __name__ == "__main__":
main()
+22
View File
@@ -0,0 +1,22 @@
A and no-lengthening cells identical to sweep_extrap.json: 182 same, 0 differ
PROPOSED RULE (tent, 3-point, projection), by mode:
mode APS CHA both flat Y1 Y2 Y3 Y4 vs Chandrupatla APS (w/t/l, p)
no lengthening 2806 3220 6026 1823 - - ✓ - 87/24/42 p=9.2e-05
A 2967 2926 5893 1525 ✓ ✗ ✓ ✓ 42/62/49 p=0.53
G1 confirmed 2933 3281 6214 1859 ✗ ✗ ✓ ✗ 81/28/44 p=0.0012
G2 pattern 2782 3169 5951 1780 ✓ ✓ ✓ ✓ 95/20/38 p=8.2e-07 ACCEPTED
G3 steps+results 2953 3205 6158 1804 ✓ ✗ ✓ ✓ 79/28/46 p=0.004
Y6 best cell over both sets, per mode (guarantee family, >= 193 solved):
no lengthening pure+dissim+slope.ratio.derived3.project 2726+2799=5525
A pure+dissim+ext+slope.ratio.derived3.project 2859+2553=5412
G1 confirmed pure+dissim+extC+slope.ratio.derived3.project 2882+2757=5639
G2 pattern pure+dissim+extP+slope.ratio.derived3.project 2759+2789=5548
G3 steps+results pure+dissim+extR+slope.ratio.derived3.project 2808+2785=5593
readings under the projection, APS/CHA by mode:
slope no 2848/3114 A 2971/2763 G1 3004/2993 G2 2809/3033 G3 2934/3043
shape+slope no 2675/3498 A 2930/3506 G1 2800/3511 G2 2894/3483 G3 2944/3477
dissim+slope no 2726/2799 A 2859/2553 G1 2882/2757 G2 2759/2789 G3 2808/2785
defect no 2523/3210 A 3180/2955 G1 2667/3260 G2 2580/3183 G3 2684/3210
+78
View File
@@ -0,0 +1,78 @@
CHANGED pure.ratio.derived3.step CHA: total 3460 -> 3524, solved 40 -> 40, over2x 10 -> 15
CHANGED pure.ratio.start.both CHA: total 3631 -> 3626, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.ratio.start.project CHA: total 5029 -> 5024, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.ratio_soft.bracket.both CHA: total 3315 -> 3335, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.ratio_soft.bracket.bracket CHA: total 3321 -> 3341, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.ratio_soft.bracket.credit CHA: total 3130 -> 3129, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.ratio_soft.bracket.project CHA: total 4763 -> 4763, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.ratio_soft.bracket.safe CHA: total 3998 -> 4059, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.ratio_soft.bracket.step CHA: total 4004 -> 4065, solved 40 -> 40, over2x 2 -> 2
CHANGED pure.ratio_soft.derived.both CHA: total 3308 -> 3358, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.ratio_soft.derived.bracket CHA: total 3167 -> 3235, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.ratio_soft.derived.credit CHA: total 3094 -> 3094, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.ratio_soft.derived.safe CHA: total 3643 -> 3695, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.ratio_soft.derived.step CHA: total 3704 -> 3756, solved 40 -> 40, over2x 8 -> 8
CHANGED pure.ratio_soft.derived3.both CHA: total 3370 -> 3435, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.ratio_soft.derived3.bracket CHA: total 3513 -> 3581, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.ratio_soft.derived3.none CHA: total 4285 -> 4248, solved 35 -> 35, over2x 5 -> 5
CHANGED pure.ratio_soft.derived3.safe CHA: total 3681 -> 3741, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.ratio_soft.derived3.step CHA: total 3724 -> 3819, solved 40 -> 40, over2x 5 -> 10
CHANGED pure.ratio_soft.start.both CHA: total 3508 -> 3508, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.ratio_soft.start.project CHA: total 4954 -> 4949, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.rational.bracket.both CHA: total 3314 -> 3323, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.rational.bracket.bracket CHA: total 3433 -> 3440, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.rational.bracket.credit CHA: total 3040 -> 3039, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.rational.bracket.project CHA: total 4676 -> 4671, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.rational.bracket.safe CHA: total 3835 -> 3829, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.rational.bracket.step CHA: total 3841 -> 3835, solved 40 -> 40, over2x 3 -> 3
CHANGED pure.rational.derived.both CHA: total 3674 -> 3715, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.rational.derived.bracket CHA: total 3766 -> 3821, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.rational.derived.safe CHA: total 3557 -> 3637, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.rational.derived.step CHA: total 3618 -> 3698, solved 40 -> 40, over2x 10 -> 10
CHANGED pure.rational.derived3.both CHA: total 3815 -> 3873, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.rational.derived3.bracket CHA: total 3994 -> 4052, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.rational.derived3.safe CHA: total 3505 -> 3538, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.rational.derived3.step CHA: total 3618 -> 3698, solved 40 -> 40, over2x 9 -> 13
CHANGED pure.rational.start.project CHA: total 4762 -> 4758, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.bracket.both CHA: total 3391 -> 3431, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.bracket.bracket CHA: total 3387 -> 3443, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.bracket.safe CHA: total 3989 -> 4038, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.bracket.step CHA: total 4017 -> 4066, solved 40 -> 40, over2x 1 -> 1
CHANGED pure.sqrt.derived.both CHA: total 3870 -> 3908, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.derived.bracket CHA: total 4102 -> 4162, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.derived.none CHA: total 3511 -> 3538, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.derived.project CHA: total 3511 -> 3538, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.derived.safe CHA: total 3493 -> 3520, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.derived.step CHA: total 3493 -> 3525, solved 40 -> 40, over2x 0 -> 1
CHANGED pure.sqrt.derived3.both CHA: total 3737 -> 3781, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.derived3.bracket CHA: total 3859 -> 3904, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.derived3.credit CHA: total 3130 -> 3130, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.derived3.none CHA: total 3852 -> 3883, solved 40 -> 40, over2x 4 -> 4
CHANGED pure.sqrt.derived3.project CHA: total 3642 -> 3649, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.derived3.safe CHA: total 3891 -> 3928, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.derived3.step CHA: total 3924 -> 4039, solved 40 -> 40, over2x 5 -> 10
CHANGED pure.sqrt.start.both CHA: total 3829 -> 3829, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.start.credit CHA: total 3130 -> 3130, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.start.project CHA: total 5120 -> 5115, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.start.safe CHA: total 4395 -> 4395, solved 40 -> 40, over2x 0 -> 0
CHANGED pure.sqrt.start.step CHA: total 4396 -> 4396, solved 40 -> 40, over2x 0 -> 0
CHANGED ridders CHA: total 4216 -> 3768, solved 36 -> 40, over2x 0 -> 0
unexpected changes: 168
rows changed: APS 5 of 264, CHA 163 of 264
label APS old->new CHA old->new solved
pure.ratio.derived3.project 2806->2806 3220->3220 193->193
pure.ratio.derived3.credit 3568->3568 3020->3020 193->193
pure.ratio.derived3.none 2806->2806 4104->4104 188->188
pure.ratio.derived3.safe 3036->3036 3363->3390 193->193
chandrupatla 2787->2787 2441->2441 193->193
toms748 2820->2820 4000->4000 193->193
brent 2889->2889 4115->4115 191->191
bisection 7530->7530 3090->3090 193->193
itp 3957->3957 2857->2896 193->193
itp_oracle 3260->3260 2611->2647 193->193
ridders 3313->3313 4216->3768 189->193
illinois 7045->7045 6234->6234 176->176
regula_falsi 10560->10560 8239->8239 133->133
pure.sqrt.bracket.project 7796->7796 4516->4516 194->194
pure.exp_abs.bracket.both 3461->3461 2733->2736 193->193
proposed rule, CHA problems changed: {}
+227
View File
@@ -0,0 +1,227 @@
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
BAD CHA place got 45 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
BAD CHA itp_oracle total got 2647 want 2611
OK CHA itp place got 6 want 6
BAD CHA itp total got 2896 want 2857
OK CHA bisection place got 16 want 16
OK CHA bisection total got 3090 want 3090
BAD CHA toms748 place got 159 want 164
OK CHA toms748 total got 4000 want 4000
BAD CHA brent place got 227 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:
BAD CHA cbrt over the whole set got 4109 want 4099
OK single fixed choice, both sets got 6026 want 6026
BAD switch by benchmark set, both sets got 6915 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:
BAD mean total, scale=derived3, lin got 6893 want 6809
BAD mean total, scale=derived3, quad got 22315 want 22294
BAD mean total, scale=derived, lin got 6981 want 6906
BAD mean total, scale=derived, quad got 22206 want 22167
BAD mean total, scale=bracket, lin got 10279 want 10263
BAD mean total, scale=bracket, quad got 19127 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']
BAD similarity ratio best got 6560 want 6496
BAD similarity exp_abs best got 6895 want 6822
BAD similarity rational best got 7118 want 7038
BAD similarity ratio_soft best got 9578 want 9502
BAD similarity sqrt best got 10546 want 10497
BAD similarity cbrt best got 12002 want 11941
BAD similarity exp_sq best got 14307 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']
BAD scale derived3 best got 6560 want 6496
BAD scale derived best got 6702 want 6628
BAD scale bracket best got 9107 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
BAD slope.ratio.derived3.project CHA got 3114 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
BAD pure+shape+slope.ratio.derived3.safe CHA got 3126 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
BAD pure+dissim+slope.ratio.derived3.project CHA got 2799 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
BAD pure+shape+dissim+slope.ratio.derived3.safe CHA got 2871 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
!! gate.ratio.derived.project not in sweep_gate.json — stale sweep
OK log.ratio_soft.derived.safe APS got 6940 want 6940
BAD log.ratio_soft.derived.safe CHA got 3541 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
BAD pure+dissim+slope.ratio.derived3.safe CHA per-set got 2695 want 2683
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
BAD far+rational both CHA m=1 got 3406 want 3387
BAD far+rational both CHA order got 3906 want 3216
OK far+rational bracket APS m=1 got 4606 want 4606
OK far+rational bracket APS order got 3542 want 3542
BAD far+rational bracket CHA m=1 got 3602 want 3571
BAD far+rational bracket CHA order got 3950 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
BAD far+rational credit CHA order got 3646 want 2884
OK far+rational project APS m=1 got 6932 want 6932
OK far+rational project APS order got 6364 want 6364
BAD far+rational project CHA m=1 got 4789 want 4794
BAD far+rational project CHA order got 5098 want 4559
OK far+rational safe APS m=1 got 5987 want 5987
OK far+rational safe APS order got 5229 want 5229
BAD far+rational safe CHA m=1 got 4134 want 4108
BAD far+rational safe CHA order got 4503 want 3923
OK far+rational step APS m=1 got 6435 want 6435
OK far+rational step APS order got 5551 want 5551
BAD far+rational step CHA m=1 got 4287 want 4257
BAD far+rational step CHA order got 4602 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
BAD cheaper in 7 of 7 safeguards got 6 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
BAD ratio.band.project CHA got 3059 want 3066
BAD ratio.band.credit CHA got 2892 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
BAD ratio_soft.simpair.project CHA got 2897 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
BAD band refusal rate % got 63.5 want 63.6
BAD defect refusal rate % got 6.6 want 7.0
The gap decomposition (fresh runs of the diagnosis scripts):
Traceback (most recent call last):
File "/home/valeri/sim-numerics/verify_article.py", line 558, in <module>
main()
File "/home/valeri/sim-numerics/verify_article.py", line 220, in main
verify_gap_decomposition()
File "/home/valeri/sim-numerics/verify_article.py", line 388, in verify_gap_decomposition
fl = diag_flat_detectable.main()
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/valeri/sim-numerics/diag_flat_detectable.py", line 54, in main
groups[key] += states(d["f"], a, b, float(d["root"]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/valeri/sim-numerics/diag_flat_detectable.py", line 40, in states
out.append(_local_order(s["x"], s["fx"], s["xp"], s["fp"], s["xp2"], s["fp2"]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/valeri/sim-numerics/simroot/pure.py", line 592, in _local_order
theta = f2 * fpp / (fp * fp)
~~~~~~~~~^~~~~~~~~~~
ZeroDivisionError: float division by zero
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# Regenerate EVERY sweep file after a change to the method or the baselines
# (2026-09-21: sign tests by product underflowed; fixed in pure.py and baselines.py).
# Frozen runner: detached, logged, explicit end marker.
export OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1
cd "$HOME/sim-numerics" || exit 1
for flag in "" --log --gate --slope --cap --combo --points --direction --order \
--decision --extrap --extrap2; do
echo "=== sweep ${flag:-(full)} ==="
~/torchenv/bin/python -u sweep_full.py $flag 2>&1 | grep -E "^sweep:|wrote|Traceback|Error|FAILED"
done
echo "=== SWEEPS DONE ==="
+21 -6
View File
@@ -47,6 +47,21 @@ class _Budget(Exception):
pass
def _opposite(u, v):
"""True when u and v have strictly opposite signs. NOT u * v < 0: for the tiny
values of a flat function near its root (x^19 there is ~1e-200) the product
underflows to -0.0, '-0.0 < 0.0' is False, and a bracket update then moves the
wrong end and loses the root while still reporting convergence (found 2026-09-21
on fun6.5, x^19 over [-10, 100])."""
return (u < 0.0 < v) or (v < 0.0 < u)
def _same_sign(u, v):
"""True when u and v are both strictly positive or both strictly negative; see
_opposite for why this is not u * v > 0."""
return (u > 0.0 and v > 0.0) or (u < 0.0 and v < 0.0)
def _converged(a, b, xtol):
return abs(b - a) <= xtol
@@ -68,7 +83,7 @@ def bisection(f, a, b, xtol, maxfev):
fx = g(x)
if fx == 0.0:
return x, g.n, True
if fa * fx < 0.0:
if _opposite(fa, fx):
b, fb = x, fx
else:
a, fa = x, fx
@@ -104,7 +119,7 @@ def _false_position_family(f, a, b, xtol, maxfev, correction):
fx = g(x)
if fx == 0.0:
return x, g.n, True
if fa * fx < 0.0:
if _opposite(fa, fx):
# b replaced; a became stale -> correct fa
fa = correction(fa, fb, fx)
b, fb = x, fx
@@ -170,9 +185,9 @@ def ridders(f, a, b, xtol, maxfev):
if fx == 0.0:
return x, g.n, True
# rebuild the tightest bracket among a, c, x, b
if fc * fx < 0.0:
if _opposite(fc, fx):
a, fa, b, fb = (c, fc, x, fx) if c < x else (x, fx, c, fc)
elif fa * fx < 0.0:
elif _opposite(fa, fx):
b, fb = x, fx
else:
a, fa = x, fx
@@ -202,7 +217,7 @@ def brent(f, a, b, xtol, maxfev, stats=None):
c, fc = a, fa
d = e = b - a
while True:
if fb * fc > 0.0:
if _same_sign(fb, fc):
c, fc = a, fa
d = e = b - a
if abs(fc) < abs(fb):
@@ -305,7 +320,7 @@ def itp(f, a, b, xtol, maxfev, kappa1=None, kappa2=2.0, n0=1):
fx = g(x)
if fx == 0.0:
return x, g.n, True
if fx * fa < 0.0:
if _opposite(fx, fa):
b, fb = x, fx
else:
a, fa = x, fx
+79 -24
View File
@@ -93,7 +93,7 @@ from __future__ import annotations
import math
from .baselines import Counter, _Budget
from .baselines import Counter, _Budget, _opposite, _same_sign
# --------------------------------------------------------------------------
# [0,1] similarity functions of a normalised residual z: S(0) = 1 (identical
@@ -126,6 +126,63 @@ DIRECTIONS = ("distance", "dissimilar")
EXPONENTS = ("none", "order")
EXTRAPOLATE_MODES = ("none", "steps", "confirmed", "pattern", "steps+results")
def _ratio_sim(a, b):
"""The SIM ratio form for two magnitudes: min/max, 1 when they are equal."""
hi = max(a, b)
return min(a, b) / hi if hi > 0.0 else 0.0
def _extrapolation_s(mode, step, x, x_prev, x_prev2, fx, f_prev, f_prev2, moves):
"""How much of the geometric-series lengthening the history CONFIRMS: returns s in
[0, 1); the step becomes step/(1 - s). 0 means leave the step alone.
"steps" A: similarity of this step to the previous displacement.
"confirmed" G1: three moves the same way, both length ratios q1, q2 < 1; the
latest ratio q2, scaled by how similar q1 and q2 are.
"pattern" G2: A's s, applied only when the pattern of successive length ratios
repeats itself - period = the lag (2 .. half the history, so that two
repeats fit) of highest mean ratio similarity; applied iff that
similarity exceeds 1/2, the bisection constant.
"steps+results" G3: the smaller of A's s and the similarity of the last two RESULTS
f_n/f_(n-1) and f_(n-1)/f_(n-2); a result of either sign change
(a crossing) makes it 0.
No constant beyond bisection's 1/2 appears in any of them (PREREG 2026-09-21)."""
d1 = x - x_prev
if step == 0.0 or d1 == 0.0 or (step > 0.0) != (d1 > 0.0):
return 0.0
s_step = _ratio_sim(abs(step), abs(d1))
if mode == "steps":
return s_step
if mode == "confirmed":
if x_prev2 is None:
return 0.0
d2 = x_prev - x_prev2
if d2 == 0.0 or (d2 > 0.0) != (d1 > 0.0):
return 0.0
q1, q2 = abs(d1) / abs(d2), abs(step) / abs(d1)
if not (q1 < 1.0 and q2 < 1.0):
return 0.0
return q2 * _ratio_sim(q1, q2)
if mode == "pattern":
qs = [abs(moves[j]) / abs(moves[j - 1]) for j in range(1, len(moves))
if moves[j - 1] != 0.0 and moves[j] != 0.0]
k, best = len(qs), 0.0
for p in range(2, k // 2 + 1):
sims = [_ratio_sim(qs[j], qs[j - p]) for j in range(p, k)]
best = max(best, sum(sims) / len(sims))
return s_step if best > 0.5 else 0.0
if mode == "steps+results":
if f_prev2 is None or f_prev == 0.0 or f_prev2 == 0.0:
return 0.0
r_n, r_p = fx / f_prev, f_prev / f_prev2
if not (r_n > 0.0 and r_p > 0.0):
return 0.0
return min(s_step, _ratio_sim(r_n, r_p))
return 0.0
def _read_z(resid, sc, exponent, m):
"""The normalised residual the similarity function is applied to.
@@ -595,8 +652,9 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
if scale not in ("derived", "derived3", "derived_rho", "derived3_rho",
"derived3_m", "derived3_cap", "start", "bracket", "far"):
raise ValueError(f"unknown scale {scale!r}")
if extrapolate not in ("none", "steps"):
if extrapolate not in EXTRAPOLATE_MODES:
raise ValueError(f"unknown extrapolate {extrapolate!r}")
moves = [] # displacement history, kept only for extrapolate="pattern"
if exponent not in EXPONENTS:
raise ValueError(f"unknown exponent {exponent!r}")
if exponent == "order" and scale not in ("derived3", "derived3_cap", "bracket", "far"):
@@ -641,7 +699,7 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
return a, g.n, True
if fb == 0.0:
return b, g.n, True
if fa * fb > 0.0:
if _same_sign(fa, fb):
raise ValueError("f(a) and f(b) must straddle zero")
A, FA, B, FB = a, fa, b, fb # the proven bracket
@@ -866,26 +924,23 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
step = _log_space_step(S, x, fx, far, slope, c_near, xtol)
if step is None:
step = (1.0 - S(z_now)) * (far - x)
if extrapolate == "steps":
# PREREG 2026-09-21 (A in the method). The similarity of THIS
# step to the previous displacement: two successive moves the
# same way, of similar length, mean the iterate creeps along a
# geometric series whose remainder is step*S/(1-S) - so the
# step is lengthened by 1/(1-S). On x^m under a Newton step
# S = 1 - 1/m and the factor is m, which lands on the root.
# At a simple root successive steps shrink superlinearly, S ->
# 0 and the step is untouched. Capped at the far end, the same
# bound the step itself has; no constant.
d_prev = x - x_prev
if (step != 0.0 and d_prev != 0.0
and (step > 0.0) == (d_prev > 0.0)):
s_steps = (min(abs(step), abs(d_prev))
/ max(abs(step), abs(d_prev)))
if s_steps < 1.0:
longer = step / (1.0 - s_steps)
reach = abs(far - x)
step = (longer if abs(longer) <= reach
else math.copysign(reach, step))
if extrapolate != "none":
# PREREG 2026-09-21 (A in the method) and PREREG 2026-09-21
# (gates G1-G3). Two successive moves the same way, of similar
# length, mean the iterate creeps along a geometric series
# whose remainder is step*S/(1-S): the step is lengthened by
# 1/(1-S). The modes differ only in how much of S the history
# CONFIRMS - see _extrapolation_s. Capped at the far end, the
# same bound the step itself has; no constant.
if extrapolate == "pattern":
moves.append(x - x_prev)
s_eff = _extrapolation_s(extrapolate, step, x, x_prev,
x_prev2, fx, f_prev, f_prev2, moves)
if 0.0 < s_eff < 1.0:
longer = step / (1.0 - s_eff)
reach = abs(far - x)
step = (longer if abs(longer) <= reach
else math.copysign(reach, step))
if stats is not None and "trace" in stats:
# DIAGNOSTIC HOOK, behaviour-neutral: records the state each
# step was taken from, so another model can be evaluated on the
@@ -934,7 +989,7 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
if stats is not None:
stats['forced'] = forced; stats['pre_refused'] = pre_refused; stats['triple_changed'] = triple_changed; stats['dir_changed'] = dir_changed
return x_new, g.n, True
if FA * fnew < 0.0:
if _opposite(FA, fnew):
B, FB = x_new, fnew
else:
A, FA = x_new, fnew
+4 -2
View File
@@ -40,6 +40,8 @@ no sigma, no temperature, no threshold, no tuned coefficient anywhere below.
"""
from __future__ import annotations
from .baselines import _opposite, _same_sign # 2026-09-21 fix
import math
from .baselines import Counter, _Budget
@@ -155,7 +157,7 @@ def sim_root(f, a, b, xtol, maxfev, similarity="neg_sq", prototypes="pair",
pending = False
if fx == 0.0:
return x, g.n, True
if fa * fx < 0.0:
if _opposite(fa, fx):
b, fb = x, fx
else:
a, fa = x, fx
@@ -228,7 +230,7 @@ def sim_root(f, a, b, xtol, maxfev, similarity="neg_sq", prototypes="pair",
mem.append((x, fx))
if fx == 0.0:
return x, g.n, True
if fa * fx < 0.0:
if _opposite(fa, fx):
b, fb = x, fx
else:
a, fa = x, fx
+4 -2
View File
@@ -30,6 +30,8 @@ anything; every problem is shown.
"""
from __future__ import annotations
from simroot.baselines import _opposite, _same_sign # 2026-09-21 fix
import math
from dataclasses import dataclass
from typing import Callable
@@ -85,7 +87,7 @@ def bisection(f, a, b, tol=TOL, maxfev=MAXFEV):
fx = g(x)
if fx == 0.0 or (b - a) <= tol:
return x, st["n"]
if fa * fx < 0.0:
if _opposite(fa, fx):
b, fb = x, fx
else:
a, fa = x, fx
@@ -118,7 +120,7 @@ def sim_step(f, a, b, tol=TOL, maxfev=MAXFEV):
fx = g(x)
if fx == 0.0 or abs(b - a) <= tol:
return x, st["n"]
if fa * fx < 0.0:
if _opposite(fa, fx):
b, fb = x, fx
else:
a, fa = x, fx
+3 -1
View File
@@ -33,6 +33,8 @@ linear S, which is exactly the collapse this stage is meant to expose.
"""
from __future__ import annotations
from simroot.baselines import _opposite, _same_sign # 2026-09-21 fix
import math
from simroot.baselines import Counter, _Budget, bisection, brent
@@ -106,7 +108,7 @@ def pure_similarity_step(f, a, b, xtol, maxfev, sim="rational", scale="s0"):
if fnew == 0.0:
return x_new, g.n, True
smax = max(smax, abs(fnew))
if fx * fnew < 0.0:
if _opposite(fx, fnew):
# the root is between the old point and the new one
far, ffar = x, fx
x, fx = x_new, fnew
+3 -1
View File
@@ -17,6 +17,8 @@ is accepted on authority. Each is recomputed here from scratch.
"""
from __future__ import annotations
from simroot.baselines import _opposite, _same_sign # 2026-09-21 fix
import math
r2 = math.sqrt(2.0)
@@ -75,7 +77,7 @@ def main():
xn = 0.5 * (A + B)
fn = f(xn)
xp2, fp2, xp, fp, x, fx = xp, fp, x, fx, xn, fn
if FA * fn < 0.0:
if _opposite(FA, fn):
B = xn
else:
A, FA = xn, fn
+4 -2
View File
@@ -23,6 +23,8 @@ Each is run raw and credit-guarded, on the same 199 problems.
"""
from __future__ import annotations
from simroot.baselines import _opposite, _same_sign # 2026-09-21 fix
import math
from scipy.optimize._tstutils import (_APS_TESTS_DICTS,
@@ -84,7 +86,7 @@ def solve(f, a, b, xtol, maxfev, rule, guarded):
return a, g.n, True
if fb == 0.0:
return b, g.n, True
if fa * fb > 0.0:
if _same_sign(fa, fb):
raise ValueError("no sign change")
A, FA, B, FB = a, fa, b, fb
hist = [(a, fa), (b, fb)]
@@ -108,7 +110,7 @@ def solve(f, a, b, xtol, maxfev, rule, guarded):
x = x_new
if fn == 0.0:
return x_new, g.n, True
if FA * fn < 0.0:
if _opposite(FA, fn):
B, FB = x_new, fn
else:
A, FA = x_new, fn
+3 -1
View File
@@ -33,6 +33,8 @@ validated against scipy.optimize.brentq to 0.2% of evaluations.
"""
from __future__ import annotations
from simroot.baselines import _opposite, _same_sign # 2026-09-21 fix
import math
from scipy.optimize._tstutils import (_APS_TESTS_DICTS,
@@ -63,7 +65,7 @@ def brent_blend(f, a, b, xtol, maxfev, mode="blend"):
c, fc = a, fa
d = e = b - a
while True:
if fb * fc > 0.0:
if _same_sign(fb, fc):
c, fc = a, fa
d = e = b - a
if abs(fc) < abs(fb):
+4 -2
View File
@@ -24,6 +24,8 @@ by 7 dB.
"""
from __future__ import annotations
from simroot.baselines import _opposite, _same_sign # 2026-09-21 fix
import math
import numpy as np
@@ -63,7 +65,7 @@ def falsi_driver(f, a, b, xtol, maxfev, repair):
return a, g.n, True
if fb == 0.0:
return b, g.n, True
if fa * fb > 0.0:
if _same_sign(fa, fb):
raise ValueError("no sign change")
sg = _Safeguard("credit", abs(b - a), xtol)
while abs(b - a) > xtol:
@@ -82,7 +84,7 @@ def falsi_driver(f, a, b, xtol, maxfev, repair):
fx = g(x_new)
if fx == 0.0:
return x_new, g.n, True
if fa * fx < 0.0:
if _opposite(fa, fx):
if repair == "illinois":
fa *= 0.5
elif repair == "pegasus":
+4 -2
View File
@@ -26,6 +26,8 @@ and Galdino's (2011).
"""
from __future__ import annotations
from simroot.baselines import _opposite, _same_sign # 2026-09-21 fix
import math
from scipy.optimize._tstutils import (_APS_TESTS_DICTS,
@@ -133,7 +135,7 @@ def falsi_modern(f, a, b, xtol, maxfev, variant):
return a, g.n, True
if fb == 0.0:
return b, g.n, True
if fa * fb > 0.0:
if _same_sign(fa, fb):
raise ValueError("no sign change")
while abs(b - a) > xtol:
lo, hi = (a, b) if a < b else (b, a)
@@ -156,7 +158,7 @@ def falsi_modern(f, a, b, xtol, maxfev, variant):
m = f_moved / (f_moved + fx) if (f_moved + fx) != 0.0 else 0.5
return m if 0.0 < m < 1.0 else 0.5
if fa * fx < 0.0:
if _opposite(fa, fx):
fa *= factor(fa, fb)
b, fb = x_new, fx
else:
+3 -1
View File
@@ -30,6 +30,8 @@ explanation is wrong and must not go into the paper.
"""
from __future__ import annotations
from simroot.baselines import _opposite, _same_sign # 2026-09-21 fix
import math
from scipy.optimize._tstutils import (_APS_TESTS_DICTS,
@@ -157,7 +159,7 @@ def replay_bracket(f, a, b, xs):
w0 = hi - lo
positions.append((xi - lo) / w0)
fi = f(xi)
if flo * fi < 0.0:
if _opposite(flo, fi):
hi = xi
else:
lo, flo = xi, fi
+3 -1
View File
@@ -13,6 +13,8 @@ directly on the worst offenders.
"""
from __future__ import annotations
from simroot.baselines import _opposite, _same_sign # 2026-09-21 fix
import math
from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS_DICTS
@@ -78,7 +80,7 @@ def replay(f, a, b, similarity="ratio"):
xp2, fp2, xp, fp, x, fx = xp, fp, x, fx, x_new, fn
if fn == 0.0:
break
if FA * fn < 0.0:
if _opposite(FA, fn):
B, FB = x_new, fn
else:
A, FA = x_new, fn
+4 -2
View File
@@ -27,6 +27,8 @@ brentq would not be comparable and is excluded.
"""
from __future__ import annotations
from simroot.baselines import _opposite, _same_sign # 2026-09-21 fix
import math
import time
@@ -65,7 +67,7 @@ def ours_inlined(f, a, b, xtol, maxfev):
return a, n, True
if fb == 0.0:
return b, n, True
if fa * fb > 0.0:
if _same_sign(fa, fb):
raise ValueError("f(a) and f(b) must straddle zero")
A, FA, B = a, fa, b
if abs(fa) <= abs(fb):
@@ -132,7 +134,7 @@ def ours_inlined(f, a, b, xtol, maxfev):
xp2, fp2, xp, fp, x, fx = xp, fp, x, fx, xn, fn
if fn == 0.0:
return xn, n, True
if FA * fn < 0.0:
if _opposite(FA, fn):
B = xn
else:
A, FA = xn, fn
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+21 -5
View File
@@ -136,8 +136,9 @@ def label(spec):
if len(spec) > 7 and spec[7] != "distance":
tag = f"{tag}+dissim"
if len(spec) > 9 and spec[9] != "none":
# the pair (off, on) must be distinguishable in a results file
tag = f"{tag}+ext"
# every extrapolation mode must be distinguishable in a results file
tag = f"{tag}+" + {"steps": "ext", "confirmed": "extC", "pattern": "extP",
"steps+results": "extR"}[spec[9]]
if len(spec) > 8 and spec[8] != "none":
# the exponent must be visible in the label: (far, none) is false position and
# (far, order) is the hypothesis, and a results file that cannot tell them apart
@@ -218,6 +219,18 @@ def _preflight_extrap_mode():
raise SystemExit("extrapolate preflight FAILED: refusing to report zeros")
def build_grid_extrap2(smoke):
"""PREREG_extrapolation_gates_2026-09-21: the same cells as build_grid_extrap, every
one in all five modes (none, A, G1, G2, G3)."""
for mode in ("confirmed", "pattern", "steps+results"):
x, _n, ok = pure_root(lambda t: t - 1.0, 0.0, 3.0, 1e-10, 100, similarity="ratio",
scale="derived3", guard="project", extrapolate=mode)
if not (ok and abs(x - 1.0) < 1e-8):
raise SystemExit(f"extrapolate={mode} preflight FAILED: refusing to report zeros")
modes = ("none", "steps", "confirmed", "pattern", "steps+results")
return [s[:9] + (m,) for s in build_grid_extrap(smoke) if s[9] == "none" for m in modes]
def build_grid_extrap(smoke):
"""PREREG 2026-09-21: step extrapolation (A) inside the method, every cell as a pair
(none, steps). Tent with every reading and every guard; all nine functions without
@@ -374,10 +387,12 @@ def main():
ordermode = "--order" in sys.argv # slot 1: the similarity read BY PLACE
decmode = "--decision" in sys.argv # slot 2: the reading in the DECISION place
extmode = "--extrap" in sys.argv # A in the method: step extrapolation
ext2mode = "--extrap2" in sys.argv # gates G1-G3 on the lengthening
combomode = "--combo" in sys.argv # modes A and B together
pointsmode = "--points" in sys.argv # mode A: triple by shape
dirmode = "--direction" in sys.argv # mode B: far end by dissimilarity
specs = (build_grid_extrap(smoke) if extmode else
specs = (build_grid_extrap2(smoke) if ext2mode else
build_grid_extrap(smoke) if extmode else
build_grid_decision(smoke) if decmode else
build_grid_order(smoke) if ordermode else
build_grid_cap(smoke) if capmode else
@@ -402,8 +417,9 @@ def main():
# the published sweep_full.json is what verify_article.py checks the paper
# against, so the log grid never writes into it
if extmode or decmode or ordermode or capmode or combomode or pointsmode or dirmode:
base = ("sweep_extrap" if extmode else
if ext2mode or extmode or decmode or ordermode or capmode or combomode or pointsmode or dirmode:
base = ("sweep_extrap2" if ext2mode else
"sweep_extrap" if extmode else
"sweep_decision" if decmode else
"sweep_order" if ordermode else
"sweep_cap" if capmode else
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+40
View File
@@ -73,6 +73,46 @@ def test_on_a_budget_bound_run_extrapolation_changes_the_path_not_the_count():
assert ev["steps"][1] != ev["none"][1]
GATES = ("confirmed", "pattern", "steps+results")
@pytest.mark.parametrize("mode", GATES)
@pytest.mark.parametrize("f,a,b", SMOOTH + FLAT)
@pytest.mark.parametrize("guard", ("project", "credit"))
def test_every_gate_keeps_the_guarantee(mode, f, a, b, guard):
x, n, ok = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3",
guard=guard, extrapolate=mode)
assert ok and min(a, b) <= x <= max(a, b)
half = math.ceil(math.log2(abs(b - a) / XTOL))
assert n <= (2 * half + 3 if guard == "project" else half + 4), (mode, n)
def test_the_gates_decline_without_history():
"""Each gate needs history to confirm anything; without it the step is untouched."""
from simroot.pure import _extrapolation_s
# opposite directions -> nothing to confirm, in every mode
for mode in ("steps",) + GATES:
assert _extrapolation_s(mode, 0.1, 1.0, 1.5, None, 1.0, 2.0, None, []) == 0.0
# same direction, but G1 has no third point, G3 no third value, G2 no history
assert _extrapolation_s("confirmed", 0.1, 1.0, 0.8, None, 1.0, 2.0, None, []) == 0.0
assert _extrapolation_s("steps+results", 0.1, 1.0, 0.8, None, 1.0, 2.0, None, []) == 0.0
assert _extrapolation_s("pattern", 0.1, 1.0, 0.8, None, 1.0, 2.0, None, [0.2]) == 0.0
def test_results_after_a_crossing_confirm_nothing():
"""G3: a sign change of f between the last results is a crossing, not a creep."""
from simroot.pure import _extrapolation_s
assert _extrapolation_s("steps+results", 0.1, 1.0, 0.9, 0.8, 1.0, -2.0, 4.0, []) == 0.0
assert _extrapolation_s("steps+results", 0.1, 1.0, 0.9, 0.8, 1.0, 2.0, 4.0, []) > 0.0
def test_a_confirmed_geometric_series_is_read_exactly():
"""G1 on three moves in ratio 0.5, 0.5: fully confirmed, s = q2 = 0.5."""
from simroot.pure import _extrapolation_s
s = _extrapolation_s("confirmed", 0.025, 1.0, 0.95, 0.85, 1.0, 2.0, 4.0, [])
assert s == pytest.approx(0.5)
def test_unknown_mode_is_refused():
with pytest.raises(ValueError):
pure_root(lambda t: t, -1.0, 1.0, XTOL, MAXFEV, extrapolate="aitken")
+3 -1
View File
@@ -5,6 +5,8 @@ They are asserted here so a later edit cannot quietly restore the old claim.
"""
from __future__ import annotations
from simroot.baselines import _opposite, _same_sign # 2026-09-21 fix
import math
import os
import sys
@@ -80,7 +82,7 @@ def test_the_tent_never_clips_under_the_derived_scale():
xn = 0.5 * (A + B)
fn = F(xn)
xp2, fp2, xp, fp, x, fx = xp, fp, x, fx, xn, fn
if FA * fn < 0.0:
if _opposite(FA, fn):
B = xn
else:
A, FA = xn, fn
+46
View File
@@ -0,0 +1,46 @@
"""REGRESSION, 2026-09-21: sign tests by product underflowed on flat functions.
`FA * fnew < 0.0` with FA ~ -1e-200 and fnew ~ 1e-150 is -0.0, and '-0.0 < 0.0' is
False: the bracket update moved the wrong end, the root left the bracket, and the run
still reported convergence - on fun6.5 (x^19 over [-10, 100]) the proposed rule returned
6.7e-9 and a lengthening mode 1.27e-6 with a claimed 1e-12 bracket. The same product
test sat in our bisection, false-position family, Ridders, Brent and ITP.
"""
from __future__ import annotations
import pytest
from simroot.baselines import (_opposite, _same_sign, bisection, illinois, itp,
regula_falsi, ridders)
from simroot.pure import pure_root
XTOL, MAXFEV = 1e-12, 200
def test_the_product_underflows_and_the_sign_test_does_not():
u, v = -1e-200, 1e-150
assert u * v == 0.0 # the defect, pinned
assert _opposite(u, v) and not _same_sign(u, v)
assert _same_sign(-1e-200, -1e-150) and not _opposite(-1e-200, -1e-150)
assert not _opposite(0.0, 1.0) and not _same_sign(0.0, 1.0)
def power19(t):
return t ** 19
@pytest.mark.parametrize("guard", ("project", "safe", "credit", "none"))
@pytest.mark.parametrize("extrapolate", ("none", "confirmed"))
def test_the_method_keeps_the_root_in_the_bracket_on_x19(guard, extrapolate):
for sim in ("ratio", "cbrt"):
x, n, ok = pure_root(power19, -10.0, 100.0, XTOL, MAXFEV, similarity=sim,
scale="derived3", guard=guard, extrapolate=extrapolate)
if ok:
assert abs(x) <= XTOL, (sim, guard, extrapolate, x)
@pytest.mark.parametrize("method", (bisection, regula_falsi, illinois, ridders, itp))
def test_the_classical_methods_keep_the_root_on_x19(method):
x, n, ok = method(power19, -10.0, 100.0, XTOL, MAXFEV)
if ok:
assert abs(x) <= XTOL, (method.__name__, x)