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>
183 lines
7.2 KiB
Python
183 lines
7.2 KiB
Python
"""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
|
|
|
|
from simroot.baselines import _opposite, _same_sign # 2026-09-21 fix
|
|
|
|
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 _opposite(FA, fn):
|
|
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)}
|
|
# since 2026-09-21 the competitors are the library alone: brent is
|
|
# scipy.brentq (7004; our pure-Python implementation, timed in section 5.2,
|
|
# is 6991) and chandrupatla is scipy's elementwise.find_root (5228; the
|
|
# earlier 5208 came from a per-problem best of ours and the library, with
|
|
# ours stopping on a bracket twice as wide)
|
|
for lbl, want in (("pure.ratio.derived3.project", 6026),
|
|
("brent", 7004), ("bisection", 10620),
|
|
("chandrupatla", 5228)):
|
|
got = rows[lbl]["APS"]["total"] + rows[lbl]["CHA"]["total"]
|
|
assert got == want, (lbl, got, want)
|