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
+51
View File
@@ -0,0 +1,51 @@
# PREREG — проверка двух сильнейших вариантов: абляция и новый корпус
Составлено до прогона, ветка `signfix-and-gates`. Валерий: «результат сильный и уже достоин
статьи»; «давай проверим оба сильнейших варианта».
## Зачем
Все решения сегодня принимались на тех же 199 задачах (APS 154 + Chandrupatla 45). Предрегистрация
защищает каждый шаг, но не цепочку: итоговые ячейки отобраны по этим наборам. Нужны (1) абляция
каждой составляющей и (2) задачи, на которых ничего не разрабатывалось.
## Варианты (проекция, tent, трёхточечный масштаб)
- **A** = Г2 + `contra+flatanalogmempt+exp+half` (APS 2559, CHA 2047)
- **B** = `defect+flatanalogmempt+exp`, без Г2 (APS 2531, CHA 1972)
- справочно: предложенный метод без добавок; соперники — библиотечные Чандрупатла
(`elementwise.find_root`), Брент (`brentq`), TOMS748.
## Часть 1 — новый корпус (`corpus_new.py`, зерно 20260921, закоммичен до прогона)
206 задач, не использованных в разработке: C0 — набор SciPy `original` (6); C1–C5 по 40,
параметры из заранее заданных распределений: C1 гладкий простой корень, C2 трансцендентный простой
(atan/tanh/expm1/sinh), C3 кратный/плоский корень |x−r|^p, p ~ U(2, 20), C4 экспоненциальный склон,
C5 почти сокращающиеся члены (как у fun9, параметры случайные). Корень известен по построению.
Учёт тот же: XTOL 10⁻¹², MAXFEV 200, решено при |x − r| ≤ 10⁻⁶, неудача = 200.
Для каждого варианта V ∈ {A, B}:
| # | ПРОЙДЕНО, если |
|---|---|
| **O1** | сумма вызовов V на корпусе меньше, чем у Чандрупатлы |
| **O2** | по задачам против Чандрупатлы побед больше, чем поражений, знаковый тест p < 0.05 |
| **O3** | гарантия: 0 нарушений (≤ 2⌈log₂(w/xtol)⌉ + 4 на решённых); решено не меньше, чем у Чандрупатлы |
| **O4** | сумма V меньше, чем у предложенного метода без добавок (добавки переносятся) |
**Вывод «переносится на новые задачи»** — только при O1, O2, O3 одновременно. Сообщается всё, по
каждому классу отдельно, против всех трёх соперников. **Никаких изменений метода после этого
прогона в рамках этой проверки**; корпус и критерии не меняются.
Классы C3–C5 близки к тому, на чём мы искали (плоские, экспонента, сокращение); C0–C2 — нейтральны
и выгодны модели Чандрупатлы. Вывод сообщается и отдельно по C0–C2.
## Часть 2 — абляция (APS, CHA и новый корпус)
Снимается по одной составляющей, Δ = (без неё) − (с ней), плюс = составляющая заслужена:
- A: Г2; `contra`; `half`; `exp`; точка (`flatanalogmempt` → `flatanalogmem`); весь плоский режим
(снять `flatanalogmempt` и `exp`).
- B: `defect`; `exp`; точка; весь плоский режим.
Составляющая с Δ ≤ 0 на всех трёх наборах **не заслужена** и из предложения убирается.
+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)