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:
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user