"""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")