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:
@@ -0,0 +1,177 @@
|
||||
"""Pins the corrections the revision made.
|
||||
|
||||
Each of these replaced a statement in the paper that was wrong or too broad.
|
||||
They are asserted here so a later edit cannot quietly restore the old claim.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from simroot.pure import PURE_SIMS, pure_root
|
||||
from stage5_three_point import PROBLEMS, order_of
|
||||
|
||||
R2 = math.sqrt(2.0)
|
||||
F = lambda x: x * x - 2.0
|
||||
XTOL, MAXFEV = 1e-12, 200
|
||||
|
||||
|
||||
def iterate(S, n, sigma=2.0, x0=0.0, xfar=2.0):
|
||||
x, errs = x0, []
|
||||
for _ in range(n):
|
||||
x = x + (1.0 - S(F(x) / sigma)) * (xfar - x)
|
||||
errs.append(abs(x - R2))
|
||||
return errs
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# the naming contradiction: the proposed similarity is the tent, and its
|
||||
# degeneracy is a property of the CHOSEN scale only
|
||||
# --------------------------------------------------------------------------
|
||||
def test_the_tent_is_captured_under_the_chosen_scale():
|
||||
"""Table 1's last row. Not a slow rate - capture at the far end on the
|
||||
first step."""
|
||||
e = iterate(PURE_SIMS["ratio"], 200)
|
||||
assert abs(e[0] - e[-1]) < 1e-15, (e[0], e[-1])
|
||||
assert abs(e[0] - 0.5858) < 1e-3, e[0]
|
||||
|
||||
|
||||
def test_the_tent_is_the_best_similarity_under_the_derived_scale():
|
||||
"""The same function, the other scale. If these two tests ever agree in
|
||||
sign, the paper's central thesis - that the content is in the scale - has
|
||||
been broken."""
|
||||
tot = {}
|
||||
for sim in PURE_SIMS:
|
||||
s = 0
|
||||
for _n, f, a, b, root in PROBLEMS:
|
||||
x, n, conv = pure_root(f, a, b, XTOL, MAXFEV, similarity=sim,
|
||||
scale="derived3", guard="project")
|
||||
s += n if (conv and abs(x - root) <= 1e-8) else MAXFEV
|
||||
tot[sim] = s
|
||||
assert min(tot, key=lambda k: tot[k]) == "ratio", tot
|
||||
|
||||
|
||||
def test_the_tent_never_clips_under_the_derived_scale():
|
||||
"""Why the degeneracy disappears: |f|/sigma stays below 1, so the
|
||||
truncation in max(0, 1-|z|) never engages."""
|
||||
from simroot.pure import _slope3
|
||||
a, b = 0.0, 2.0
|
||||
fa, fb = F(a), F(b)
|
||||
A, FA, B = a, fa, b
|
||||
x, fx, xp, fp = a, fa, b, fb
|
||||
xp2 = fp2 = None
|
||||
clipped = steps = 0
|
||||
for _ in range(40):
|
||||
if abs(B - A) <= XTOL:
|
||||
break
|
||||
far = B if abs(B - x) >= abs(A - x) else A
|
||||
sc = abs(_slope3(x, fx, xp, fp, xp2, fp2)) * abs(far - x)
|
||||
if sc == 0.0:
|
||||
break
|
||||
steps += 1
|
||||
clipped += abs(fx) / sc >= 1.0
|
||||
xn = x + min(1.0, abs(fx) / sc) * (far - x)
|
||||
if not (min(A, B) < xn < max(A, B)):
|
||||
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:
|
||||
B = xn
|
||||
else:
|
||||
A, FA = xn, fn
|
||||
assert steps > 0
|
||||
assert clipped == 0, f"{clipped} of {steps} steps clipped"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Proposition 1 is specific; Proposition 1' locates the boundary
|
||||
# --------------------------------------------------------------------------
|
||||
def test_only_the_ratio_form_reproduces_regula_falsi():
|
||||
a, b = 1.0, 4.0
|
||||
fa, fb = F(a), F(b)
|
||||
falsi = (a * fb - b * fa) / (fb - fa)
|
||||
matches = []
|
||||
for name, S in (("rational", PURE_SIMS["rational"]),
|
||||
("exp_abs", PURE_SIMS["exp_abs"]),
|
||||
("rat_sq", PURE_SIMS["rat_sq"]),
|
||||
("cosine", PURE_SIMS["cosine"]),
|
||||
("ratio", PURE_SIMS["ratio"])):
|
||||
pt = a + (1.0 - S(F(a) / abs(fb))) * (b - a)
|
||||
if abs(pt - falsi) < 1e-12:
|
||||
matches.append(name)
|
||||
assert matches == ["rational"], matches
|
||||
|
||||
|
||||
def test_the_bracket_end_difference_gives_regula_falsi_exactly():
|
||||
"""Proposition 1'. This is why 'most recent' in the definition of the
|
||||
divided difference is load-bearing."""
|
||||
a, b = 1.0, 4.0
|
||||
fa, fb = F(a), F(b)
|
||||
falsi = (a * fb - b * fa) / (fb - fa)
|
||||
sigma = abs((fb - fa) / (b - a)) * abs(b - a)
|
||||
pt = a + min(1.0, abs(fa) / sigma) * (b - a)
|
||||
assert pt == falsi
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Table 1's c = 0 rows: a sublinear law with a predicted coefficient
|
||||
# --------------------------------------------------------------------------
|
||||
@pytest.mark.parametrize("sim,want", [("rat_sq", 0.8536), ("exp_sq", 0.8536),
|
||||
("cosine", 1.7071)])
|
||||
def test_the_quadratic_similarities_are_sublinear_with_a_predicted_constant(sim, want):
|
||||
S = PURE_SIMS[sim]
|
||||
h = 1e-4
|
||||
c2 = (1.0 - S(h)) / (h * h)
|
||||
fp, D, sigma = 2.0 * R2, 2.0 - R2, 2.0
|
||||
predicted = sigma * sigma / (c2 * fp * fp * D)
|
||||
assert abs(predicted - want) < 1e-3, predicted
|
||||
e = iterate(S, 4001)
|
||||
assert abs(4000 * e[4000] - predicted) < 0.02, (4000 * e[4000], predicted)
|
||||
|
||||
|
||||
def test_there_is_no_rate_to_measure_for_those_rows():
|
||||
"""The old table reported 0.93-0.94 as a 'measured rate'; the ratio
|
||||
creeps to 1, so that number was the window's end, not a rate."""
|
||||
e = iterate(PURE_SIMS["rat_sq"], 4001)
|
||||
ratios = [e[n] / e[n - 1] for n in (10, 100, 1000, 4000)]
|
||||
assert ratios == sorted(ratios), ratios
|
||||
assert ratios[0] < 0.94 < ratios[-1]
|
||||
assert ratios[-1] > 0.999
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# the order is 2, not Muller's 1.84
|
||||
# --------------------------------------------------------------------------
|
||||
def test_the_three_point_order_is_two():
|
||||
ps = [order_of(lambda f_, a_, b_, xt, mx: pure_root(
|
||||
f_, a_, b_, XTOL, MAXFEV, scale="derived3", guard="project"),
|
||||
f, a, b, root) for _n, 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]
|
||||
assert abs(med - 2.0) < 0.1, med
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# the two evaluation-count conventions must both be reported and must differ
|
||||
# in the direction the paper says
|
||||
# --------------------------------------------------------------------------
|
||||
@pytest.mark.skipif(not os.path.exists(os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||
"sweep_full.json")), reason="sweep not present")
|
||||
def test_charged_totals_are_what_section_5_reports():
|
||||
import json
|
||||
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
with open(os.path.join(root, "sweep_full.json"), encoding="utf8") as fh:
|
||||
rows = {r["label"]: r for r in json.load(fh)}
|
||||
# brent is now run at the better of ours and scipy.brentq per problem,
|
||||
# which is why it is 6988 rather than the 6991 of our implementation alone
|
||||
for lbl, want in (("pure.ratio.derived3.project", 6026),
|
||||
("brent", 6988), ("bisection", 10620),
|
||||
("chandrupatla", 5208)):
|
||||
got = rows[lbl]["APS"]["total"] + rows[lbl]["CHA"]["total"]
|
||||
assert got == want, (lbl, got, want)
|
||||
Reference in New Issue
Block a user