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>
This commit is contained in:
vvs
2026-09-22 07:01:13 +01:00
co-authored by Claude Opus 5
parent 0e05c92a1b
commit eeb9102d47
11 changed files with 634 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
"""Two checks of the equivalence test itself.
1. Mutation: against a reference with ONE component removed the comparison must find
differences - otherwise a passing equivalence test would prove nothing.
2. Speed: wall time of the compiled library against the Python reference over all problems.
PYTHONPATH=<wheel dir>;. python check_simbracket.py
"""
import time
import simbracket
from simroot.pure import pure_root
from sweep_full import APRIME
from tests.test_simbracket_equivalence import PROBS, reference, run
def main():
for drop in ("close", "sub", "half", "contra", "exp"):
pre = "+".join(p for p in APRIME.split("+") if p != drop)
mut = lambda f, a, b, xt, mf, pre=pre: pure_root( # noqa: E731
f, a, b, xt, mf, similarity="ratio", scale="derived3", guard="project",
prestep=pre, extrapolate="pattern")
diff = sum(run(mut, f, a, b, 1e-12, 200) != run(simbracket.find_root, f, a, b, 1e-12, 200)
for _p, f, a, b in PROBS)
print(f"mutation -{drop:<7}: {diff} of {len(PROBS)} problems differ (must be > 0)")
for name, m in (("reference (Python)", reference), ("simbracket (compiled)", simbracket.find_root)):
t0 = time.perf_counter()
for _ in range(3):
for _p, f, a, b in PROBS:
run(m, f, a, b, 1e-12, 200)
print(f"{name:<22} {(time.perf_counter() - t0) / 3:.3f} s per pass over {len(PROBS)}")
if __name__ == "__main__":
main()