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
+150
View File
@@ -0,0 +1,150 @@
"""Tests for the ANALYSIS code in stage4_why.
The conclusions about why the method wins and loses rest entirely on three
pieces of machinery here - the phase boundary, the bracket replay, and the
observed order - plus the `stats` instrumentation added to brent and
pure_root. None of it was covered, which made the explanation the least
verified part of the project.
"""
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.baselines import bisection, brent
from simroot.pure import GUARDS, pure_root
from stage4_why import (observed_order, ours, phase_boundary, phases,
replay_bracket, traced)
XTOL = 1e-12
MAXFEV = 200
# --------------------------------------------------------------------------
# the phase boundary
# --------------------------------------------------------------------------
@pytest.mark.parametrize("width", [1.0, 5.0, 41.0, 1e4, 1e8, 1e12])
def test_phase_boundary_splits_bisection_evenly(width):
"""The boundary is defined as the log-midpoint of the journey, so
bisection - which halves at a constant rate - must spend the same number
of evaluations either side of it, on a bracket of any width. If this
fails, the approach/endgame table means nothing."""
a, b = 0.0, width
root = width * 0.3 # away from the midpoint, so nothing is by luck
f = lambda x: x - root
app, end, tot = phases(bisection, f, a, b, root)
assert app + end == tot
assert abs(app - end) <= 4, f"width {width}: {app} vs {end}"
def test_phase_boundary_is_the_geometric_mean_of_width_and_attainable_tol():
# near the origin the attainable tolerance is xtol itself
assert phase_boundary(1e12, 0.0, 1e-12) == pytest.approx(1.0)
assert phase_boundary(4.0, 0.0, 1e-12) == pytest.approx(2e-6)
# far from it, floating-point spacing is the real floor
assert phase_boundary(1e12, 1e12, 1e-12) > 1e3
# --------------------------------------------------------------------------
# the bracket replay
# --------------------------------------------------------------------------
def test_replay_scores_bisection_at_exactly_one_half():
"""End-to-end check of the reconstruction: every bisection probe sits at
the midpoint and removes exactly half, by construction. Any error in the
replay shows up here immediately."""
f = lambda x: x * x - 2.0
g, xs = traced(f)
bisection(g, 0.0, 2.0, 1e-9, MAXFEV)
pos, shrinks, skipped = replay_bracket(f, 0.0, 2.0, xs[2:])
assert skipped == 0
assert pos, "no probes replayed"
for t in pos:
assert t == pytest.approx(0.5, abs=1e-12)
for s in shrinks:
assert s == pytest.approx(0.5, abs=1e-12)
def test_replay_counts_probes_outside_the_bracket():
"""A probe outside the proven bracket must be counted as skipped, not
silently dropped - dropping them flatters whoever probes outside."""
f = lambda x: x - 1.0
# 5.0 is outside [0, 2]; 1.5 and 0.5 are inside
pos, shrinks, skipped = replay_bracket(f, 0.0, 2.0, [1.5, 5.0, 0.5])
assert skipped == 1
assert len(pos) == len(shrinks) == 2
def test_replay_shrinks_are_fractions():
f = lambda x: math.cos(x) - x
g, xs = traced(f)
ours(g, 0.0, 1.0, XTOL, MAXFEV)
_pos, shrinks, _sk = replay_bracket(f, 0.0, 1.0, xs[2:])
assert shrinks
for s in shrinks:
assert 0.0 <= s <= 1.0
# --------------------------------------------------------------------------
# the observed order
# --------------------------------------------------------------------------
def test_observed_order_recovers_the_known_orders():
"""The whole explanation of the endgame loss is the measured order. It
must reproduce the textbook values for methods whose order is known."""
f = lambda x: x * x - 2.0
root = math.sqrt(2.0)
p_ours = observed_order(ours, f, 0.0, 2.0, root)
p_brent = observed_order(brent, f, 0.0, 2.0, root)
p_bis = observed_order(bisection, f, 0.0, 2.0, root)
assert 1.3 < p_ours < 1.9, p_ours # secant, 1.618
assert p_brent > p_ours # inverse quadratic, 1.839
assert p_bis < 1.3, p_bis # linear
# --------------------------------------------------------------------------
# the instrumentation must not perturb what it measures
# --------------------------------------------------------------------------
PROBS = [
(lambda x: x * x - 2.0, 0.0, 2.0),
(lambda x: math.exp(-x) * (x - 1.0), -1.0, 40.0),
(lambda x: x ** 10 - 1.0, 0.0, 1.3),
]
@pytest.mark.parametrize("f,a,b", PROBS)
def test_brent_stats_do_not_change_brent(f, a, b):
plain = brent(f, a, b, XTOL, MAXFEV)
st = {}
withstats = brent(f, a, b, XTOL, MAXFEV, stats=st)
assert plain == withstats
assert 0 <= st["bisect"] <= plain[1]
@pytest.mark.parametrize("guard", GUARDS)
@pytest.mark.parametrize("f,a,b", PROBS)
def test_pure_stats_do_not_change_pure_root(f, a, b, guard):
plain = pure_root(f, a, b, XTOL, MAXFEV, guard=guard)
st = {}
withstats = pure_root(f, a, b, XTOL, MAXFEV, guard=guard, stats=st)
assert plain == withstats
assert 0 <= st["forced"] <= plain[1]
@pytest.mark.parametrize("f,a,b", PROBS)
def test_an_unguarded_run_forces_nothing(f, a, b):
st = {}
pure_root(f, a, b, XTOL, MAXFEV, guard="none", stats=st)
assert st["forced"] == 0
def test_the_bracket_guard_actually_fires_where_it_is_needed():
"""The flat problem is the one that defeats every interpolation-only
rule; the safeguard must be doing visible work there."""
f = lambda x: math.exp(-x) * (x - 1.0)
st = {}
pure_root(f, -1.0, 40.0, XTOL, MAXFEV, guard="bracket", stats=st)
assert st["forced"] > 0
+91
View File
@@ -0,0 +1,91 @@
"""Active noise control: the step size of an NLMS canceller.
The third domain, and the third instance of the same pattern. What is pinned
here is the pattern and the mistake, not a victory.
"""
from __future__ import annotations
import math
import os
import sys
import numpy as np
import pytest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from anc.bench import SCENARIOS, make_primary_path, make_secondary_path, run_nlms
from anc.rules import Fixed, InSampleMeasured, KwongJohnston, Measured
N, SEEDS = 8000, 4
SNRS = (30.0, 15.0)
def sweep(rule, snrs=SNRS):
"""Both noise levels, as the experiment uses.
Measuring at 30 dB alone is the wrong test and was: with almost no
measurement noise there is nothing for a safety factor to hedge against,
the exact step mu = 1 is near optimal, and any hedging costs a little.
The whole point of the rule appears at 15 dB. A claim about the grid has
to be tested on the grid."""
vals = []
for snr in snrs:
for s in range(SEEDS):
for _sname, gen in SCENARIOS:
rng = np.random.default_rng(1000 + s)
noise = gen(N, rng)
nr = run_nlms(noise, make_primary_path(rng),
make_secondary_path(rng), rule, snr_db=snr,
seed=s)
if math.isfinite(nr):
vals.append(nr)
return sum(vals) / len(vals), min(vals), len(vals)
def test_the_in_sample_observable_is_uninformative():
"""The mistake, pinned. NLMS with mu = 1 drives the a posteriori error of
the current sample to exactly zero, so a rule that measures it reads 'the
model was perfect' and proposes mu = 1 for ever. It is therefore the
fixed mu = 1 rule under another name, and the numbers say so."""
a, _lo, _n = sweep(InSampleMeasured())
b, _lo2, _n2 = sweep(Fixed(1.0))
assert abs(a - b) < 0.1, (a, b)
def test_measuring_out_of_sample_is_better_than_measuring_in_sample():
"""The fix: what mu hedges against is the part of the error the filter
cannot cancel, and that is observable as the floor the error keeps
hitting - not as the residual of the sample just fitted."""
good, _lo, _n = sweep(Measured())
bad, _lo2, _n2 = sweep(InSampleMeasured())
assert good > bad, (good, bad)
def test_the_measured_rule_beats_the_four_constant_scheme():
"""Kwong-Johnston is the classical variable-step-size rule and carries
four constants. This is the comparison the transfer is about."""
ours, _lo, _n = sweep(Measured())
kj, _lo2, _n2 = sweep(KwongJohnston())
assert ours > kj + 3.0, (ours, kj)
def test_and_loses_to_one_well_chosen_constant():
"""The other half, and the same in all three domains: measuring the
constant beats the elaborate multi-constant schemes and does not beat a
single constant chosen correctly for the benchmark."""
ours, _lo, _n = sweep(Measured())
best_fixed, _lo2, _n2 = sweep(Fixed(0.5))
assert best_fixed > ours, (best_fixed, ours)
assert best_fixed - ours < 3.0, (best_fixed, ours)
def test_no_rule_diverges():
"""A step size that is too large makes the filter blow up; none of these
may."""
for rule in (Fixed(0.1), Fixed(0.5), Fixed(1.0), KwongJohnston(),
Measured()):
_m, _lo, n = sweep(rule)
assert n == SEEDS * len(SCENARIOS) * len(SNRS), (rule.name, n)
+89
View File
@@ -0,0 +1,89 @@
"""The paper's numbers are part of the regression.
A table transcribed by hand drifts from its data the first time the data is
regenerated. verify_article.py asserts all 72 stated numbers against
sweep_full.json; this makes that check part of the suite, so a re-run of the
sweep that moves a number fails the build instead of quietly invalidating the
paper.
"""
from __future__ import annotations
import os
import subprocess
import sys
import pytest
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# the other end of the trade the paper states: better average, one constant
PROJ_LABEL = "pure.ratio.derived3.project"
@pytest.mark.skipif(not os.path.exists(os.path.join(ROOT, "sweep_full.json")),
reason="sweep_full.json not present; run sweep_full.py")
def test_every_number_in_the_paper_matches_the_sweep():
r = subprocess.run([sys.executable, "verify_article.py"], cwd=ROOT,
capture_output=True, text=True, timeout=600)
tail = "\n".join(r.stdout.strip().splitlines()[-25:])
assert r.returncode == 0, f"the paper disagrees with the data:\n{tail}"
assert "every number stated in the paper matches" in r.stdout
assert r.stdout.count(" OK ") >= 72, r.stdout.count(" OK ")
@pytest.mark.skipif(not os.path.exists(os.path.join(ROOT, "sweep_full.json")),
reason="sweep_full.json not present")
def test_the_headline_claim_is_what_the_paper_says_it_is():
"""Guard against the one mistake that would matter most: the abstract
claiming a place the data does not support.
It caught exactly that. Until Chandrupatla's own method was added - a
reader asked why the owner of half the benchmark was missing - this test
asserted that a configuration of ours came first, and it passed only
because the method that beats us was absent from the field. The claim it
now guards is the corrected one: we are not first, Chandrupatla is, and
our margin behind it on the canonical set is under one percent."""
import json
from report_sweep import PROPOSED, rank
with open(os.path.join(ROOT, "sweep_full.json"), encoding="utf8") as fh:
rows = json.load(fh)
by = {r["label"]: r for r in rows}
assert "chandrupatla" in by, "the sweep predates the missing baselines"
aps = rank(rows, "APS")
assert aps[0]["label"] == "chandrupatla", aps[0]["label"]
def both(lbl):
return by[lbl]["APS"]["total"] + by[lbl]["CHA"]["total"]
# What the paper may claim for the credit method, and nothing more.
# It is NOT within a percent of the best on Alefeld-Potra-Shi - that was
# true of the projection variant, which carries a constant. Buying
# constant-freedom and a near-bisection worst case costs average
# performance, and the test pins the direction of that trade so the
# abstract cannot quietly reclaim the projection variant's numbers.
assert both(PROPOSED) > both("chandrupatla"), "behind Chandrupatla"
for weaker in ("toms748", "brent", "itp", "ridders", "bisection"):
assert both(PROPOSED) < both(weaker), weaker
# On the canonical set alone it is behind the classical methods; saying
# otherwise would be quoting the projection variant.
assert by[PROPOSED]["APS"]["total"] > by["toms748"]["APS"]["total"]
assert by[PROPOSED]["APS"]["total"] > by["brent"]["APS"]["total"]
# The claim it does own: the worst single problem, over the problems it
# solves, is close to bisection's and far below the classical methods'.
def worst(lbl):
w = 0
for setname in ("APS", "CHA"):
for n, ok in by[lbl][setname]["per"].values():
if ok:
w = max(w, n)
return w
assert worst(PROPOSED) < 100, worst(PROPOSED)
assert worst(PROPOSED) < worst("brent")
assert worst(PROPOSED) < worst("toms748")
assert worst(PROPOSED) < worst(PROJ_LABEL), (worst(PROPOSED),
worst(PROJ_LABEL))
+90
View File
@@ -0,0 +1,90 @@
"""Grafting similarity onto existing methods: where it works and where not.
The project's one clear success was a graft - the credit safeguard turned
regula falsi, the secant rule and inverse quadratic interpolation into methods
that solve 193 of 199 with a near-bisection worst case. This file pins the
graft that FAILED, and the boundary the two of them mark out.
"""
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.baselines import brent, chandrupatla
from stage16_blend import brent_blend, chandrupatla_blend, load, measure
PROBS = load()
def both(m):
t, ok, worst = measure(m, PROBS)
return t["APS"] + t["CHA"], ok["APS"] + ok["CHA"], worst
def test_the_controls_reproduce_the_published_methods_exactly():
"""Without this the experiment is void: a blend must be compared against
the same code with the blend switched off, not against a different
implementation."""
pub_b = both(lambda f, a, b, xt, mx: brent(f, a, b, xt, mx))
off_b = both(lambda f, a, b, xt, mx: brent_blend(f, a, b, xt, mx,
mode="off"))
assert pub_b == off_b, (pub_b, off_b)
pub_c = both(lambda f, a, b, xt, mx: chandrupatla(f, a, b, xt, mx))
off_c = both(lambda f, a, b, xt, mx: chandrupatla_blend(f, a, b, xt, mx,
mode="off"))
assert pub_c == off_c, (pub_c, off_c)
def test_softening_brents_acceptance_test_makes_it_worse():
base, _s, _w = both(lambda f, a, b, xt, mx: brent(f, a, b, xt, mx))
blend, _s2, _w2 = both(lambda f, a, b, xt, mx: brent_blend(f, a, b, xt, mx,
mode="blend"))
assert blend > 1.2 * base, (base, blend)
def test_softening_chandrupatlas_acceptance_test_makes_it_worse():
base, s_base, _w = both(lambda f, a, b, xt, mx: chandrupatla(f, a, b, xt,
mx))
blend, s_blend, _w2 = both(
lambda f, a, b, xt, mx: chandrupatla_blend(f, a, b, xt, mx,
mode="blend"))
assert blend > 1.2 * base, (base, blend)
assert s_blend < s_base, (s_base, s_blend) # and it breaks problems
def test_projecting_onto_brents_bound_also_fails():
"""The move that worked inside our own method - project, do not replace -
does not transfer to somebody else's acceptance condition. Clamping to
the boundary produces the most aggressive still-permitted step, which is
exactly where the interpolation has stopped carrying information."""
base, s_base, _w = both(lambda f, a, b, xt, mx: brent(f, a, b, xt, mx))
proj, s_proj, _w2 = both(lambda f, a, b, xt, mx: brent_blend(
f, a, b, xt, mx, mode="project"))
assert proj > base, (base, proj)
assert s_proj < s_base, (s_base, s_proj)
def test_the_boundary_this_marks_out():
"""Three failures against one success, and they differ in kind.
The safeguard graft succeeded where the quantity being controlled is a
GRADED QUALITY - how much margin to leave, how bold a step to take. The
blend graft failed where the quantity is a VALIDITY CONDITION - whether
the interpolation model applies at all. Near such a boundary a point is
not partly good; it is where the model has just stopped holding, which is
why Brent and Chandrupatla discard it rather than shrink it."""
from stage14_is_anything_reusable import RULES, load as load14, measure as m14
p14 = load14()
for name, rule in RULES:
t_raw, s_raw, _w = m14(rule, False, p14)
t_g, s_g, _w2 = m14(rule, True, p14)
assert s_g > s_raw and t_g < t_raw, name # the graft that works
base, _s, _w = both(lambda f, a, b, xt, mx: brent(f, a, b, xt, mx))
blend, _s2, _w2 = both(lambda f, a, b, xt, mx: brent_blend(
f, a, b, xt, mx, mode="blend"))
assert blend > base # the graft that does not
+161
View File
@@ -0,0 +1,161 @@
"""The one claim that survives the objection a reader raises first.
Everything else in this project is a tie, a loss, or a negative result. This
file pins the exception and, just as important, pins its scope: it is a claim
about REPAIRING regula falsi, not about being the best root finder.
"""
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.baselines import (anderson_bjorck, brent, chandrupatla, illinois,
pegasus, regula_falsi)
from stage18_bulletproof import falsi_driver, load_roots
XTOL, MAXFEV, ACC = 1e-12, 200, 1e-6
PROBS = load_roots()
REPAIRS = ("none", "illinois", "pegasus", "anderson", "credit")
def score(method):
total = solved = worst = 0
for f, a, b, root in PROBS:
try:
x, n, conv = method(f, a, b, XTOL, MAXFEV)
except Exception:
x, n, conv = float("nan"), MAXFEV, False
ok = bool(conv and math.isfinite(x) and abs(x - root) <= ACC)
total += n if ok else MAXFEV
solved += ok
if ok:
worst = max(worst, n)
return total, solved, worst
def by_repair(repair):
return score(lambda f, a, b, xt, mx: falsi_driver(f, a, b, xt, mx, repair))
# --------------------------------------------------------------------------
# the driver cannot be handicapping the competitors
# --------------------------------------------------------------------------
@pytest.mark.parametrize("repair,independent", [
("none", regula_falsi), ("illinois", illinois), ("pegasus", pegasus),
("anderson", anderson_bjorck)])
def test_two_independent_implementations_agree_exactly(repair, independent):
"""The classical repairs exist twice in this repository, written at
different times against different structures: `_false_position_family` in
simroot.baselines, and the pluggable driver in stage18. They agree to the
evaluation, which is what removes 'your driver favours your repair' from
the table."""
a = score(independent)
b = by_repair(repair)
assert a[:2] == b[:2], (repair, a, b)
# --------------------------------------------------------------------------
# the claim
# --------------------------------------------------------------------------
def test_credit_beats_every_classical_repair_on_both_axes_at_once():
"""Same interpolation, same driver, same budget; only the repair differs.
Against Illinois (1971), Pegasus (1972) and Anderson-Bjorck (1973)."""
c_total, c_solved, c_worst = by_repair("credit")
for repair in ("illinois", "pegasus", "anderson"):
t, s, w = by_repair(repair)
assert c_solved > s, (repair, c_solved, s)
assert c_worst < w, (repair, c_worst, w)
assert c_total < min(by_repair(r)[0] for r in ("illinois", "pegasus",
"anderson"))
def test_no_classical_repair_gets_near_the_solved_count():
"""193 of 199 is what bisection, Chandrupatla and TOMS 748 achieve; the
best classical repair of regula falsi reaches 176."""
_t, c_solved, _w = by_repair("credit")
assert c_solved == 193, c_solved
for repair in ("none", "illinois", "pegasus", "anderson"):
assert by_repair(repair)[1] <= 176, repair
def test_the_advantage_is_not_an_artefact_of_aggregating_two_sets():
"""A reader will ask whether the win comes from one set. On the
wide-bracket set the credit repair halves the cost and doubles the solved
count; on the canonical set it matches Illinois's solved count at lower
cost. Anderson-Bjorck is cheaper there, and fails eight problems the
credit repair solves - that trade is stated, not hidden."""
from scipy.optimize._tstutils import (_APS_TESTS_DICTS,
_CHANDRUPATLA_TESTS_DICTS)
def per_set(repair, dicts, chand):
total = solved = 0
for d in dicts:
a, b = float(d["bracket"][0]), float(d["bracket"][1])
args = () if chand else d.get("args", ())
f = (lambda ff, aa: lambda t: ff(t, *aa))(d["f"], args)
try:
x, n, conv = falsi_driver(f, a, b, XTOL, MAXFEV, repair)
except Exception:
x, n, conv = float("nan"), MAXFEV, False
ok = bool(conv and math.isfinite(x)
and abs(x - float(d["root"])) <= ACC)
total += n if ok else MAXFEV
solved += ok
return total, solved
for repair in ("illinois", "pegasus", "anderson"):
c_t, c_s = per_set("credit", _CHANDRUPATLA_TESTS_DICTS, True)
t, s = per_set(repair, _CHANDRUPATLA_TESTS_DICTS, True)
assert c_t < 0.7 * t, (repair, c_t, t) # halves the cost
assert c_s >= 1.7 * s, (repair, c_s, s) # doubles the solved
def test_the_scope_of_the_claim_is_repairs_not_the_field():
"""The credit repair of regula falsi is still beaten by Brent and by
Chandrupatla. The claim must never be stated as 'the best root finder'."""
c_total, _s, _w = by_repair("credit")
for better in (brent, chandrupatla):
t, _s2, _w2 = score(lambda f, a, b, xt, mx, _m=better: _m(f, a, b, xt,
mx))
assert t < c_total, (better.__name__, t, c_total)
# --------------------------------------------------------------------------
# the claim that does NOT survive, pinned so it is not overstated
# --------------------------------------------------------------------------
def test_the_anc_claim_is_not_uniform_and_must_be_stated_as_such():
"""Against a Kwong-Johnston oracle tuned per scenario the measured step
size wins on the mean (19.14 dB against 17.51) but on only four of ten
cells, and the mean is carried by two of them. It is a real result and
it is not bullet-proof; this test exists so the difference is not blurred."""
import numpy as np
from anc.bench import SCENARIOS, make_primary_path, make_secondary_path, \
run_nlms
from anc.rules import Measured
from stage18_bulletproof import KJTuned, KJ_GRID
wins = cells = 0
for _sname, gen in SCENARIOS:
for snr in (30.0, 15.0):
def sc(rule):
vals = []
for s in range(3):
rng = np.random.default_rng(1000 + s)
noise = gen(8000, rng)
nr = run_nlms(noise, make_primary_path(rng),
make_secondary_path(rng), rule,
snr_db=snr, seed=s)
if math.isfinite(nr):
vals.append(nr)
return sum(vals) / len(vals)
ours = sc(Measured())
best_kj = max(sc(KJTuned(a, g)) for a, g in KJ_GRID)
cells += 1
wins += ours > best_kj
assert 0 < wins < cells, (wins, cells)
+246
View File
@@ -0,0 +1,246 @@
"""The credit safeguard: the budget earned instead of scheduled.
The scheduled projection needs a multiplier, and the argument for 2 was
circular. This one banks the surplus a step delivers beyond a halving and
lends only what has been banked, so the only constant left is the endowment,
and one bit is the smallest integer for which the rule is not degenerate.
What must hold, and is asserted here: the stronger guarantee is real, the
endowment is minimal, and the cost of the stronger guarantee is stated rather
than hidden.
"""
from __future__ import annotations
import math
import os
import sys
import pytest
from scipy.optimize._tstutils import (_APS_TESTS_DICTS,
_CHANDRUPATLA_TESTS_DICTS)
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from simroot.pure import _Safeguard, pure_root
XTOL, MAXFEV, ACC = 1e-12, 200, 1e-6
def problems():
out = []
for d in _APS_TESTS_DICTS:
a, b = d["bracket"]
args = d.get("args", ())
out.append(((lambda ff, aa: lambda x: ff(x, *aa))(d["f"], args),
float(a), float(b), float(d["root"])))
for d in _CHANDRUPATLA_TESTS_DICTS:
a, b = d["bracket"]
out.append(((lambda ff: lambda x: ff(x))(d["f"]),
float(a), float(b), float(d["root"])))
return out
ALL = problems()
def sweep(guard):
total = solved = over = 0
for f, a, b, root in ALL:
try:
x, n, conv = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio",
scale="derived3", guard=guard)
except Exception:
x, n, conv = float("nan"), MAXFEV, False
ok = bool(conv and math.isfinite(x) and abs(x - root) <= ACC)
total += n if ok else MAXFEV
solved += ok
if ok and n > math.ceil(math.log2(abs(b - a) / XTOL)) + 4:
over += 1
return total, solved, over
def test_the_credit_guard_meets_the_near_minimal_bound_everywhere():
"""bisection + one step, on all 199 problems. The scheduled projection
is allowed twice that and uses it on 19 of them."""
_t, solved, over = sweep("credit")
assert solved == 193, solved
assert over == 0, over
def test_the_scheduled_projection_really_does_use_its_larger_budget():
"""If this ever drops to zero the two guards have converged and the
multiplier stopped mattering - which would be worth knowing."""
_t, solved, over = sweep("project")
assert solved == 193, solved
assert over > 10, over
def test_the_stronger_guarantee_costs_and_the_cost_is_stated():
"""No free lunch: the paper must not claim the credit rule is both
cheaper and safer."""
t_credit, _s, _o = sweep("credit")
t_sched, _s2, _o2 = sweep("project")
assert t_credit > t_sched, (t_credit, t_sched)
assert (t_credit - t_sched) / t_sched < 0.15, (t_credit, t_sched)
def test_a_zero_endowment_is_degenerate():
"""Why one bit is the minimum: with no endowment the allowance on the
first step is exactly half the bracket, which forces the midpoint."""
sg = _Safeguard("credit", 1.0, XTOL)
assert sg.credit == 1.0
sg.credit = 0.0
assert sg.project_credit(0.9, 0.0, 1.0) == pytest.approx(0.5)
# with the endowment the same step is left alone
sg.credit = 1.0
assert sg.project_credit(0.9, 0.0, 1.0) == pytest.approx(0.9)
def test_credit_never_goes_negative_and_banks_surplus():
sg = _Safeguard("credit", 1.0, XTOL)
sg.credit = 0.0
sg.note_shrink(1.0, 1.0) # no shrink at all
assert sg.credit == 0.0 # clamped, never a debt
sg.note_shrink(1.0, 0.125) # three bits of shrink
assert sg.credit == pytest.approx(2.0)
@pytest.mark.parametrize("c0,steps_over", [(0.25, 1), (0.5, 1), (1.0, 1),
(2.0, 2), (3.0, 3)])
def test_the_guarantee_is_bisection_plus_ceil_of_the_endowment(c0, steps_over):
"""The relation that makes the endowment determined rather than chosen."""
worst = 0
for f, a, b, root in ALL:
try:
x, n, conv = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio",
scale="derived3", guard="credit",
endowment=c0)
except Exception:
continue
if conv and math.isfinite(x) and abs(x - root) <= ACC:
half = math.ceil(math.log2(abs(b - a) / XTOL))
worst = max(worst, (n - 2) - half)
assert worst == steps_over, (c0, worst, steps_over)
def test_one_bit_is_the_cheapest_endowment_that_promises_one_step():
"""Why C0 = 1 is forced and not picked: every C0 in (0,1] gives the same
one-step guarantee, and 1 is the most permissive of them, hence the
cheapest. If a smaller endowment ever became cheaper, the argument that
the constant is determined would collapse."""
def cost(c0):
t = 0
for f, a, b, root in ALL:
try:
x, n, conv = pure_root(f, a, b, XTOL, MAXFEV,
similarity="ratio", scale="derived3",
guard="credit", endowment=c0)
except Exception:
x, n, conv = float("nan"), MAXFEV, False
ok = conv and math.isfinite(x) and abs(x - root) <= ACC
t += n if ok else MAXFEV
return t
c_one = cost(1.0)
for smaller in (0.1, 0.25, 0.5):
assert cost(smaller) > c_one, smaller
def test_the_scheduled_multiplier_has_no_such_determination():
"""The contrast that decides which safeguard the paper should propose.
The multiplicative family has no smallest non-trivial member: 1 is
degenerate and everything above it is admissible with a different cost, so
the multiplier is genuinely free."""
def cost(fac):
t = 0
for f, a, b, root in ALL:
try:
x, n, conv = pure_root(f, a, b, XTOL, MAXFEV,
similarity="ratio", scale="derived3",
guard="project", budget_factor=fac)
except Exception:
x, n, conv = float("nan"), MAXFEV, False
ok = conv and math.isfinite(x) and abs(x - root) <= ACC
t += n if ok else MAXFEV
return t
degenerate = cost(1.0)
assert degenerate > cost(2.0) # 1 is degenerate
assert cost(1.25) < cost(2.0) # and 2 is not the best either
def test_the_endowment_cannot_be_earned_and_so_cannot_be_computed():
"""Why C0 is a licence rather than a quantity to be derived from the run.
Credit is earned only by a step that beats a halving, and such a step is
permitted only when credit already exists. Starting from zero the
mechanism is locked: the allowance is exactly half the bracket, which
forces the midpoint, which delivers exactly one bit, which banks nothing.
The method is then bisection and stays bisection - so no amount of running
can produce the evidence that would justify a larger endowment."""
sg = _Safeguard("credit", 1.0, XTOL)
sg.credit = 0.0
probe = None
for _ in range(30):
probe = sg.project_credit(0.99, 0.0, 1.0) # a bold step is proposed
assert probe == pytest.approx(0.5) # ...and always refused
sg.note_shrink(1.0, max(probe, 1.0 - probe))
assert sg.credit == 0.0 # nothing is ever banked
# end to end: zero endowment reproduces bisection's cost
from simroot.baselines import bisection
t_zero = t_bis = 0
for f, a, b, root in ALL:
for meth, acc in ((lambda: pure_root(f, a, b, XTOL, MAXFEV,
similarity="ratio",
scale="derived3", guard="credit",
endowment=0.0), "z"),
(lambda: bisection(f, a, b, XTOL, MAXFEV), "b")):
try:
x, n, conv = meth()
except Exception:
x, n, conv = float("nan"), MAXFEV, False
ok = conv and math.isfinite(x) and abs(x - root) <= ACC
if acc == "z":
t_zero += n if ok else MAXFEV
else:
t_bis += n if ok else MAXFEV
assert abs(t_zero - t_bis) / t_bis < 0.01, (t_zero, t_bis)
def test_a_large_credit_cannot_overflow_the_exponent():
"""Found by the algorithm audit. The allowance is w * 2^(C-1) and C
accumulates the log of every shrink without bound, so the exponentiation
overflowed a double at C ~ 1025 and the OverflowError escaped - only the
budget exception is caught in pure_root. It is reachable: sixty
nine-decade steps reach C = 1012, and a bracket of width 1e300 with a
tolerance of 1e-8 has about 1023 bits of shrink available."""
sg = _Safeguard("credit", 1.0, XTOL)
for c in (1.0, 100.0, 1024.0, 1100.0, 1e6):
sg.credit = c
assert sg.project_credit(0.9, 0.0, 1.0) == 0.9, c
# and the accumulation that gets there must not raise either
sg.credit = 1.0
w = 1.0
for _ in range(60):
sg.note_shrink(w, w * 1e-9)
w *= 1e-9
assert sg.credit > 1000.0
assert sg.project_credit(0.9, 0.0, 1.0) == 0.9
def test_a_very_wide_bracket_is_solved_end_to_end():
"""The same defect, reached through the public entry point."""
x, n, conv = pure_root(lambda t: t - 1.0, -1e150, 1e150, 1e-12, 4000,
similarity="ratio", scale="derived3",
guard="credit")
assert conv and abs(x - 1.0) < 1e-6, (x, n, conv)
def test_credit_needs_no_tolerance_at_all():
"""Unlike the scheduled version it reads only widths, so there is no
n_max, no eps, and nothing fixed in advance from the initial bracket."""
f = lambda t: t * t - 2.0
x, _n, conv = pure_root(f, 0.0, 2.0, XTOL, MAXFEV, similarity="ratio",
scale="derived3", guard="credit")
assert conv and abs(x - math.sqrt(2.0)) <= 1e-8
+111
View File
@@ -0,0 +1,111 @@
"""The objection that the comparison stopped at 1973, and what it settled.
Two things came out of running it, and they scope the claim exactly:
the credit projection is the best repair known for the false-position
family - better than 1971, 1972, 1973, 1995 and 2011 on evaluations,
solved count and worst case at once, and the only one that reaches the
193-of-199 ceiling;
and it is NOT better than a purpose-built acceptance test: swapped into
Chandrupatla's own interpolation it costs 7627 where his test costs 5209.
So the safeguard is a good DEFAULT for a rule that has none, not a
replacement for one that has a good one. Both halves are pinned.
"""
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.baselines import chandrupatla
from stage18_bulletproof import falsi_driver, load_roots
from stage19_modern import chandrupatla_guard, falsi_modern, score
PROBS = load_roots()
XTOL, MAXFEV = 1e-12, 200
def falsi(repair):
return score(lambda f, a, b, xt, mx: falsi_driver(f, a, b, xt, mx, repair),
PROBS)
def modern(variant):
return score(lambda f, a, b, xt, mx: falsi_modern(f, a, b, xt, mx, variant),
PROBS)
# --------------------------------------------------------------------------
# the half that holds
# --------------------------------------------------------------------------
@pytest.mark.parametrize("name,getter", [
("illinois-1971", lambda: falsi("illinois")),
("pegasus-1972", lambda: falsi("pegasus")),
("anderson-1973", lambda: falsi("anderson")),
("ford-1995", lambda: modern("ford")),
("galdino-2011", lambda: modern("galdino")),
])
def test_credit_beats_every_published_repair_on_all_three_axes(name, getter):
c_t, c_s, c_w = falsi("credit")
t, s, w = getter()
assert c_t < t, (name, c_t, t)
assert c_s > s, (name, c_s, s)
assert c_w < w, (name, c_w, w)
def test_no_published_repair_reaches_the_ceiling():
"""193 of 199 is what bisection, Chandrupatla and TOMS 748 reach. Fifty
years of repairs of the false-position family stop at 176."""
assert falsi("credit")[1] == 193
for g in (lambda: falsi("illinois"), lambda: falsi("pegasus"),
lambda: falsi("anderson"), lambda: modern("ford"),
lambda: modern("galdino")):
assert g()[1] <= 176
# --------------------------------------------------------------------------
# the half that does not - pinned so the claim is never overstated
# --------------------------------------------------------------------------
def test_the_pluggable_chandrupatla_reproduces_the_published_one():
"""Without this the swap below compares against a different algorithm."""
a = score(lambda f, x, y, xt, mx: chandrupatla_guard(f, x, y, xt, mx,
"own"), PROBS)
b = score(lambda f, x, y, xt, mx: chandrupatla(f, x, y, xt, mx), PROBS)
assert a[:2] == b[:2], (a, b)
def test_credit_rescues_chandrupatlas_interpolation_but_loses_to_his_test():
"""The sharp test. Same interpolation, same bookkeeping, only the guard
differs. Credit turns an unguarded rule that solves 165 into one that
solves 193 - and Chandrupatla's own acceptance test does the same job for
a third fewer evaluations."""
own = score(lambda f, a, b, xt, mx: chandrupatla_guard(f, a, b, xt, mx,
"own"), PROBS)
cred = score(lambda f, a, b, xt, mx: chandrupatla_guard(f, a, b, xt, mx,
"credit"), PROBS)
none = score(lambda f, a, b, xt, mx: chandrupatla_guard(f, a, b, xt, mx,
"none"), PROBS)
assert cred[1] > none[1] and cred[0] < none[0] # it rescues
assert own[0] < cred[0] # and is beaten
assert own[1] == cred[1] == 193
def test_the_scope_sentence_the_paper_may_use():
"""A default safeguard for a rule that has none - not a replacement for a
purpose-built one. Both directions asserted together so neither can be
quoted alone."""
c_t, c_s, _cw = falsi("credit")
best_published = min(falsi(r)[0] for r in ("illinois", "pegasus",
"anderson"))
best_published = min(best_published, modern("ford")[0],
modern("galdino")[0])
assert c_t < best_published # best repair known
own = score(lambda f, a, b, xt, mx: chandrupatla_guard(f, a, b, xt, mx,
"own"), PROBS)
assert own[0] < c_t # and not the best guard
+102
View File
@@ -0,0 +1,102 @@
"""The second domain: adaptive step-size control for ODEs.
What is being tested is the transfer, not the integrator: everything except
the controller is shared, so a difference can only come from the controller.
"""
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 odestep.controllers import (ALL, Classical, Elementary, GustafssonPI,
MeasuredSafety)
from odestep.driver import integrate, validate_against_scipy
from stage15_ode import PROBLEMS, TOLS, reference, scaled_err
def test_the_driver_reproduces_the_library():
"""The instrument before the experiment.
The first version of this work carried a hand-typed Dormand-Prince
tableau and validated it by measuring convergence orders. That was both
an unnecessary risk and, as it turned out, a material one: with the
hand-written tableau the measured-safety controller appeared to beat the
five-constant PI controller by 8%, and with SciPy's it is 2.6% behind.
The integrator is now SciPy's throughout and the check is direct."""
for name, mine, ref, endpoint in validate_against_scipy():
assert abs(mine - ref) / ref < 0.05, (name, mine, ref)
assert endpoint < 1e-6, (name, endpoint)
def test_every_controller_solves_an_easy_problem():
f = lambda t, y: [-y[0]]
for c in ALL:
r = integrate(f, 0.0, 10.0, [1.0], c, atol=1e-8, rtol=1e-8)
assert r["reached_end"], c.name
assert abs(r["y"][0] - math.exp(-10.0)) < 1e-6, c.name
def test_the_constant_free_baseline_is_the_one_to_beat():
"""The elementary controller is what the error model alone dictates and
what every practical code refuses to use. If it were not much worse,
there would be nothing for a safety factor - chosen or measured - to buy."""
f = lambda t, y: [y[1], 5.0 * (1 - y[0] * y[0]) * y[1] - y[0]]
elem = integrate(f, 0.0, 25.0, [2.0, 0.0], Elementary(), atol=1e-8,
rtol=1e-8)
meas = integrate(f, 0.0, 25.0, [2.0, 0.0], MeasuredSafety(), atol=1e-8,
rtol=1e-8)
# margins measured with SciPy's integrator. An earlier version demanded
# a factor of two on evaluations and five on rejections; those came from
# a hand-written tableau and are not what the library produces.
assert elem["nfev"] > 1.2 * meas["nfev"], (elem["nfev"], meas["nfev"])
assert elem["rejected"] > 1.5 * meas["rejected"], (elem["rejected"],
meas["rejected"])
def test_where_the_measured_safety_factor_actually_stands():
"""The honest position, over the whole grid, with SciPy's integrator.
It is NOT ahead of the PI controller - an earlier version of this test
asserted that, on numbers produced by a hand-written tableau. What it is:
level with a five-constant controller to within a few percent while
having none, and clearly ahead of the constant-free elementary rule that
every practical code refuses to use. It is behind the three-constant
classical controller and the test says so."""
tot = {c.name: 0 for c in ALL}
fails = {c.name: 0 for c in ALL}
refs = {name: (exact if exact is not None else reference(f, t0, t1, y0))
for name, f, t0, t1, y0, exact in PROBLEMS}
for tol in TOLS:
for name, f, t0, t1, y0, _exact in PROBLEMS:
for c in ALL:
r = integrate(f, t0, t1, y0, c, atol=tol, rtol=tol)
err = scaled_err(r["y"], refs[name], tol, tol)
bad = (not r["reached_end"]) or (math.isfinite(err)
and err > 1000.0)
tot[c.name] += r["nfev"]
fails[c.name] += bad
# nobody wins by failing more
assert len(set(fails.values())) == 1, fails
m = tot[MeasuredSafety().name]
pi = tot[GustafssonPI().name]
# level with the five-constant PI controller, within five percent
assert abs(m - pi) / pi < 0.05, (m, pi)
# clearly ahead of the constant-free rule the error model alone dictates
assert m < 0.9 * tot[Elementary().name], (m, tot[Elementary().name])
# and honestly behind the three-constant classical one
assert m > tot[Classical().name], (m, tot[Classical().name])
def test_the_controller_holds_no_unused_state():
"""It was called a credit controller while banking a margin that took no
part in any decision - the ledger was vestigial and the name unearned.
Nothing may accumulate here that the step factor does not read."""
c = MeasuredSafety()
c.reset()
fields = set(vars(c))
assert fields == {"predicted", "delta"}, fields
+144
View File
@@ -0,0 +1,144 @@
"""Pins the phase detector as REFUTED.
The rho correction was derived, implemented and measured; it failed on all
three criteria set before it was run. These tests exist so nobody adopts it
later on the strength of the derivation alone, and so that if someone repairs
it the tests fail loudly and the claim gets re-examined.
"""
from __future__ import annotations
import math
import os
import sys
import pytest
from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS_DICTS
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from simroot.pure import pure_root
from stage5_three_point import PROBLEMS, order_of
XTOL = 1e-12
MAXFEV = 200
ACC = 1e-6
def total(scale, sim="ratio"):
t = 0
for _n, f, a, b, root in PROBLEMS:
x, n, conv = pure_root(f, a, b, XTOL, MAXFEV, similarity=sim,
scale=scale, guard="project")
t += n if (conv and abs(x - root) <= 1e-8) else MAXFEV
return t
def med_order(scale, sim="ratio"):
ps = [order_of(lambda f_, a_, b_, xt, mx: pure_root(
f_, a_, b_, XTOL, MAXFEV, similarity=sim, scale=scale, 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]
return sorted(ps)[len(ps) // 2] if ps else float("nan")
def test_rho_makes_the_standard_problems_worse():
plain, rho = total("derived3"), total("derived3_rho")
assert rho > plain * 1.4, (plain, rho)
def test_rho_destroys_the_order_it_was_meant_to_preserve():
"""The correction was supposed to vanish near the root. It does not."""
assert med_order("derived3") > 1.7
assert med_order("derived3_rho") < 1.3
def test_rho_does_not_capture_the_family_it_was_built_for():
"""fun3/fun4/fun5 are where cbrt beats us 2.6x. The detector was built to
recover that and recovers essentially none of it."""
fam = [d for d in _CHANDRUPATLA_TESTS_DICTS
if d["ID"].startswith(("fun3.", "fun4.", "fun5."))]
def t(sim, scale):
s = 0
for d in fam:
a, b = float(d["bracket"][0]), float(d["bracket"][1])
x, n, conv = pure_root(d["f"], a, b, XTOL, MAXFEV,
similarity=sim, scale=scale, guard="project")
s += n if (conv and abs(x - float(d["root"])) <= ACC) else MAXFEV
return s
plain, rho, cb = t("ratio", "derived3"), t("ratio", "derived3_rho"), t("cbrt", "derived3")
assert cb < 0.6 * plain, (cb, plain) # the regime is real
assert rho > 0.9 * plain, (rho, plain) # the detector does not reach it
def test_the_refuted_scales_are_still_reachable_and_still_converge():
"""Refuted is not broken: the experiment must stay reproducible."""
for scale in ("derived_rho", "derived3_rho"):
x, n, conv = pure_root(lambda t: t * t - 2.0, 0.0, 2.0, XTOL, MAXFEV,
scale=scale, guard="project")
assert conv and abs(x - math.sqrt(2.0)) <= 1e-8
def test_the_proposed_configuration_uses_neither_refuted_variant():
"""Guards against a refuted variant quietly becoming the default."""
from report_sweep import PROPOSED
assert "_rho" not in PROPOSED
assert "_m" not in PROPOSED
# --------------------------------------------------------------------------
# the m correction: derivation sound, estimator not
# --------------------------------------------------------------------------
def test_m_preserves_the_order_and_the_small_set_which_is_why_it_deceives():
"""Unlike rho, the m correction passes both of the tests a hand-picked
set can show. Pinned precisely because that is what makes it dangerous."""
assert med_order("derived3_m") > 1.7
assert total("derived3_m") <= total("derived3")
def test_m_loses_on_the_full_benchmark():
"""And fails the only test that matters. If this ever flips, the
estimator has been improved and the refutation must be revisited."""
import json
if not os.path.exists(os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"sweep_full.json")):
pytest.skip("sweep not present")
from scipy.optimize._tstutils import _APS_TESTS_DICTS
plain = m = 0
for d in _APS_TESTS_DICTS:
a, b = float(d["bracket"][0]), float(d["bracket"][1])
args = d.get("args", ())
f = (lambda ff, aa: lambda t: ff(t, *aa))(d["f"], args)
root = float(d["root"])
for scale, acc in (("derived3", "p"), ("derived3_m", "m")):
try:
x, n, conv = pure_root(f, a, b, XTOL, MAXFEV,
similarity="ratio", scale=scale,
guard="project")
except Exception:
x, n, conv = float("nan"), MAXFEV, False
good = conv and math.isfinite(x) and abs(x - root) <= ACC
if acc == "p":
plain += n if good else MAXFEV
else:
m += n if good else MAXFEV
assert m > plain, (m, plain)
def test_the_local_order_estimator_declines_when_the_model_does_not_apply():
"""theta outside (0,1) means the power-law picture does not describe the
data; the estimator must return 1 rather than clamp a value into range."""
from simroot.pure import _local_order
# a straight line: f'' = 0, theta = 0, m must be exactly 1
lin = lambda t: 3.0 * t - 6.0
assert _local_order(3.0, lin(3.0), 2.0, lin(2.0), 1.0, lin(1.0)) == 1.0
# missing third point
assert _local_order(1.0, 1.0, 0.5, 0.5, None, None) == 1.0
# repeated nodes
assert _local_order(1.0, 1.0, 1.0, 1.0, 1.0, 1.0) == 1.0
# a double root at 0: f = t^2 has theta = 1/2, so m = 2
sq = lambda t: t * t
m = _local_order(1e-3, sq(1e-3), 2e-3, sq(2e-3), 3e-3, sq(3e-3))
assert abs(m - 2.0) < 1e-6, m
+142
View File
@@ -0,0 +1,142 @@
"""Tests for the projection safeguard.
This is the guard the paper will propose, so its guarantee has to be checked
where it matters - on every problem of both benchmark sets, not on a sample -
and the order it was built to preserve has to be shown to survive.
"""
from __future__ import annotations
import math
import os
import sys
import pytest
from scipy.optimize._tstutils import (_APS_TESTS_DICTS,
_CHANDRUPATLA_TESTS_DICTS)
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
XTOL = 1e-12
MAXFEV = 200
ACC = 1e-6
PROJECTING = ("project", "safe")
def _all_problems():
out = []
for d in _APS_TESTS_DICTS:
a, b = d["bracket"]
args = d.get("args", ())
out.append((d["ID"], (lambda ff, aa: lambda x: ff(x, *aa))(d["f"], args),
float(a), float(b), float(d["root"])))
for d in _CHANDRUPATLA_TESTS_DICTS:
a, b = d["bracket"]
out.append((d["ID"], (lambda ff: lambda x: ff(x))(d["f"]),
float(a), float(b), float(d["root"])))
return out
ALL = _all_problems()
def bound(a, b):
"""The guarantee the paper may state.
The budget gives w_{j+1} <= 2^(n_max - j) * eps with n_max = 2*half, so in
exact arithmetic the width reaches xtol = 2*eps at step j = n_max - 1 and
the run costs n_max steps plus the two endpoint evaluations.
In floating point the bracket can land exactly ON the budget boundary -
the instrumented invariant in stage6b shows the worst ratio at 1.000000 -
and the loop condition `w > xtol` then demands one further step. The
honest statement is therefore n_max + 1 steps, i.e.
2 * ceil(log2(w / xtol)) + 3 evaluations,
which is still at most twice bisection plus one step. The `+1` is stated
rather than hidden: it is a property of the termination test at the
boundary, not slack that could absorb a real violation."""
return 2 * math.ceil(math.log2(abs(b - a) / XTOL)) + 3
@pytest.mark.parametrize("guard", PROJECTING)
@pytest.mark.parametrize("scale", ["derived", "derived3"])
def test_projection_respects_the_bound_on_all_199_problems(guard, scale):
"""The claim the paper makes about the projection guard. A single
violation invalidates it, so every problem is checked."""
bad = []
for pid, f, a, b, root in ALL:
x, n, conv = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio",
scale=scale, guard=guard)
if conv and abs(x - root) <= ACC and n > bound(a, b):
bad.append((pid, n, bound(a, b)))
assert not bad, f"{guard}/{scale} exceeded the bound on {bad[:5]}"
@pytest.mark.parametrize("guard", PROJECTING)
def test_projection_solves_as_much_as_the_unguarded_rule(guard):
"""A safeguard that bought its guarantee by failing more often would be
no bargain."""
solved_guard = solved_none = 0
for _pid, f, a, b, root in ALL:
for g, acc in ((guard, "g"), ("none", "n")):
x, _n, conv = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio",
scale="derived3", guard=g)
ok = conv and abs(x - root) <= ACC
if acc == "g":
solved_guard += ok
else:
solved_none += ok
assert solved_guard >= solved_none, (solved_guard, solved_none)
def test_projection_keeps_the_order_the_other_guards_destroy():
"""The whole point: `bracket` and `both` collapse the three-point rule
from order ~1.94 to ~1.00; `project` and `safe` must not."""
med = {}
for guard in ("none", "bracket", "both", "project", "safe", "step"):
ps = [order_of((lambda g: lambda f_, a_, b_, xt, mx: pure_root(
f_, a_, b_, XTOL, MAXFEV, scale="derived3", guard=g))(guard),
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[guard] = sorted(ps)[len(ps) // 2] if ps else float("nan")
assert med["bracket"] < 1.2, med # the known collapse
assert med["both"] < 1.2, med
assert med["project"] > 1.7, med # the fix
assert med["safe"] > 1.7, med
assert med["safe"] >= med["bracket"] + 0.5, med
def test_zero_slack_would_be_degenerate():
"""Guards against someone 'tightening' the budget back to the exact
bisection bound: with no slack the projection radius on a unit bracket is
a twentieth of it from the very first step, and the method becomes
bisection. The doubling is load-bearing."""
from simroot.pure import _Safeguard
sg = _Safeguard("project", 1.0, XTOL)
r_doubled = 2.0 ** (sg.n_half - 0) * sg.eps - 0.5
half = sg.n_half // 2
r_zero_slack = 2.0 ** half * sg.eps - 0.5
assert r_zero_slack < 0.06, r_zero_slack
assert r_doubled > 1e6, r_doubled
@pytest.mark.parametrize("sim", sorted(PURE_SIMS))
def test_every_similarity_works_under_the_projection(sim):
f = lambda t: t * t - 2.0
x, _n, conv = pure_root(f, 0.0, 2.0, XTOL, MAXFEV, similarity=sim,
scale="derived3", guard="safe")
assert conv and abs(x - math.sqrt(2.0)) <= 1e-8
def test_projection_needs_a_tolerance_and_a_real_bracket():
with pytest.raises(ValueError):
from simroot.pure import _Safeguard
_Safeguard("project", 1.0, 0.0)
with pytest.raises(ValueError):
from simroot.pure import _Safeguard
_Safeguard("safe", 0.0, XTOL)
+177
View File
@@ -0,0 +1,177 @@
"""Regression suite for simroot.pure and for the benchmark harness.
Each audited defect is pinned as a test, then the contracts the method is
allowed to claim in the paper - and only those.
"""
from __future__ import annotations
import math
import os
import sys
import pytest
from scipy.optimize._tstutils import (_APS_TESTS_DICTS,
_CHANDRUPATLA_TESTS_DICTS)
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from simroot.baselines import bisection, itp
from simroot.pure import GUARDS, PURE_SIMS, pure_root
XTOL = 1e-12
MAXFEV = 200
ACC = 1e-6
SMALL = [
("x^2-2", lambda x: x * x - 2.0, 0.0, 2.0, math.sqrt(2.0)),
("cos-x", lambda x: math.cos(x) - x, 0.0, 1.0, 0.7390851332151607),
("cubic", lambda x: x ** 3 - x - 2.0, 1.0, 2.0, 1.5213797068045676),
("x^10-1", lambda x: x ** 10 - 1.0, 0.0, 1.3, 1.0),
("flat", lambda x: math.exp(-x) * (x - 1.0), -1.0, 40.0, 1.0),
("atan", lambda x: math.atan(x), -1.0, 3.0, 0.0),
]
# the guards that are ALLOWED to claim a worst-case bound
SOUND_GUARDS = ("bracket", "both")
def _problems(dicts, chandrupatla=False):
out = []
for d in dicts:
a, b = float(d["bracket"][0]), float(d["bracket"][1])
args = () if chandrupatla else d.get("args", ())
f = (lambda ff, aa: lambda x: ff(x, *aa))(d["f"], args)
out.append((d["ID"], f, a, b, float(d["root"])))
return out
APS = _problems(_APS_TESTS_DICTS)
CHA = _problems(_CHANDRUPATLA_TESTS_DICTS, chandrupatla=True)
def two_x_bisection_bound(a, b):
return 2 * math.ceil(math.log2(abs(b - a) / XTOL)) + 4
# --------------------------------------------------------------------------
# audited defects, pinned
# --------------------------------------------------------------------------
@pytest.mark.parametrize("guard", SOUND_GUARDS)
def test_bug_pending_flag_gave_three_x_not_two_x(guard):
"""The bracket guard cleared `pending` on the halving branch and then
returned early, so TWO similarity steps were granted after every halving
instead of one - a 3x bisection guarantee dressed up as 2x. Chandrupatla
fun4.3 caught it: 139 evaluations against a bound of 114."""
d = next(x for x in _CHANDRUPATLA_TESTS_DICTS if x["ID"] == "fun4.3")
a, b = float(d["bracket"][0]), float(d["bracket"][1])
x, n, conv = pure_root(d["f"], a, b, XTOL, MAXFEV,
similarity="ratio", scale="derived", guard=guard)
assert conv and abs(x - float(d["root"])) <= ACC
assert n <= two_x_bisection_bound(a, b), f"{n} evaluations, 3x regression"
def test_step_guard_makes_no_worst_case_claim():
"""The step guard is cheap but unsound: a sequence creeping towards a
non-root with halving steps never trips it. The suite must NOT assert a
bound for it - this test exists so that nobody adds one later without
noticing that the benchmark reports real violations."""
violations = 0
for _pid, f, a, b, root in CHA:
x, n, conv = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio",
scale="derived", guard="step")
if conv and abs(x - root) <= ACC and n > two_x_bisection_bound(a, b):
violations += 1
assert violations > 0, ("the step guard no longer violates the 2x bound; "
"if that is a real improvement, promote it to "
"SOUND_GUARDS and prove it")
# --------------------------------------------------------------------------
# contracts
# --------------------------------------------------------------------------
@pytest.mark.parametrize("guard", SOUND_GUARDS)
def test_sound_guards_respect_the_bound_on_every_benchmark_problem(guard):
"""The only worst-case claim the paper may make."""
bad = []
for pid, f, a, b, root in APS + CHA:
x, n, conv = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio",
scale="derived", guard=guard)
if conv and abs(x - root) <= ACC and n > two_x_bisection_bound(a, b):
bad.append((pid, n, two_x_bisection_bound(a, b)))
assert not bad, f"{guard} exceeded the 2x bound on {bad[:5]}"
@pytest.mark.parametrize("sim", sorted(PURE_SIMS))
@pytest.mark.parametrize("prob", SMALL, ids=[p[0] for p in SMALL])
def test_derived_scale_with_a_sound_guard_always_converges(sim, prob):
_, f, a, b, root = prob
x, n, conv = pure_root(f, a, b, XTOL, 400, similarity=sim,
scale="derived", guard="both")
assert conv, f"pure.{sim}.both did not converge"
assert abs(x - root) <= 1e-8
@pytest.mark.parametrize("guard", GUARDS)
def test_never_leaves_the_bracket(guard):
for _pid, f, a, b, _root in SMALL:
x, _n, _c = pure_root(f, a, b, XTOL, MAXFEV, guard=guard)
assert min(a, b) - 1e-9 <= x <= max(a, b) + 1e-9
def test_rejects_unknown_settings_loudly():
f = lambda t: t * t - 2.0
with pytest.raises(ValueError):
pure_root(f, 0.0, 2.0, XTOL, MAXFEV, similarity="no-such")
with pytest.raises(ValueError):
pure_root(f, 0.0, 2.0, XTOL, MAXFEV, scale="no-such")
with pytest.raises(ValueError):
pure_root(f, 0.0, 2.0, XTOL, MAXFEV, guard="no-such")
with pytest.raises(ValueError):
pure_root(f, 3.0, 4.0, XTOL, MAXFEV) # no sign change
def test_budget_is_never_exceeded():
for budget in (0, 1, 2, 5, 30):
for guard in GUARDS:
_x, n, _c = pure_root(lambda t: math.exp(-t) * (t - 1.0),
-1.0, 40.0, XTOL, budget, guard=guard)
assert n <= budget
# --------------------------------------------------------------------------
# the competitor must be sound too, or the benchmark is worthless
# --------------------------------------------------------------------------
@pytest.mark.parametrize("n0", [0, 1, 2, 5])
def test_itp_respects_its_own_published_guarantee(n0):
"""ITP's theorem: it never needs more iterations than bisection plus n0.
Checking the theorem is the strongest validation available for the
implementation without a reference to diff against.
The permitted count is n_half + n0 + 1, not n_half + n0: the induction
gives bracket_j <= 2^(n_max-j+1)*eps, so the width first drops to 2*eps at
j = n_max, and the iterations actually run j = 0 .. n_max, which is
n_max + 1 of them. Stating it as n_max flagged 3 of 154 problems by
exactly one iteration - the signature of an indexing mistake in the test
rather than of a broken implementation."""
bad = []
for pid, f, a, b, root in APS:
if not a < b:
continue
allowed = math.ceil(math.log2((b - a) / XTOL)) + n0 + 1
x, n, conv = itp(f, a, b, XTOL, 400, kappa1=0.2 / (b - a), n0=n0)
if conv and abs(x - root) <= ACC and (n - 2) > allowed:
bad.append((pid, n - 2, allowed))
assert not bad, f"ITP broke its own bound on {bad[:5]}"
def test_bisection_is_the_reference_ceiling_everyone_beats():
"""Sanity check on the harness: if the new method were not actually
solving anything, this is the comparison that would expose it."""
tot_pure = tot_bis = 0
for _pid, f, a, b, root in APS:
_x, n1, c1 = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio",
scale="derived", guard="both")
_y, n2, c2 = bisection(f, a, b, XTOL, MAXFEV)
tot_pure += n1 if c1 else MAXFEV
tot_bis += n2 if c2 else MAXFEV
assert tot_pure < tot_bis, (tot_pure, tot_bis)
+107
View File
@@ -0,0 +1,107 @@
"""Pins the regime findings of stage 7.
Three conclusions were drawn from that analysis and used to decide what to do
next. Each is asserted here so that a re-run of the sweep which moves them
fails the build instead of quietly invalidating the conclusion.
"""
from __future__ import annotations
import json
import os
import sys
import pytest
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ROOT)
SWEEP = os.path.join(ROOT, "sweep_full.json")
pytestmark = pytest.mark.skipif(not os.path.exists(SWEEP),
reason="sweep_full.json not present")
SCALES = ("start", "bracket", "derived", "derived3")
MAXFEV = 200
def load():
with open(SWEEP, encoding="utf8") as fh:
return {r["label"]: r for r in json.load(fh)}
def cost(rows, sim, scale, pid):
r = rows[f"pure.{sim}.{scale}.project"]
d = r["APS"]["per"].get(pid) or r["CHA"]["per"].get(pid)
return d[0] if d[1] else MAXFEV
def all_pids(rows):
r = rows["pure.ratio.derived3.project"]
return list(r["APS"]["per"]) + list(r["CHA"]["per"])
def test_the_scale_axis_has_little_headroom():
"""5.3%. If this ever grows a lot, the scale is worth revisiting; as it
stands it is not where the regimes live."""
rows = load()
pids = all_pids(rows)
ours = sum(cost(rows, "ratio", "derived3", p) for p in pids)
oracle = sum(min(cost(rows, "ratio", s, p) for s in SCALES) for p in pids)
head = (ours - oracle) / ours
assert 0.0 <= head < 0.10, head
def test_cbrt_beats_us_consistently_on_the_fun4_family():
"""The signature of a real regime rather than luck: the same function at
five different parameters all prefer the same similarity, by a wide
margin. This is the finding that keeps the line open."""
rows = load()
fam = [f"fun4.{i}" for i in range(1, 6)]
better = 0
for pid in fam:
ours = cost(rows, "ratio", "derived3", pid)
cb = cost(rows, "cbrt", "derived3", pid)
if cb < ours:
better += 1
assert better == 5, f"only {better}/5 of the fun4 family prefer cbrt"
tot_ours = sum(cost(rows, "ratio", "derived3", p) for p in fam)
tot_cb = sum(cost(rows, "cbrt", "derived3", p) for p in fam)
assert tot_cb < 0.6 * tot_ours, (tot_cb, tot_ours)
def test_switching_by_benchmark_set_is_worse_than_not_switching():
"""The finding that closed the obvious exploit: cbrt wins the fun3/4/5
families but loses the rest of Chandrupatla, so a set-level switch costs
more than a single fixed choice. Any future 'regime' proposal has to beat
this, not merely beat one family."""
rows = load()
single = (rows["pure.ratio.derived3.project"]["APS"]["total"]
+ rows["pure.ratio.derived3.project"]["CHA"]["total"])
switched = (rows["pure.ratio.derived3.project"]["APS"]["total"]
+ rows["pure.cbrt.derived3.project"]["CHA"]["total"])
assert switched > single, (switched, single)
def test_the_proposed_similarity_wins_most_problems_outright():
"""155 of 199. The per-problem oracle over similarities is an upper
bound on regime detection, not a method, and most of the field is already
ours without it."""
rows = load()
pids = all_pids(rows)
sims = ("ratio", "sqrt", "cbrt", "rational", "ratio_soft", "exp_abs",
"rat_sq", "exp_sq", "cosine")
wins = 0
for p in pids:
c = {s: cost(rows, s, "derived3", p) for s in sims}
if c["ratio"] == min(c.values()):
wins += 1
assert wins >= 150, wins
def test_stage7_runs():
"""The analysis script itself must still execute against the current
sweep file."""
import subprocess
r = subprocess.run([sys.executable, "stage7_regimes.py"], cwd=ROOT,
capture_output=True, text=True, timeout=600)
assert r.returncode == 0, r.stderr[-800:]
assert "SETTLING THE TWO SUSPICIONS" in r.stdout
+77
View File
@@ -0,0 +1,77 @@
"""A prediction registered before the run, and falsified by it.
The hypothesis was that a method which wins in some places and loses in others
must have a regime where it wins outright, and that the regime is where the
quantity the constant approximates CHANGES during the run - because one
constant cannot be right at two different noise levels while a measured value
tracks.
It was stated before measuring, with the falsification condition attached: the
measured rule had to lose every constant-noise cell and win every
varying-noise cell. It lost all twelve.
The first version of the test could not have confirmed it either way - noise
reduction was scored on the second half of the run, by which time every ramp
and step had already arrived, so the metric never saw a transition. That flaw
was found and fixed before the conclusion was drawn, and the conclusion did
not change.
This file exists so the line is not reopened without new evidence.
"""
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 anc.rules import Fixed, Measured
from stage20_prediction import (FLAT_5, FLAT_30, STEP_25, SWING_25, sig_cabin,
sig_tonal, score)
SEEDS = 3
def test_the_metric_window_sees_the_transition():
"""The flaw that would have produced a false falsification. A varying
profile must not score the same as a constant one at its final level."""
varying = score(Fixed(0.1), sig_tonal, SWING_25, seeds=SEEDS)
final_level = score(Fixed(0.1), sig_tonal, FLAT_5, seeds=SEEDS)
assert varying > final_level + 2.0, (varying, final_level)
@pytest.mark.parametrize("signal", [sig_tonal, sig_cabin],
ids=["tonal", "cabin"])
@pytest.mark.parametrize("profile,kind", [(FLAT_30, "control"),
(FLAT_5, "control"),
(SWING_25, "prediction"),
(STEP_25, "prediction")],
ids=["flat30", "flat5", "ramp", "step"])
def test_an_oracle_constant_beats_the_measured_rule_everywhere(signal, profile,
kind):
"""The registered prediction required the measured rule to WIN the two
'prediction' cells. It does not, on either signal. A single constant,
chosen with knowledge of the scenario, is ahead even when the noise level
swings by 25 dB inside the run."""
ms = score(Measured(), signal, profile, seeds=SEEDS)
best = max(score(Fixed(mu), signal, profile, seeds=SEEDS)
for mu in (0.05, 0.1, 0.2, 0.5, 1.0))
assert best > ms, (kind, best, ms)
def test_the_conclusion_this_licenses():
"""Winning somewhere and losing elsewhere does not imply a regime of
outright victory exists; it is equally consistent with a middling
generalist. On this evidence there is no such regime, and the claim the
work may make is the scoped one in test_modern_repairs.py."""
losses = 0
for signal in (sig_tonal, sig_cabin):
for profile in (FLAT_30, FLAT_5, SWING_25, STEP_25):
ms = score(Measured(), signal, profile, seeds=SEEDS)
best = max(score(Fixed(mu), signal, profile, seeds=SEEDS)
for mu in (0.05, 0.2, 1.0))
losses += ms < best
assert losses == 8, losses
+95
View File
@@ -0,0 +1,95 @@
"""The relaxation parameter: the fourth domain, and the fourth time the same
pattern appears.
omega in SOR is the oldest hand-tuned step length in numerical computing. The
similarity form - omega = 1 + cos between successive corrections - lands
exactly in the admissible interval [1, 2] with nothing chosen, and it beats
Gauss-Seidel five-fold. It loses to a swept omega two-fold.
A second map, derived from the theory rather than from the geometry, was tried
and is worse than the first. Both are pinned: the working one so it is not
lost, the refuted one so it is not retried.
"""
from __future__ import annotations
import math
import os
import sys
import numpy as np
import pytest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from relax.sor import (ContractionOmega, Fixed, SimilarityOmega, poisson_rhs,
residual, solve, sweep_gs)
from stage23_relaxation import anisotropic, convection, theory_check
N = 14
F = poisson_rhs(N)
W_OPT = 2.0 / (1.0 + math.sin(math.pi / (N + 1)))
def test_the_sweep_reproduces_the_sor_theory():
"""The instrument before the experiment: for the model problem the SOR
spectral radius is exactly omega* - 1."""
_w, theory, measured = theory_check(16)
assert abs(theory - measured) < 0.02, (theory, measured)
def test_the_similarity_omega_lands_in_the_admissible_interval():
"""Range of the similarity and convergence condition of the method
coincide, which is why nothing had to be clipped by hand."""
r = SimilarityOmega()
r.reset()
d1 = np.array([1.0, 2.0, 3.0])
assert r.next_omega(None, None) == 1.0
assert r.next_omega(d1, d1) == pytest.approx(2.0) # identical
assert r.next_omega(d1, -d1) == pytest.approx(1.0) # opposed
assert r.next_omega(np.array([1.0, 0.0, 0.0]),
np.array([0.0, 1.0, 0.0])) == pytest.approx(1.0)
@pytest.mark.parametrize("label,coeff", [
("poisson", None), ("aniso", anisotropic(N, 0.1))])
def test_similarity_beats_gauss_seidel_by_a_wide_margin(label, coeff):
gs, _ = solve(F, Fixed(1.0), coeff=coeff)
sim, _ = solve(F, SimilarityOmega(), coeff=coeff)
assert sim * 3 < gs, (label, sim, gs)
@pytest.mark.parametrize("label,coeff", [
("poisson", None), ("aniso", anisotropic(N, 0.1))])
def test_and_loses_to_a_swept_omega(label, coeff):
"""The other half, stated so the claim is never inflated."""
sim, _ = solve(F, SimilarityOmega(), coeff=coeff)
best = min(solve(F, Fixed(w), coeff=coeff)[0]
for w in (1.2, 1.4, 1.6, 1.8, W_OPT))
assert best < sim, (label, best, sim)
def test_the_similarity_rule_over_relaxes():
"""The diagnosis, not just the outcome: the cosine between successive SOR
corrections sits near 1, so 1 + cos saturates near 2 and the rule picks a
larger omega than the optimum."""
_sim, mean_w = solve(F, SimilarityOmega())
assert mean_w > W_OPT, (mean_w, W_OPT)
def test_the_contraction_map_is_refuted_and_worse():
"""The second attempt, pinned so it is not tried again. Undamped
feedback pins omega at the limit from the first sweep."""
sim, _ = solve(F, SimilarityOmega())
con, mean_w = solve(F, ContractionOmega())
assert con > sim, (con, sim)
assert mean_w > 1.9, mean_w
def test_convection_is_where_it_actually_hurts():
"""On a strongly non-symmetric problem Gauss-Seidel is already fast and
the rule makes it slower - the one case where the similarity form is not
merely second but harmful."""
coeff = convection(N, 10.0)
gs, _ = solve(F, Fixed(1.0), coeff=coeff)
sim, _ = solve(F, SimilarityOmega(), coeff=coeff)
assert sim > gs, (sim, gs)
+82
View File
@@ -0,0 +1,82 @@
"""The credit safeguard applied to step rules it was not designed for.
This is the finding that decides what the work is about. As a
one-dimensional root finder the similarity method is subsumed by
Chandrupatla 1997 on every axis measured. The safeguard is not: it is
independent of the similarity framing and it converts unsafe interpolating
rules into safe ones without a constant and without a cost.
"""
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.baselines import chandrupatla
from stage14_is_anything_reusable import RULES, load, measure
PROBS = load()
@pytest.mark.parametrize("name,rule", RULES, ids=[n for n, _ in RULES])
def test_the_guard_makes_an_unsafe_rule_solve_everything(name, rule):
"""Every rule, guarded, must reach the 193 of 199 that is the most any
method here achieves - including bisection and Chandrupatla."""
_t_raw, s_raw, _w_raw = measure(rule, False, PROBS)
_t_g, s_g, _w_g = measure(rule, True, PROBS)
assert s_g == 193, (name, s_g)
assert s_g > s_raw, (name, s_raw, s_g)
@pytest.mark.parametrize("name,rule", RULES, ids=[n for n, _ in RULES])
def test_the_guard_is_free_or_better(name, rule):
"""Safety normally costs evaluations. Here it saves them, because the
failures it prevents were being charged the full budget."""
t_raw, _s, _w = measure(rule, False, PROBS)
t_g, _s2, _w2 = measure(rule, True, PROBS)
assert t_g < t_raw, (name, t_raw, t_g)
@pytest.mark.parametrize("name,rule", RULES, ids=[n for n, _ in RULES])
def test_the_guarded_worst_case_is_at_bisection_level(name, rule):
"""The point of a safeguard. Bisection's worst problem here costs 82
evaluations; every guarded rule must be in that neighbourhood, where the
raw rules run to 135, 178 and the 200-evaluation cap."""
_t, _s, w_g = measure(rule, True, PROBS)
assert w_g <= 90, (name, w_g)
def test_the_similarity_method_is_subsumed_by_chandrupatla():
"""Stated as a test so the paper cannot drift back to claiming otherwise.
Chandrupatla is better on total, on the worst case, and on stability,
with the same number of constants (none) and the same 193 solved."""
from simroot.pure import pure_root
XTOL, MAXFEV, ACC = 1e-12, 200, 1e-6
ours_t = ours_w = ours_s = 0
cha_t = cha_w = cha_s = 0
for f, a, b, root in PROBS:
for meth, which in ((lambda: pure_root(f, a, b, XTOL, MAXFEV,
similarity="ratio",
scale="derived3",
guard="credit"), "o"),
(lambda: chandrupatla(f, a, b, XTOL, MAXFEV), "c")):
try:
x, n, conv = meth()
except Exception:
x, n, conv = float("nan"), MAXFEV, False
ok = bool(conv and math.isfinite(x) and abs(x - root) <= ACC)
if which == "o":
ours_t += n if ok else MAXFEV
ours_s += ok
ours_w = max(ours_w, n) if ok else ours_w
else:
cha_t += n if ok else MAXFEV
cha_s += ok
cha_w = max(cha_w, n) if ok else cha_w
assert cha_s == ours_s == 193
assert cha_t < ours_t, (cha_t, ours_t)
assert cha_w <= ours_w, (cha_w, ours_w)
+177
View File
@@ -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)
+105
View File
@@ -0,0 +1,105 @@
"""The similarity family for the NLMS step size, and what it settled.
Two corrections came from outside and both were right:
1. mu should be a SIMILARITY, not a variance estimate. The filter can only
remove what correlates with its input, so the quantity is the cosine
between the error and the filtered reference. It beats the variance
formulation on eleven of twelve cells.
2. Testing one similarity function is as incomplete here as it would have
been in the root-finding work. The whole family must be run, and when it
is, the best member is not the cosine.
And the law of the first domain - that the slope of (1 - S) at the origin
decides, not the shape of S - orders this family too, monotonically and
without exception. That is a prediction made in one domain and confirmed in
another.
What it did not settle: even the best member loses to a constant tuned per
scenario, on all twelve cells.
"""
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 anc.rules import CosineSimilarity, Fixed, Measured
from simroot.pure import PURE_SIMS
from stage20_prediction import FLAT_15, FLAT_30, STEP_25, sig_cabin, sig_tonal, \
score
SEEDS = 3
CELLS = [(sig_tonal, FLAT_30), (sig_tonal, STEP_25),
(sig_cabin, FLAT_15), (sig_cabin, STEP_25)]
def make(name):
r = CosineSimilarity()
r.S = None if name is None else PURE_SIMS[name]
return r
def mean(rule):
vals = [score(rule, gen, prof, seeds=SEEDS) for gen, prof in CELLS]
return sum(vals) / len(vals)
def test_the_similarity_formulation_beats_the_variance_one():
"""Correction 1. mu as 'how much of the error resembles what the filter
can remove' against mu as 'one minus the noise-power fraction'.
Stated carefully, because the first version of this test was not: what
beats the variance rule on eleven of twelve cells is the best member of
the FAMILY, not the raw cosine. The raw cosine can lose to it - on this
subset it does, 14.04 against 14.32 - which is precisely why correction 2
(run the whole family) was needed before the claim could be made at all.
"""
best_member = max(mean(make(n)) for n in (None, "ratio", "exp_abs",
"rational", "ratio_soft",
"sqrt", "cbrt"))
assert best_member > mean(Measured()), best_member
def test_the_raw_cosine_is_not_the_best_member():
"""Correction 2. A single member is not the family: the identity
S(d) = 1 - d is beaten by members with a steeper approach to 1."""
raw = mean(make(None))
best = max(mean(make(n)) for n in ("sqrt", "cbrt", "ratio_soft"))
assert best > raw, (best, raw)
def test_two_code_paths_for_the_same_function_agree():
"""S = None takes mu = |cos| directly; S = ratio computes
max(0, 1 - d) with d = 1 - |cos|. They are the same function on [0,1]
and reach it by different code, so they must score identically."""
a = mean(make(None))
b = mean(make("ratio"))
assert abs(a - b) < 1e-9, (a, b)
def test_the_law_of_the_first_domain_orders_this_family_too():
"""The prediction carried across domains: slope of (1 - S) at 0 decides.
Every slope-1 member must beat every slope-0 member."""
slope1 = [mean(make(n)) for n in ("ratio", "exp_abs", "rational")]
slope0 = [mean(make(n)) for n in ("rat_sq", "exp_sq", "cosine")]
assert min(slope1) > max(slope0), (slope1, slope0)
def test_and_it_still_loses_to_a_tuned_constant():
"""The half that did not work, pinned so the claim is never overstated.
The competitor knows the scenario; no deployable controller does - and it
is still ahead."""
losses = 0
for gen, prof in CELLS:
best_member = max(score(make(n), gen, prof, seeds=SEEDS)
for n in (None, "cbrt", "sqrt", "ratio"))
oracle = max(score(Fixed(mu), gen, prof, seeds=SEEDS)
for mu in (0.05, 0.1, 0.2, 0.5, 1.0))
losses += best_member < oracle
assert losses == len(CELLS), losses
+234
View File
@@ -0,0 +1,234 @@
"""Regression suite for the similarity root finders and the baselines.
Every bug found in a static audit becomes a test here (project rule), so the
four audited defects are pinned first, then the behavioural contracts.
"""
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.baselines import (BASELINES, bisection, brent, illinois, itp,
regula_falsi, ridders, toms748)
from simroot.sim import SIMILARITIES, sim_root
from stage0b_pure_similarity import SIMS_01, pure_similarity_step
from stage0d_derived_similarity import FAST_SIMS, derived_step, pure_step
XTOL = 1e-12
MAXFEV = 300
PROBLEMS = [
("x^2-2", lambda x: x * x - 2.0, 0.0, 2.0, math.sqrt(2.0)),
("cos-x", lambda x: math.cos(x) - x, 0.0, 1.0, 0.7390851332151607),
("exp-2", lambda x: math.exp(x) - 2.0, 0.0, 2.0, math.log(2.0)),
("cubic", lambda x: x ** 3 - x - 2.0, 1.0, 2.0, 1.5213797068045676),
("x^10-1", lambda x: x ** 10 - 1.0, 0.0, 1.3, 1.0),
("flat", lambda x: math.exp(-x) * (x - 1.0), -1.0, 40.0, 1.0),
("atan", lambda x: math.atan(x), -1.0, 3.0, 0.0),
]
GUARDED_SIM = [(s, p) for s in SIMILARITIES for p in ("point", "pair", "pair2d")]
# Interpolation-only methods: no bisection safeguard, hence no worst-case
# guarantee. They are in the suite as references, not as claims.
NO_GUARANTEE = {"regula_falsi", "pegasus"}
# --------------------------------------------------------------------------
# audited defects, pinned
# --------------------------------------------------------------------------
@pytest.mark.parametrize("name", sorted(BASELINES))
def test_bug1_exhausted_budget_is_an_honest_failure(name):
"""A budget that runs out on the very first evaluation must return
converged=False, never raise UnboundLocalError."""
method = BASELINES[name]
for budget in (0, 1, 2):
x, n, conv = method(lambda t: t * t - 2.0, 0.0, 2.0, XTOL, budget)
assert n <= budget
assert math.isfinite(x)
assert conv is False or n <= budget
def test_bug2_itp_stops_at_the_same_bracket_as_everyone_else():
"""ITP is natively written against a half-width epsilon. If xtol were fed
in as epsilon, ITP would stop at twice the bracket width of its
competitors and win the benchmark on the stopping rule. The final
accuracy must be comparable to the other methods, not 2x looser."""
f = lambda t: t * t - 2.0
root = math.sqrt(2.0)
xtol = 1e-8
x_itp, _, c1 = itp(f, 0.0, 2.0, xtol, MAXFEV)
x_bis, _, c2 = bisection(f, 0.0, 2.0, xtol, MAXFEV)
assert c1 and c2
assert abs(x_itp - root) <= xtol
assert abs(x_bis - root) <= xtol
def test_bug3_itp_rejects_a_reversed_bracket_loudly():
with pytest.raises(ValueError):
itp(lambda t: t * t - 2.0, 2.0, 0.0, XTOL, MAXFEV)
def test_bug4_stagnation_is_not_reported_as_success():
"""sim_root used to return converged=True when it could no longer split
the bracket, regardless of whether the tolerance had been met. A
tolerance far below what float can represent on this bracket must not
come back as a success."""
f = lambda t: math.exp(-t) * (t - 1.0)
x, n, conv = sim_root(f, -1.0, 40.0, 1e-300, 400,
similarity="inv", prototypes="pair2d", guard=True)
if not conv:
return # honest failure - fine
assert abs(x - 1.0) <= 1e-8, "claimed success while stagnated"
# --------------------------------------------------------------------------
# behavioural contracts
# --------------------------------------------------------------------------
@pytest.mark.parametrize("name", sorted(BASELINES))
@pytest.mark.parametrize("prob", PROBLEMS, ids=[p[0] for p in PROBLEMS])
def test_baselines_find_the_root(name, prob):
_, f, a, b, root = prob
x, n, conv = BASELINES[name](f, a, b, XTOL, MAXFEV)
if name in NO_GUARANTEE:
# These carry no bisection safeguard and therefore no worst-case
# bound; the literature says so and the flat problem shows it. What
# is required of them is only that they stay honest about it.
assert n <= MAXFEV
assert math.isfinite(x)
if not conv:
return
assert conv, f"{name} did not converge"
assert abs(x - root) <= 1e-8, f"{name} converged to the wrong point"
assert n <= MAXFEV
@pytest.mark.parametrize("sim,proto", GUARDED_SIM,
ids=[f"{s}.{p}" for s, p in GUARDED_SIM])
@pytest.mark.parametrize("prob", PROBLEMS, ids=[p[0] for p in PROBLEMS])
def test_guarded_sim_never_fails(sim, proto, prob):
"""The parameter-free safeguard is the whole reason it exists: with it on,
no similarity function and no prototype level may fail on any problem."""
_, f, a, b, root = prob
x, n, conv = sim_root(f, a, b, XTOL, MAXFEV,
similarity=sim, prototypes=proto, guard=True)
assert conv, f"SIM.{proto}.{sim} did not converge"
assert abs(x - root) <= 1e-8
assert n <= MAXFEV
@pytest.mark.parametrize("sim,proto", GUARDED_SIM,
ids=[f"{s}.{p}" for s, p in GUARDED_SIM])
@pytest.mark.parametrize("prob", PROBLEMS, ids=[p[0] for p in PROBLEMS])
def test_guard_bounds_the_worst_case_by_twice_bisection(sim, proto, prob):
"""The safeguard pays back one bisection step for every similarity step
that failed to halve the bracket, so the worst case is bounded by twice
the BISECTION BOUND.
The comparison must be against the bound ceil(log2(w/xtol)), not against
what bisection happened to spend on this problem: on atan over [-1, 3]
bisection lands exactly on the root in 4 evaluations, which is luck, not
a worst case, and comparing against it asserts something that was never
claimed."""
_, f, a, b, _ = prob
n_bound = math.ceil(math.log2(abs(b - a) / XTOL))
_, n_sim, conv = sim_root(f, a, b, XTOL, MAXFEV,
similarity=sim, prototypes=proto, guard=True)
assert conv
assert n_sim <= 2 * n_bound + 4, f"{n_sim} > 2*{n_bound}+4"
@pytest.mark.parametrize("prob", PROBLEMS, ids=[p[0] for p in PROBLEMS])
def test_every_method_stays_inside_the_initial_bracket(prob):
"""A bracketing method must never return a point outside the bracket it
was given, converged or not."""
_, f, a, b, _ = prob
lo, hi = min(a, b), max(a, b)
for name, m in BASELINES.items():
x, _, _ = m(f, a, b, XTOL, MAXFEV)
assert lo - 1e-9 <= x <= hi + 1e-9, f"{name} left the bracket"
for sim, proto in GUARDED_SIM:
x, _, _ = sim_root(f, a, b, XTOL, MAXFEV, similarity=sim,
prototypes=proto, guard=True)
assert lo - 1e-9 <= x <= hi + 1e-9, f"SIM.{proto}.{sim} left the bracket"
def test_the_pure_two_point_similarity_step_is_regula_falsi_exactly():
"""The stage-0 identity, as an executable claim rather than an assertion
in prose: normalising the similarity by the two bracket ends reproduces
the false-position formula bit for bit."""
for fa, fb in ((-5.436, 1.6e-16), (-1.0, 3.0), (2.5, -0.125), (-1e-9, 7.0)):
a, b = -1.0, 40.0
s_a = abs(fb) / (abs(fa) + abs(fb))
s_b = abs(fa) / (abs(fa) + abs(fb))
x_sim = s_a * a + s_b * b
x_falsi = (a * fb - b * fa) / (fb - fa)
assert abs(x_sim - x_falsi) <= 1e-12 * max(1.0, abs(x_falsi))
def test_similarity_shape_predicts_the_contraction_rate():
"""The stage-0c law: rate = |1 - c*|f'(r)|*D/scale| with c the slope of
(1 - S) at zero. Pinned for the two linear-approach similarities, where
it was measured to four digits."""
f = lambda t: t * t - 2.0
root, fp, D, scale = math.sqrt(2.0), 2.0 * math.sqrt(2.0), 2.0 - math.sqrt(2.0), 2.0
predicted = abs(1.0 - 1.0 * fp * D / scale)
for sim in ("rational", "exp_abs"):
S = SIMS_01[sim]
x, errs = 0.0, []
for _ in range(14):
x = x + (1.0 - S(abs(f(x)) / scale)) * (2.0 - x)
errs.append(abs(x - root))
win = [errs[i + 1] / errs[i] for i in range(len(errs) - 1) if errs[i] > 1e-11]
measured = sum(win[-3:]) / len(win[-3:])
assert abs(measured - predicted) < 1e-3, (sim, measured, predicted)
def test_quadratic_approach_similarities_stagnate():
"""The other half of the same law: a similarity reaching 1 quadratically
has c = 0, so the predicted rate is 1 and the iteration must crawl."""
f = lambda t: t * t - 2.0
root = math.sqrt(2.0)
for sim in ("rat_sq", "exp_sq", "cosine"):
S = SIMS_01[sim]
x = 0.0
for _ in range(14):
x = x + (1.0 - S(abs(f(x)) / 2.0)) * (2.0 - x)
assert abs(x - root) > 1e-3, f"{sim} was expected to stagnate"
@pytest.mark.parametrize("sim", sorted(FAST_SIMS))
@pytest.mark.parametrize("prob", PROBLEMS, ids=[p[0] for p in PROBLEMS])
def test_derived_scale_converges_where_chosen_scale_need_not(sim, prob):
"""The data-derived local scale plus the parameter-free safeguard must
always converge; the chosen-scale version carries no such promise and is
only required not to crash."""
_, f, a, b, root = prob
x, n, conv = derived_step(f, a, b, XTOL, MAXFEV, sim=sim)
assert conv, f"derived.{sim} did not converge"
assert abs(x - root) <= 1e-8
x2, n2, _ = pure_step(f, a, b, XTOL, MAXFEV, sim)
assert math.isfinite(x2) and n2 <= MAXFEV
def test_counter_never_exceeds_the_budget():
for budget in (3, 7, 25):
for name, m in BASELINES.items():
_, n, _ = m(lambda t: math.exp(-t) * (t - 1.0), -1.0, 40.0,
XTOL, budget)
assert n <= budget, f"{name} spent {n} of {budget}"
_, n, _ = sim_root(lambda t: math.exp(-t) * (t - 1.0), -1.0, 40.0,
XTOL, budget, prototypes="pair2d", guard=True)
assert n <= budget
def test_root_already_at_an_endpoint_costs_two_evaluations():
for name, m in BASELINES.items():
x, n, conv = m(lambda t: t - 1.0, 1.0, 5.0, XTOL, MAXFEV)
assert conv and abs(x - 1.0) <= 1e-8, name
assert n <= 3, f"{name} spent {n} on a root sitting on the bracket end"
+124
View File
@@ -0,0 +1,124 @@
"""Tests for the three-point scale.
The claim being pinned: a divided difference is a COMPUTATION, not a fitted
coefficient, so `derived3` introduces no parameter; and it must actually lift
the order, otherwise the stage-4 explanation of the endgame deficit is wrong.
"""
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 GUARDS, PURE_SIMS, _slope3, pure_root
from stage5_three_point import PROBLEMS, order_of
XTOL = 1e-12
MAXFEV = 300
# --------------------------------------------------------------------------
# the divided difference itself
# --------------------------------------------------------------------------
@pytest.mark.parametrize("q", [
lambda t: 3.0 * t * t - 2.0 * t + 5.0,
lambda t: -1.5 * t * t + 0.25 * t - 7.0,
lambda t: t * t,
])
def test_slope3_is_exact_on_a_quadratic(q):
"""The derivative of the parabola through three points of a quadratic is
that quadratic's derivative, exactly. If this drifts, the order gain is
not what it is claimed to be."""
h = 1e-3
for x2 in (-2.0, 0.3, 4.0):
x1, x0 = x2 - h, x2 - 2.0 * h
s = _slope3(x2, q(x2), x1, q(x1), x0, q(x0))
exact = (q(x2 + 1e-7) - q(x2 - 1e-7)) / 2e-7
assert s == pytest.approx(exact, rel=1e-6), (x2, s, exact)
def test_slope3_is_exact_on_a_line():
f = lambda t: 4.0 * t - 1.0
s = _slope3(2.0, f(2.0), 1.0, f(1.0), 0.0, f(0.0))
assert s == pytest.approx(4.0)
def test_slope3_falls_back_to_two_points_when_the_third_is_missing():
f = lambda t: t * t
two = (f(2.0) - f(1.0)) / (2.0 - 1.0)
assert _slope3(2.0, f(2.0), 1.0, f(1.0), None, None) == pytest.approx(two)
# a repeated node must not divide by zero
assert math.isfinite(_slope3(2.0, f(2.0), 1.0, f(1.0), 1.0, f(1.0)))
assert math.isfinite(_slope3(2.0, f(2.0), 1.0, f(1.0), 2.0, f(2.0)))
def test_slope3_never_returns_nan_or_inf_on_degenerate_input():
for args in [(1.0, 0.0, 1.0, 0.0, 1.0, 0.0),
(1.0, 1e308, 0.5, -1e308, 0.0, 1e308),
(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)]:
s = _slope3(*args)
assert math.isfinite(s) or s == 0.0
# --------------------------------------------------------------------------
# the method built on it
# --------------------------------------------------------------------------
@pytest.mark.parametrize("guard", GUARDS)
@pytest.mark.parametrize("prob", PROBLEMS, ids=[p[0] for p in PROBLEMS])
def test_derived3_converges(prob, guard):
_, f, a, b, root = prob
x, n, conv = pure_root(f, a, b, XTOL, MAXFEV, scale="derived3", guard=guard)
assert conv, f"derived3.{guard} did not converge"
assert abs(x - root) <= 1e-8
assert n <= MAXFEV
@pytest.mark.parametrize("sim", sorted(PURE_SIMS))
def test_derived3_works_for_every_similarity(sim):
f = lambda t: t * t - 2.0
x, n, conv = pure_root(f, 0.0, 2.0, XTOL, MAXFEV, similarity=sim,
scale="derived3", guard="both")
assert conv and abs(x - math.sqrt(2.0)) <= 1e-8
def test_three_points_raise_the_order_above_two_points():
"""The whole point of the change. Measured on the problems where an
order can be estimated at all."""
two, three = [], []
for _n, f, a, b, root in PROBLEMS:
p2 = order_of(lambda f_, a_, b_, xt, mx: pure_root(
f_, a_, b_, XTOL, MAXFEV, scale="derived", guard="step"),
f, a, b, root)
p3 = order_of(lambda f_, a_, b_, xt, mx: pure_root(
f_, a_, b_, XTOL, MAXFEV, scale="derived3", guard="step"),
f, a, b, root)
if math.isfinite(p2) and math.isfinite(p3):
two.append(p2)
three.append(p3)
assert len(two) >= 5
m2 = sorted(two)[len(two) // 2]
m3 = sorted(three)[len(three) // 2]
assert 1.4 < m2 < 1.8, m2 # secant
assert m3 > m2 + 0.15, (m2, m3) # a real lift, not noise
def test_the_sound_guard_still_costs_the_order():
"""Honest pin of the tension found in stage 5: `both` carries the 2x
bisection bound and, on a method that shrinks the ERROR faster than the
BRACKET, that bound forces a bisection every other step and flattens the
order. This is a property of the guarantee, not a defect - but if
someone later claims high order AND the 2x bound from the same
configuration, this test says otherwise."""
f = lambda t: t * t - 2.0
root = math.sqrt(2.0)
p_step = order_of(lambda f_, a_, b_, xt, mx: pure_root(
f_, a_, b_, XTOL, MAXFEV, scale="derived3", guard="step"),
f, 0.0, 2.0, root)
p_both = order_of(lambda f_, a_, b_, xt, mx: pure_root(
f_, a_, b_, XTOL, MAXFEV, scale="derived3", guard="both"),
f, 0.0, 2.0, root)
assert p_step > p_both, (p_step, p_both)