baseline: the published sim-numerics artifact as it stands (490 tests, verify_article.py checks all 72 paper numbers against sweep_full.json)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
vvs
2026-09-20 22:38:17 +01:00
co-authored by Claude Opus 5
commit 4a23aacf96
75 changed files with 12175 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
"""Stage 6 - the projection safeguard: guarantee AND order, or neither?
The paper's weak point: the configuration that tops the Alefeld-Potra-Shi
ranking carries no worst-case bound, and the best configuration that does is
14th, 18% behind Brent. Comparing an unguaranteed method's average against
guaranteed ones is not a comparison a reader will accept.
The three earlier safeguards all REPLACE the computed step with a midpoint,
which throws the computation away and, with the three-point scale, collapses
the order from 1.94 to 1.00. The projection guard instead keeps as much of
the step as the bisection budget allows:
r_j = 2^(n_half - j) * (xtol/2) - (b-a)/2
x = x_sim if |x_sim - x_mid| <= r_j
= x_mid + sign(d) * r_j otherwise
Because the bracket after a step is at most (b-a)/2 + r_j = 2^(n_half-j)*eps,
induction bounds the run by n_half steps: the worst case is EXACTLY bisection.
No constant is introduced - ITP's kappa1, kappa2 and n0 all belong to its
truncation stage, which repairs regula falsi and which the similarity step
does not need.
What must be true for this to be worth putting in the paper:
(1) zero runs exceed the bisection bound, on all 199 problems;
(2) the order stays near 1.94, not 1.00;
(3) the total is close to the unguarded configuration's.
Each is checked below. This is the SMOKE run.
"""
from __future__ import annotations
import math
from simroot.baselines import bisection, brent, itp, toms748
from simroot.pure import GUARDS, pure_root
from stage5_three_point import PROBLEMS, order_of
XTOL = 1e-12
MAXFEV = 200
def main():
methods = [
("bisection", lambda f, a, b: bisection(f, a, b, XTOL, MAXFEV)),
("brent", lambda f, a, b: brent(f, a, b, XTOL, MAXFEV)),
("toms748", lambda f, a, b: toms748(f, a, b, XTOL, MAXFEV)),
("itp (3 constants)", lambda f, a, b: itp(f, a, b, XTOL, MAXFEV)),
]
for gu in GUARDS:
methods.append((f"OURS 3pt .{gu}",
(lambda g: lambda f, a, b: pure_root(
f, a, b, XTOL, MAXFEV, scale="derived3",
guard=g))(gu)))
width = max(len(n) for n, _ in methods) + 1
header = (f"{'method':{width}s}" +
"".join(f"{p[0][:13]:>15s}" for p in PROBLEMS) +
f"{'TOTAL':>8s}{'FAIL':>6s}{'>bisect bound':>15s}")
print(header)
print("-" * len(header))
for name, fn in methods:
cells, total, fails, over = [], 0, 0, 0
for _, f, a, b, root in PROBLEMS:
x, n, conv = fn(f, a, b)
ok = conv and abs(x - root) <= 1e-8
cells.append(f"{n:>15d}" if ok else f"{'FAIL(' + str(n) + ')':>15s}")
total += n
fails += 0 if ok else 1
# the bisection bound itself, not twice it: 2 endpoint evaluations
# plus ceil(log2(w/xtol)) steps
if ok and n > math.ceil(math.log2(abs(b - a) / XTOL)) + 2:
over += 1
print(f"{name:{width}s}" + "".join(cells) +
f"{total:>8d}{fails:>6d}{over:>15d}")
print("\nobserved order (median over the eight problems)")
print(f"{'method':22s}{'order':>9s}")
print("-" * 33)
for gu in GUARDS:
ps = [order_of((lambda g: lambda f_, a_, b_, xt, mx: pure_root(
f_, a_, b_, XTOL, MAXFEV, scale="derived3", guard=g))(gu),
f, a, b, root) for _, f, a, b, root in PROBLEMS]
ps = [p for p in ps if math.isfinite(p) and 0.5 < p < 5.0]
med = sorted(ps)[len(ps) // 2] if ps else float("nan")
print(f"{'OURS 3pt .' + gu:22s}{med:>9.3f}")
if __name__ == "__main__":
main()