PREREG before any run: out-of-sample corpus and ablation for the two strongest variants. corpus_new.py (seed 20260921): SciPy 'original' (6) + five generated classes x 40 (smooth simple, transcendental simple, multiple/flat |x-r|^p p~U(2,20), exponential flank, near-cancelling terms), built and validity-checked only (no method has seen it). O1-O3 = transfers (total below Chandrupatla, per-problem sign test p<0.05, guarantee); O4 vs the plain rule; per class. Leave-one-out ablation of every component on APS, CHA and the new corpus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
vvs
2026-09-21 19:30:27 +01:00
co-authored by Claude Opus 5
parent bbb16ec81c
commit 47361dabea
2 changed files with 175 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
"""An OUT-OF-SAMPLE corpus for PREREG_out_of_sample_2026-09-21: problems no method variant
was developed on. Fixed before any method is run on it; seed and distributions below are
part of the pre-registration and are never changed afterwards.
Six classes. C0 is SciPy's 'original' collection (6 problems, never used here before).
C1-C5 are generated, 40 problems each, from one numpy Generator(seed=20260921):
C1 smooth simple root f = (x - r) * g(x), g = exp(a sin(c x)) + b x^2 > 0
C2 transcendental simple f = one of atan(k(x-r)), tanh(k(x-r)), expm1(k(x-r)), sinh(k(x-r))
C3 multiple / flat root f = sign(x-r) |x-r|^p * g(x), p ~ U(2, 20) (real, not only odd)
C4 exponential flank f = exp(k x) - exp(k r) (steep one-sided growth)
C5 near-cancelling terms f = exp(x) - e^r + a (1/x^2 - 1/r^2) - b (1/x^3 - 1/r^3), x > 0
(the kind of end that misled us on fun9, with random parameters)
Brackets: root r ~ U(-1, 1) (C5: r ~ U(0.3, 2)); width L log-uniform on [0.1, 1000] (C4:
capped so that k * right end <= 600, C5: left end >= r/1000); the root sits at a fraction
u ~ U(0.02, 0.98) of the bracket. Every problem is checked for finite values of opposite
sign at the ends; one that fails is redrawn (the redraw count is reported).
"""
from __future__ import annotations
import math
import numpy as np
SEED = 20260921
PER_CLASS = 40
def _bracket(rng, r, lo_w=0.1, hi_w=1000.0):
L = math.exp(rng.uniform(math.log(lo_w), math.log(hi_w)))
u = rng.uniform(0.02, 0.98)
return r - u * L, r + (1.0 - u) * L
def _ok(f, a, b):
try:
fa, fb = f(a), f(b)
except (OverflowError, ValueError, ZeroDivisionError):
return False
return (math.isfinite(fa) and math.isfinite(fb) and fa != 0.0 and fb != 0.0
and (fa < 0.0) != (fb < 0.0))
def _c1(rng):
r = rng.uniform(-1, 1)
a, b, c = rng.uniform(0, 1), rng.uniform(0, 0.5), rng.uniform(0.5, 5)
f = lambda x, r=r, a=a, b=b, c=c: (x - r) * (math.exp(a * math.sin(c * x)) + b * x * x)
return f, r, _bracket(rng, r)
def _c2(rng):
r = rng.uniform(-1, 1)
k = math.exp(rng.uniform(math.log(0.2), math.log(20)))
kind = int(rng.integers(4))
base = (math.atan, math.tanh, math.expm1, math.sinh)[kind]
a, b = _bracket(rng, r)
if kind in (2, 3): # keep exp/sinh finite on the bracket
span = max(abs(a - r), abs(b - r))
if k * span > 600:
k = 600 / span
return (lambda x, r=r, k=k, g=base: g(k * (x - r))), r, (a, b)
def _c3(rng):
r = rng.uniform(-1, 1)
p = rng.uniform(2, 20)
a, c = rng.uniform(0, 1), rng.uniform(0.5, 5)
lo, hi = _bracket(rng, r, hi_w=30.0) # |x - r|^20 stays finite
f = lambda x, r=r, p=p, a=a, c=c: math.copysign(abs(x - r) ** p, x - r) * math.exp(a * math.sin(c * x))
return f, r, (lo, hi)
def _c4(rng):
r = rng.uniform(-1, 1)
k = math.exp(rng.uniform(math.log(0.5), math.log(5)))
lo, hi = _bracket(rng, r)
if k * hi > 600:
hi = 600 / k
er = math.exp(k * r)
return (lambda x, k=k, er=er: math.exp(k * x) - er), r, (lo, hi)
def _c5(rng):
r = rng.uniform(0.3, 2.0)
a = math.exp(rng.uniform(math.log(1e-3), math.log(1e-1)))
b = a * rng.uniform(0.01, 0.5) * r
L = math.exp(rng.uniform(math.log(0.1), math.log(100)))
u = rng.uniform(0.02, 0.98)
lo, hi = max(r - u * L, r / 1000.0), r + (1.0 - u) * L
hi = min(hi, 600.0)
er = math.exp(r)
f = lambda x, r=r, a=a, b=b, er=er: (math.exp(x) - er + a * (1 / x ** 2 - 1 / r ** 2)
- b * (1 / x ** 3 - 1 / r ** 3))
return f, r, (lo, hi)
def build():
"""[(class, id, f, a, b, root)] and the number of redraws per class."""
from scipy.optimize._tstutils import _ORIGINAL_TESTS_DICTS
out, redraws = [], {}
for i, d in enumerate(_ORIGINAL_TESTS_DICTS):
a, b = d["bracket"]
args = d.get("args", ())
f = (lambda ff, aa: lambda x: ff(x, *aa))(d["f"], args)
out.append(("C0", f"C0.{i:02d}", f, float(a), float(b), float(d["root"])))
rng = np.random.default_rng(SEED)
for cls, gen in (("C1", _c1), ("C2", _c2), ("C3", _c3), ("C4", _c4), ("C5", _c5)):
n = red = 0
while n < PER_CLASS:
f, r, (a, b) = gen(rng)
if _ok(f, a, b) and min(a, b) < r < max(a, b):
out.append((cls, f"{cls}.{n:02d}", f, float(a), float(b), float(r)))
n += 1
else:
red += 1
redraws[cls] = red
return out, redraws
if __name__ == "__main__":
probs, red = build()
from collections import Counter
print("problems per class:", dict(Counter(p[0] for p in probs)), " redraws:", red)