diff --git a/PREREG_close_sub_2026-09-21.md b/PREREG_close_sub_2026-09-21.md new file mode 100644 index 0000000..1a8a899 --- /dev/null +++ b/PREREG_close_sub_2026-09-21.md @@ -0,0 +1,50 @@ +# PREREG — два пробела базового шага: закрытие скобки и корни порядка < 1; проверка на 4-м корпусе + +Составлено до кода и до прогона, ветка `signfix-and-gates`. Валерий: «пред-регистрируй и чини оба +пробела, проверка на четвёртом корпусе». + +## Основание (`c2b0eea`, прикладной корпус) + +1. **Нет закрытия скобки.** sin[3, 4]: корень достигнут за 7 вызовов (|f| ≈ 10⁻¹⁶), дальний конец + остался в 1.3·10⁻⁷ — ещё 17 делений до 10⁻¹². Чандрупатла ставит точку на допуск за корень. +2. **Корень порядка m < 1** (√|x|·sgn x): шаг перелетает и качается (+0.15, −0.10, +0.066 …, + сжатие ≈ 0.8), 84 против 4. Флаг видит только недолёт без смены знака; режим — только m ≥ 2. + +## Правки (составляющие `close` и `sub` составного `prestep`) + +**`close` — ЗАИМСТВОВАНО (Деккер 1969, Брент 1973), в статье называется так.** Если шаг модели +короче xtol/2, он заменяется шагом ровно xtol/2 в сторону дальнего конца: если модель права и +корень ближе xtol/2, новая точка оказывается за корнем и скобка сжимается до допуска. +xtol — допуск пользователя, ½ — константа деления; других констант нет. + +**`sub` — вторая ветвь того же неравенства.** Касательный шаг на подходе порядка m уменьшает ошибку +по x в |1 − 1/m| раз; он проигрывает делению пополам при |1 − 1/m| > ½, т.е. при **m > 2** (недолёт — +уже используется) **или m < 2/3** (перелёт). `sub` добавляет вторую ветвь: плоский режим входит и +тогда, когда аналогичная пара читает **m ≤ 2/3**, а флаг — шаг модели с |f_new/f_old| ≥ ¼ **со сменой +знака** (перелёт; для недолёта флаг прежний). В режиме точка из порядка +t = |FA|^(1/m)/(|FA|^(1/m) + |FB|^(1/m)) — при m = ½ точна для чистой степени. Выход — прежний. + +Правки добавляются к варианту **A** (Г2 + `contra+flatanalogmempt+exp+half`) → **A′** = +Г2 + `contra+flatanalogmempt+exp+half+close+sub`. + +## Разработка — только на увиденных корпусах + +Реализация, отладка и смоук — на APS, CHA, новом корпусе 1, Roots.jl и прикладном. Итоговый код A′ +**коммитится до** прогона на четвёртом корпусе; после — никаких изменений. + +## Проверка — четвёртый корпус (`corpus_fourth.py`, зерно 20260922, закоммичен сейчас) + +240 задач, новые семейства по 40: D1 случайный многочлен (простой корень), D2 многочлен с кратным +корнем 3–9, **D3 дробная степень |x − r|^p, p ~ U(0.15, 0.95) — целевой для `sub`**, D4 суммы экспонент +и синусов, D5 композиции tanh/atan/log1p от кубики, **D6 нечётные относительно корня (sin, x + ax³, +arcsinh) — целевой для `close`**. Успех — смена знака в пределах 10⁻⁶; XTOL 10⁻¹², MAXFEV 200. + +| # | ПРОЙДЕНО, если | +|---|---| +| **F1** | сумма A′ на корпусе 4 меньше, чем у Чандрупатлы | +| **F2** | по задачам против Чандрупатлы побед больше, чем поражений, знаковый тест p < 0.05 | +| **F3** | гарантия: 0 нарушений; решено не меньше, чем у Чандрупатлы | +| **F4** | правки заслужены: A′ дешевле A на корпусе 4; абляция — снять `close`, снять `sub` по одной, Δ сообщается | + +«A′ переносится» — F1, F2, F3 одновременно. Сообщается также вывод **без целевых классов** +(D1, D2, D4, D5), против Брента и TOMS748, и A′ на всех увиденных корпусах (описательно). diff --git a/corpus_fourth.py b/corpus_fourth.py new file mode 100644 index 0000000..f2fe199 --- /dev/null +++ b/corpus_fourth.py @@ -0,0 +1,149 @@ +"""The FOURTH out-of-sample corpus for PREREG_close_sub_2026-09-21 - written and committed before +the two repairs are implemented and before any method is run on it. Seed 20260922; the +function families are NEW (none of corpus_new's closed forms is reused). 40 per class: + + D1 random polynomial, one simple root in the bracket (others outside) - neutral + D2 random polynomial with one MULTIPLE root of odd multiplicity 3..9 - flat side + D3 fractional power |x - r|^p sign(x - r) * g(x), p ~ U(0.15, 0.95) - TARGETED (repair 2) + D4 sums of exponentials and sines with one root in the bracket - neutral + D5 compositions tanh(q(x)), atan(q(x)), log1p(q(x)^2) - c with a cubic q - neutral + D6 odd around the root: s(x - r) with s in {sin(k.), (.)+a(.)^3, arcsinh(k.)} - TARGETED (repair 1) + +Roots are known by construction for D1-D3 and D6; for D4-D5 success is judged by a sign +change within 1e-6 (corpus_roots_jl.solved), which is used for every class for uniformity. +Brackets: width log-uniform on [0.1, 100], the root at a fraction U(0.02, 0.98). A problem +without finite values of opposite sign at the ends is redrawn (redraws reported). +""" +from __future__ import annotations + +import math + +import numpy as np + +SEED = 20260922 +PER_CLASS = 40 + + +def _bracket(rng, r, lo=0.1, hi=100.0): + L = math.exp(rng.uniform(math.log(lo), math.log(hi))) + 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 _poly_from_roots(roots, scale): + def f(x, roots=tuple(roots), scale=scale): + v = scale + for q in roots: + v *= (x - q) + return v + return f + + +def _d1(rng): + r = rng.uniform(-1, 1) + a, b = _bracket(rng, r) + deg = int(rng.integers(3, 10)) + others = [] + while len(others) < deg - 1: # the other roots outside the bracket, or complex pairs + if rng.uniform() < 0.5 and len(others) <= deg - 3: + c, d = rng.uniform(-3, 3), rng.uniform(0.1, 3) + others.append(("c", c, d)) + others.append(None) + else: + side = a - rng.uniform(0.1, 5) if rng.uniform() < 0.5 else b + rng.uniform(0.1, 5) + others.append(("r", side)) + others = [o for o in others if o is not None][: deg - 1] + + def f(x, r=r, others=tuple(others)): + v = x - r + for o in others: + v *= (x - o[1]) if o[0] == "r" else ((x - o[1]) ** 2 + o[2] ** 2) + return v + return f, r, (a, b) + + +def _d2(rng): + r = rng.uniform(-1, 1) + m = int(rng.choice([3, 5, 7, 9])) + a, b = _bracket(rng, r, hi=10.0) + c = rng.uniform(0.5, 3) + return (lambda x, r=r, m=m, c=c: (x - r) ** m * (1 + c * x * x)), r, (a, b) + + +def _d3(rng): + r = rng.uniform(-1, 1) + p = rng.uniform(0.15, 0.95) + a0, c = rng.uniform(0, 1), rng.uniform(0.5, 5) + a, b = _bracket(rng, r) + return (lambda x, r=r, p=p, a0=a0, c=c: + math.copysign(abs(x - r) ** p, x - r) * math.exp(a0 * math.sin(c * x))), r, (a, b) + + +def _d4(rng): + r = rng.uniform(-1, 1) + k1, k2 = rng.uniform(-2, 2), rng.uniform(-2, 2) + w, ph = rng.uniform(0.5, 4), rng.uniform(0, 2 * math.pi) + a, b = _bracket(rng, r, hi=20.0) + + def g(x, k1=k1, k2=k2, w=w, ph=ph): + return math.exp(k1 * x) + 0.5 * math.exp(k2 * x) + math.sin(w * x + ph) + gr = g(r) + slope = abs(k1) + abs(k2) + w + 1.0 + return (lambda x, g=g, gr=gr, r=r, s=slope: g(x) - gr + 1.5 * s * (x - r)), r, (a, b) + + +def _d5(rng): + r = rng.uniform(-1, 1) + c1, c2 = rng.uniform(0.5, 3), rng.uniform(-1, 1) + kind = int(rng.integers(3)) + a, b = _bracket(rng, r, hi=10.0) + q = lambda x, r=r, c1=c1, c2=c2: c1 * (x - r) + c2 * (x - r) ** 2 + (x - r) ** 3 + if kind == 0: + return (lambda x, q=q: math.tanh(q(x))), r, (a, b) + if kind == 1: + return (lambda x, q=q: math.atan(q(x))), r, (a, b) + return (lambda x, q=q: math.copysign(math.log1p(q(x) ** 2), q(x))), r, (a, b) + + +def _d6(rng): + r = rng.uniform(-1, 1) + k = math.exp(rng.uniform(math.log(0.3), math.log(3))) + kind = int(rng.integers(3)) + a, b = _bracket(rng, r, hi=3.0 / k if kind == 0 else 100.0) # sin: a single root + if kind == 0: + return (lambda x, r=r, k=k: math.sin(k * (x - r))), r, (a, b) + if kind == 1: + aa = rng.uniform(0.1, 2) + return (lambda x, r=r, aa=aa: (x - r) + aa * (x - r) ** 3), r, (a, b) + return (lambda x, r=r, k=k: math.asinh(k * (x - r))), r, (a, b) + + +def build(): + out, redraws = [], {} + rng = np.random.default_rng(SEED) + for cls, gen in (("D1", _d1), ("D2", _d2), ("D3", _d3), ("D4", _d4), ("D5", _d5), ("D6", _d6)): + 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__": + from collections import Counter + probs, red = build() + print(len(probs), dict(Counter(p[0] for p in probs)), "redraws", red)