Impact, every sweep regenerated: the paper's key configurations are unchanged to the evaluation (proposed 2806/3220, credit 3568/3020, Chandrupatla, TOMS 748, Brent, bisection); 46 stated numbers move (ITP CHA 2857 -> 2896, ITP oracle 2611 -> 2647, record CHA figures by 14-18, the grid means by ~1%) and one finding weakens: the measured-order exponent of section 7.1 was cheaper in 29 of 60 far cells and 7 of 7 safeguards; with the fix 12 of 61 and 6 of 7, and on the wide-bracket set it now loses in every safeguard and drops 5 problems. PREREG G1-G3 outcome on the fixed code: G2 (repeating step pattern, period from the data) PASSES Y1-Y4 and is ACCEPTED by the stop rule - proposed rule APS 2806 -> 2782 (below Chandrupatla's 2787), CHA 3220 -> 3169, both 6026 -> 5951, per problem vs Chandrupatla on APS 95/20/38 p = 8e-7. G1 fails Y1/Y2 (its Y3 miss was the sign bug), G3 fails Y2, A fails Y2. Regression 887 pass; the two red tests are the paper-number check (awaiting the paper update) and test_blend (fixed since, copy in stage16). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
119 lines
5.0 KiB
Python
119 lines
5.0 KiB
Python
"""Pins for PREREG_extrapolate_in_method_2026-09-21.md: step extrapolation (A)."""
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
|
|
import pytest
|
|
|
|
from simroot.pure import pure_root
|
|
|
|
SMOOTH = (
|
|
(lambda t: t ** 3 - 2.0 * t - 5.0, 1.0, 4.0),
|
|
(lambda t: math.exp(t) - 2.0, 0.0, 3.0),
|
|
(lambda t: math.sin(t) - 0.5, 0.0, 1.5),
|
|
)
|
|
FLAT = (
|
|
(lambda t: 6.0 * (t - 2.0) ** 5, 0.0, 10.0),
|
|
(lambda t: t ** 9, -1.0, 2.0),
|
|
(lambda t: (t - 3.0) ** 3, 0.0, 10.0),
|
|
)
|
|
XTOL, MAXFEV = 1e-12, 200
|
|
|
|
|
|
@pytest.mark.parametrize("f,a,b", SMOOTH + FLAT)
|
|
@pytest.mark.parametrize("guard", ("project", "safe", "credit"))
|
|
def test_default_is_the_published_method_bit_for_bit(f, a, b, guard):
|
|
ref = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3", guard=guard)
|
|
off = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3", guard=guard,
|
|
extrapolate="none")
|
|
assert ref == off
|
|
|
|
|
|
@pytest.mark.parametrize("f,a,b", SMOOTH + FLAT)
|
|
@pytest.mark.parametrize("guard", ("project", "credit"))
|
|
def test_the_guarantee_survives_extrapolation(f, a, b, guard):
|
|
x, n, ok = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3",
|
|
guard=guard, extrapolate="steps")
|
|
assert ok and min(a, b) <= x <= max(a, b)
|
|
half = math.ceil(math.log2(abs(b - a) / XTOL))
|
|
bound = 2 * half + 3 if guard == "project" else half + 4
|
|
assert n <= bound, (n, bound)
|
|
|
|
|
|
@pytest.mark.parametrize("f,a,b", FLAT[1:])
|
|
def test_extrapolation_breaks_the_flat_cycle(f, a, b):
|
|
"""The mechanism the pre-registration rests on: on a pure power the similar successive
|
|
undershoots are replaced by a longer step, and the run gets cheaper."""
|
|
_x, n_off, _ = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3",
|
|
guard="project")
|
|
_x, n_on, _ = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3",
|
|
guard="project", extrapolate="steps")
|
|
assert n_on < n_off, (n_on, n_off)
|
|
|
|
|
|
def test_on_a_budget_bound_run_extrapolation_changes_the_path_not_the_count():
|
|
"""MEASURED, pinned as measured: on 6(x-2)^5 over [0, 10] both runs cost 90 evaluations
|
|
against a projection guarantee of 2*44 + 3 = 91 - the budget schedule, not the step,
|
|
decides when the run ends. The paths differ from the fourth evaluation on (87
|
|
positions), so the mode is active; its gain is absorbed by the safeguard here. The
|
|
expectation written first (strictly fewer evaluations) was wrong for this problem."""
|
|
f, a, b = FLAT[0]
|
|
ev = {}
|
|
for mode in ("none", "steps"):
|
|
seen = []
|
|
|
|
def g(t, _s=seen):
|
|
_s.append(t)
|
|
return f(t)
|
|
_x, n, ok = pure_root(g, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3",
|
|
guard="project", extrapolate=mode)
|
|
assert ok
|
|
ev[mode] = (n, seen)
|
|
assert ev["steps"][0] <= ev["none"][0]
|
|
assert ev["steps"][1] != ev["none"][1]
|
|
|
|
|
|
GATES = ("confirmed", "pattern", "steps+results")
|
|
|
|
|
|
@pytest.mark.parametrize("mode", GATES)
|
|
@pytest.mark.parametrize("f,a,b", SMOOTH + FLAT)
|
|
@pytest.mark.parametrize("guard", ("project", "credit"))
|
|
def test_every_gate_keeps_the_guarantee(mode, f, a, b, guard):
|
|
x, n, ok = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3",
|
|
guard=guard, extrapolate=mode)
|
|
assert ok and min(a, b) <= x <= max(a, b)
|
|
half = math.ceil(math.log2(abs(b - a) / XTOL))
|
|
assert n <= (2 * half + 3 if guard == "project" else half + 4), (mode, n)
|
|
|
|
|
|
def test_the_gates_decline_without_history():
|
|
"""Each gate needs history to confirm anything; without it the step is untouched."""
|
|
from simroot.pure import _extrapolation_s
|
|
# opposite directions -> nothing to confirm, in every mode
|
|
for mode in ("steps",) + GATES:
|
|
assert _extrapolation_s(mode, 0.1, 1.0, 1.5, None, 1.0, 2.0, None, []) == 0.0
|
|
# same direction, but G1 has no third point, G3 no third value, G2 no history
|
|
assert _extrapolation_s("confirmed", 0.1, 1.0, 0.8, None, 1.0, 2.0, None, []) == 0.0
|
|
assert _extrapolation_s("steps+results", 0.1, 1.0, 0.8, None, 1.0, 2.0, None, []) == 0.0
|
|
assert _extrapolation_s("pattern", 0.1, 1.0, 0.8, None, 1.0, 2.0, None, [0.2]) == 0.0
|
|
|
|
|
|
def test_results_after_a_crossing_confirm_nothing():
|
|
"""G3: a sign change of f between the last results is a crossing, not a creep."""
|
|
from simroot.pure import _extrapolation_s
|
|
assert _extrapolation_s("steps+results", 0.1, 1.0, 0.9, 0.8, 1.0, -2.0, 4.0, []) == 0.0
|
|
assert _extrapolation_s("steps+results", 0.1, 1.0, 0.9, 0.8, 1.0, 2.0, 4.0, []) > 0.0
|
|
|
|
|
|
def test_a_confirmed_geometric_series_is_read_exactly():
|
|
"""G1 on three moves in ratio 0.5, 0.5: fully confirmed, s = q2 = 0.5."""
|
|
from simroot.pure import _extrapolation_s
|
|
s = _extrapolation_s("confirmed", 0.025, 1.0, 0.95, 0.85, 1.0, 2.0, 4.0, [])
|
|
assert s == pytest.approx(0.5)
|
|
|
|
|
|
def test_unknown_mode_is_refused():
|
|
with pytest.raises(ValueError):
|
|
pure_root(lambda t: t, -1.0, 1.0, XTOL, MAXFEV, extrapolate="aitken")
|