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
+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