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