Files
sim-numerics/tests/test_credit_guard.py
T
vvsandClaude Opus 5 1b6c626f67 Valeri's decisions (1) competitors from the library, (2) variant a. (1) Chandrupatla is scipy.optimize.elementwise.find_root, Brent is brentq, no more per-problem best_of. Our Chandrupatla stopped on a bracket twice as wide as every other method (the factor the protocol corrects for ITP); fixed (+0.5*xtol) in baselines.py and in the two copies found by grep (stage16_blend, stage19_modern) and the traced mirror, and pinned: ours == library on every problem. Regenerating sweep_full.json changed exactly three rows - chandrupatla APS 2785->2787, CHA 2423->2441, brent CHA 4099->4115 - and nothing else (diff_sweeps.py: 0 unexpected).
(2) The proposed method is again the scheduled projection (2806/3220/6026), its budget multiplier DECLARED as the one free constant: the canonical total is the same for every multiplier 1.25-4, the wide-bracket one is not. The earned projection is the constant-free variant (3568/3020/6588). What the old tables hid: problem by problem the proposed rule beats Chandrupatla on the canonical set 87/24/42, exact sign test p = 9e-5, while trailing him by 0.7% in total; on the wide-bracket set he is genuinely stronger (26 to 9, p = 0.006). Gap decomposition recomputed with the library: 244 (was 262), 160 of it on the flat families (two thirds), fun1 58 (a quarter, not a third as the abstract said).

Also corrected on the way: the 'subsumed by Chandrupatla' pin claimed he is better on the worst single problem - true only with the looser tolerance (84 against the credit rule's 83 once fixed); §8.3 still called zero slack degenerate, which Table 1a refutes; §5.2's Brent figure is our pure-Python implementation and now says so. verify_article.py 286/286, 802 tests pass twice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 10:53:00 +01:00

248 lines
10 KiB
Python

"""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 behind the paper's declaring the multiplier a free constant
(and offering the earned projection as the constant-free variant).
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