Files
sim-numerics/tests/test_similarity_per_slot.py

128 lines
5.6 KiB
Python

"""Pins for PREREG 2026-09-21 slot 1: the similarity function read BY PLACE.
Every claim the mode rests on is pinned here, including the ones that make it falsifiable:
the default must stay bit-for-bit published, the far reading must BE false position, and
the order exponent must invert the power law exactly.
"""
from __future__ import annotations
import math
import pytest
from simroot.pure import PURE_SIMS, _local_order, _read_z, pure_root
PROBS = (
(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: (t - 1.0) ** 3, 0.0, 10.0),
(lambda t: math.sin(t) - 0.5, 0.0, 1.5),
)
@pytest.mark.parametrize("f,a,b", PROBS)
@pytest.mark.parametrize("guard", ("safe", "credit", "project"))
def test_default_is_bit_for_bit_the_published_method(f, a, b, guard):
"""R1: the new parameter must not touch the published grid when left alone."""
ref = pure_root(f, a, b, 1e-12, 200, similarity="ratio", scale="derived3", guard=guard)
new = pure_root(f, a, b, 1e-12, 200, similarity="ratio", scale="derived3", guard=guard,
exponent="none")
assert ref == new
def test_far_reading_with_rational_is_false_position():
"""R2: Proposition 4 must hold in the CODE, not only in the paper.
The first similarity step from x with S = 1/(1+z), z = |f(x)|/|f(far)| must land on the
secant root of the pair (x, far). If it does not, the (far, none) cell is not the
control the pre-registration claims it is.
"""
f = lambda t: t ** 3 - 2.0 * t - 5.0 # noqa: E731
a, b = 1.0, 4.0
x = 0.5 * (a + b)
# the far end must be the one whose sign differs, otherwise there is no secant root
# between the pair at all; on this bracket that is a, not b
far, fx = a, f(x)
ffar = f(far)
assert fx * ffar < 0.0
z = abs(fx) / abs(ffar)
step = (1.0 - PURE_SIMS["rational"](z)) * (far - x)
secant = x - fx * (far - x) / (ffar - fx)
assert step == pytest.approx(secant - x, rel=1e-12)
def test_order_exponent_inverts_the_power_law_exactly():
"""R3: the whole derivation is z^(1/m) = e/E. Pinned as an identity."""
for m in (1.0, 2.0, 3.0, 7.0):
for e, big in ((0.1, 2.0), (1.0, 5.0), (1e-3, 1.0)):
z = (e / big) ** m
got = _read_z(z, 1.0, "order", m)
assert got == pytest.approx(e / big, rel=1e-9)
def test_order_exponent_is_inert_at_order_one():
"""A simple root must leave the reading untouched, or the endgame is not preserved."""
for z in (1e-9, 0.3, 1.0, 12.0):
assert _read_z(z, 1.0, "order", 1.0) == z
assert _read_z(z, 1.0, "order", None) == z
assert _read_z(z, 1.0, "none", 5.0) == z
def test_local_order_reads_a_cube_low_by_a_fixed_bias():
"""MEASURED, and pinned as measured: on a pure cube the three-point estimator returns
2.830, not 3. The parabola through three points carries only the second difference, so
a cubic's order is under-read by 5.7% even in the ideal noiseless case. The bias is
towards 1, i.e. towards the uncorrected reading, which is the safe direction — but it
is a bias and the paper must say so rather than claim the estimator is exact."""
f = lambda t: t ** 3 # noqa: E731
xs = (0.30, 0.31, 0.32)
m = _local_order(xs[2], f(xs[2]), xs[1], f(xs[1]), xs[0], f(xs[0]))
assert m == pytest.approx(2.83, rel=0.02)
assert 1.0 < m < 3.0
def test_step_fraction_can_never_leave_the_bracket():
"""L5: under S = 1/(1+z) the fraction z/(1+z) is at most 1, so the step never passes
the far end. At z >= 1/eps it SATURATES at exactly 1 in floating point — the step
lands ON the end, which the loop already turns into a bisection because x_new must be
strictly inside. Pinned so the saturation is a documented boundary, not a surprise."""
for z in (0.0, 1e-12, 1.0, 1e6):
assert 0.0 <= 1.0 - PURE_SIMS["rational"](z) < 1.0
assert 1.0 - PURE_SIMS["rational"](1e300) == 1.0
@pytest.mark.parametrize("f,a,b", PROBS)
def test_far_and_order_still_solve_and_stay_in_the_bracket(f, a, b):
ok_any = False
for ex in ("none", "order"):
x, n, conv = pure_root(f, a, b, 1e-10, 300, similarity="rational", scale="far",
guard="safe", exponent=ex)
assert min(a, b) <= x <= max(a, b)
assert n <= 300
ok_any = ok_any or conv
assert ok_any
def test_derived3_m_actually_divides_by_the_order():
"""REGRESSION, 2026-09-21. The derived3_cap branch was inserted in front of the
derived3_m note and swallowed its division, so derived3_m became a silent alias of
derived3 (2806 against 2806) and a refutation recorded in the paper stopped being
reproducible. One cheap problem where the order is genuinely above 1 is enough to
catch it, so the guard does not depend on the full 154-problem pin."""
f = lambda t: (t - 1.0) ** 3 # noqa: E731
plain = pure_root(f, 0.0, 10.0, 1e-10, 300, similarity="ratio", scale="derived3",
guard="project")
with_m = pure_root(f, 0.0, 10.0, 1e-10, 300, similarity="ratio", scale="derived3_m",
guard="project")
assert plain != with_m
def test_bad_combinations_are_refused_not_silently_reinterpreted():
with pytest.raises(ValueError):
pure_root(lambda t: t, 0.0, 1.0, 1e-9, 50, exponent="cube")
with pytest.raises(ValueError):
# two-point scale has no second difference to read the order from
pure_root(lambda t: t, 0.0, 1.0, 1e-9, 50, scale="derived", exponent="order")
with pytest.raises(ValueError):
pure_root(lambda t: t, 0.0, 1.0, 1e-9, 50, scale="nonesuch")