Files
sim-numerics/tests/test_simbracket_equivalence.py
T
vvsandClaude Opus 5 eeb9102d47 simbracket: A' as a compiled library (Cython, abi3 wheels for Windows and Linux, Python >= 3.10).
lib/simbracket/_core.pyx ports exactly the A' path of simroot.pure.pure_root (tent similarity,
three-point scale, projection c=2, contra+flatanalogmempt+exp+half+close+sub, pattern) with
Python's semantics kept where they differ from C: min/max keep the first argument on ties and
NaN, float division by zero raises, math.log raises at 0, and math.hypot and the builtin sum
(compensated since 3.12) are called rather than re-coded. find_root(f, a, b, xtol, maxfev) ->
(x, nfev, converged); evaluation_bound(a, b, xtol) = 2*ceil(log2(|b-a|/xtol)) + 3.

tests/test_simbracket_equivalence.py: bit-identical x and evaluation count against pure_root on
all 868 problems of the six corpora (the 91 Roots.jl problems in full), at xtol 1e-12 and 1e-8
and with the budget cut to 12; errors match; the loaded module is the compiled one. 7/7 on
Windows and on Linux. check_simbracket.py: removing any one component from the reference
makes 36 to 455 problems differ (the test discriminates); the compiled form is 4x faster.

The wheel carries only the compiled extension; _core.c is generated (gen_c.py) and ignored.
Regression on the server x2: 1232 passed, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 07:01:13 +01:00

90 lines
3.2 KiB
Python

"""The compiled library simbracket must BE the proposed method A': the same iterate and the
same evaluation count as simroot.pure.pure_root, bit for bit, on every problem of the six
corpora (all 91 Roots.jl problems, not only the 78), at the paper's tolerance, at a looser
one, and with the budget cut so that the exhaustion path is exercised too.
"""
from __future__ import annotations
import math
import random
import pytest
simbracket = pytest.importorskip("simbracket")
from simroot.pure import pure_root # noqa: E402
from sweep_full import APRIME # noqa: E402
def reference(f, a, b, xtol, maxfev):
return pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived3",
guard="project", prestep=APRIME, extrapolate="pattern")
def problems():
from corpus_applied import build as b_app
from corpus_fourth import build as b_four
from corpus_new import build as b_new
from corpus_roots_jl import build as b_rjl
from sweep_full import load_problems
out = [(f"{s}:{pid}", f, a, b) for s, pid, f, a, b, _r in load_problems()]
out += [(f"NEW:{pid}", f, a, b) for _c, pid, f, a, b, _r in b_new()[0]]
out += [(f"RJL:{pid}", f, a, b) for pid, f, a, b, _ins in b_rjl()[0]]
out += [(f"APP:{pid}", f, a, b) for pid, f, a, b in b_app()[0]]
out += [(f"D:{pid}", f, a, b) for _c, pid, f, a, b, _r in b_four()[0]]
return out
def run(method, f, a, b, xtol, maxfev):
import scipy.optimize._tstutils as T
T._f6_cache.clear()
random.seed(20260921)
try:
x, n, ok = method(f, a, b, xtol, maxfev)
return ("ok", float(x).hex() if not math.isnan(x) else "nan", int(n), bool(ok))
except Exception as e: # noqa: BLE001 - the exception TYPE is part of the behaviour
return ("raise", type(e).__name__)
PROBS = problems()
def test_the_compiled_module_is_the_one_under_test():
import simbracket._core as core
assert core.__file__.endswith((".pyd", ".so")), core.__file__
def test_the_corpora_are_all_there():
# the 855 problems of the six corpora plus the 13 Roots.jl problems that overlap the
# development sets
assert len(PROBS) == 855 + 13
assert len({p[0] for p in PROBS}) == len(PROBS)
@pytest.mark.parametrize("xtol,maxfev", ((1e-12, 200), (1e-8, 200), (1e-12, 12)))
def test_bit_identical_to_the_reference_on_every_problem(xtol, maxfev):
bad = []
for pid, f, a, b in PROBS:
r = run(reference, f, a, b, xtol, maxfev)
c = run(simbracket.find_root, f, a, b, xtol, maxfev)
if r != c:
bad.append((pid, r, c))
assert not bad, f"{len(bad)} differ, first: {bad[:5]}"
def test_errors_match_the_reference():
f = lambda t: t - 0.3 # noqa: E731
for args in ((f, 0.5, 1.0, 1e-12, 200), (f, 0.0, 1.0, 0.0, 200)):
with pytest.raises(ValueError):
reference(*args)
with pytest.raises(ValueError):
simbracket.find_root(*args)
def test_the_bound_is_kept_and_reported():
f = lambda t: t ** 19 # noqa: E731
x, n, ok = simbracket.find_root(f, -10.0, 100.0, 1e-12, 500)
assert ok and n <= simbracket.evaluation_bound(-10.0, 100.0, 1e-12)
assert simbracket.evaluation_bound(-10.0, 100.0, 1e-12) == \
2 * math.ceil(math.log2(110.0 / 1e-12)) + 3