From 97af3ae7673e7b62148a69bbac1174a145593082 Mon Sep 17 00:00:00 2001 From: Valeri Sitnikov Date: Mon, 21 Sep 2026 19:38:26 +0100 Subject: [PATCH] PREREG before any run: second independent corpus - the Roots.jl bracketing benchmark (from the ModAB benchmark, Ganchovski & Traykov), f01-f91 transcribed; 13 problems equal to APS/Chandrupatla functions excluded objectively (7-point identity test), verdict on the other 78; strict view f24-f91; success = sign change within 1e-6 (no roots given, jumps and poles present). R1-R3 as in the first out-of-sample check. Co-Authored-By: Claude Opus 5 --- PREREG_roots_jl_2026-09-21.md | 38 +++++++ corpus_roots_jl.py | 180 ++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 PREREG_roots_jl_2026-09-21.md create mode 100644 corpus_roots_jl.py diff --git a/PREREG_roots_jl_2026-09-21.md b/PREREG_roots_jl_2026-09-21.md new file mode 100644 index 0000000..823cbab --- /dev/null +++ b/PREREG_roots_jl_2026-09-21.md @@ -0,0 +1,38 @@ +# PREREG — второй независимый корпус: эталон методов со скобкой Roots.jl / ModAB + +Составлено до прогона, ветка `signfix-and-gates`. Валерий: «давай проверим ещё на наборе задач». + +## Корпус (`corpus_roots_jl.py`, закоммичен до прогона) + +Опубликованный эталон: Roots.jl `test/benchmark-bracketing-methods.jl` (коммит cf1d256, +2026-03-30), взятый из эталона метода ModAB (Ganchovski & Traykov, +github.com/Proektsoftbg/Numerical). Задачи f01–f91 перенесены с Julia (f92–f93 с бесконечной скобкой +не берутся: ни один сравниваемый метод её не принимает). Все 91 имеют разные знаки на концах. + +- **Исключение пересечений с наборами разработки** — объективно: функция совпадает с функцией APS + или Chandrupatla до 10⁻¹² в 7 фиксированных точках скобки. Найдено 13 (f05, f06, f08, f12–f15, + f17–f20, f41, f62). **Основной вывод — по 78 оставшимся.** +- **Строгий вариант** (сообщается): только f24–f91 — вне блока f01–f23, повторяющего семейства APS + с другими параметрами. +- **Успех** — корней источник не даёт, есть разрывы и полюса: в пределах 10⁻⁶ от ответа функция + меняет знак (или ответ — точный ноль). XTOL 10⁻¹², MAXFEV 200, неудача = 200; гарантия + ≤ 2⌈log₂(w/xtol)⌉ + 4 на решённых. + +## Варианты и критерии — те же, что в PREREG_out_of_sample + +**A** = Г2 + `contra+flatanalogmempt+exp+half`; **B** = `defect+flatanalogmempt+exp`; справочно — +предложенный метод без добавок; соперники — библиотечные Чандрупатла, Брент, TOMS748. + +| # | ПРОЙДЕНО, если (на 78 задачах) | +|---|---| +| **R1** | сумма вызовов V меньше, чем у Чандрупатлы | +| **R2** | по задачам против Чандрупатлы побед больше, чем поражений, знаковый тест p < 0.05 | +| **R3** | гарантия: 0 нарушений; решено не меньше, чем у Чандрупатлы | + +«Переносится» — только при R1, R2, R3. Метод не меняется; корпус и критерии не меняются. +Сообщаются: все три соперника, строгий вариант f24–f91, 13 пересекающихся задач отдельно, задачи, +которые не решил хоть кто-то. + +**Ожидание, записано заранее:** в корпусе много разрывных и патологических задач (f38–f40, f76–f83), +где ни у одной модели нет преимущества и все близки к делению пополам; кратных корней меньше, чем в +нашем корпусе C3 (f24, f25, f26, f43–f45, f72–f74) — там, по прошлому опыту, наш главный выигрыш. diff --git a/corpus_roots_jl.py b/corpus_roots_jl.py new file mode 100644 index 0000000..0bb655d --- /dev/null +++ b/corpus_roots_jl.py @@ -0,0 +1,180 @@ +"""A second OUT-OF-SAMPLE corpus for PREREG_roots_jl_2026-09-21: the bracketing benchmark of +Roots.jl (test/benchmark-bracketing-methods.jl, commit cf1d256, 2026-03-30), itself taken from +the ModAB benchmark of Ganchovski & Traykov (github.com/Proektsoftbg/Numerical). Problems +f01-f91 transcribed from Julia; the infinite-bracket problems (f92, f93) are left out because +none of the compared methods takes an infinite bracket. + +Julia semantics kept: sign(0) = 0, 2x = 2*x, the branches as written. No roots are given +by the source; success is judged by a sign change within 1e-6 of the answer (see PREREG). +""" +from __future__ import annotations + +import math + + +def _sign(v): + return 0.0 if v == 0 else math.copysign(1.0, v) + + +def _P(x): + return x + 1.11111 + + +e = math.e +pi = math.pi + +PROBLEMS = [ + ("f01", lambda x: x ** 3 - 1, 0.5, 1.5), + ("f02", lambda x: x ** 2 * (x ** 2 / 3 + math.sqrt(2) * math.sin(x)) - math.sqrt(3) / 18, 0.1, 1), + ("f03", lambda x: 11 * x ** 11 - 1, 0.1, 1), + ("f04", lambda x: x ** 3 + 1, -1.8, 0), + ("f05", lambda x: x ** 3 - 2 * x - 5, 2, 3), + ("f06", lambda x: 2 * x * math.exp(-5) + 1 - 2 * math.exp(-5 * x), 0, 1), + ("f07", lambda x: 2 * x * math.exp(-10) + 1 - 2 * math.exp(-10 * x), 0, 1), + ("f08", lambda x: 2 * x * math.exp(-20) + 1 - 2 * math.exp(-20 * x), 0, 1), + ("f09", lambda x: (1 + (1 - 5) ** 2) * x ** 2 - (1 - 5 * x) ** 2, 0, 1), + ("f10", lambda x: (1 + (1 - 10) ** 2) * x ** 2 - (1 - 10 * x) ** 2, 0, 1), + ("f11", lambda x: (1 + (1 - 20) ** 2) * x ** 2 - (1 - 20 * x) ** 2, 0, 1), + ("f12", lambda x: x ** 2 - (1 - x) ** 5, 0, 1), + ("f13", lambda x: x ** 2 - (1 - x) ** 10, 0, 1), + ("f14", lambda x: x ** 2 - (1 - x) ** 20, 0, 1), + ("f15", lambda x: (1 + (1 - 5) ** 4) * x - (1 - 5 * x) ** 4, 0, 1), + ("f16", lambda x: (1 + (1 - 10) ** 4) * x - (1 - 10 * x) ** 4, 0, 1), + ("f17", lambda x: (1 + (1 - 20) ** 4) * x - (1 - 20 * x) ** 4, 0, 1), + ("f18", lambda x: math.exp(-5 * x) * (x - 1) + x ** 5, 0, 1), + ("f19", lambda x: math.exp(-10 * x) * (x - 1) + x ** 10, 0, 1), + ("f20", lambda x: math.exp(-20 * x) * (x - 1) + x ** 20, 0, 1), + ("f21", lambda x: x ** 2 + math.sin(x / 5) - 1 / 4, 0, 1), + ("f22", lambda x: x ** 2 + math.sin(x / 10) - 1 / 4, 0, 1), + ("f23", lambda x: x ** 2 + math.sin(x / 20) - 1 / 4, 0, 1), + ("f24", lambda x: (x + 2) * (x + 1) * (x - 3) ** 3, 2.6, 4.6), + ("f25", lambda x: (x - 4) ** 5 * math.log(x), 3.6, 5.6), + ("f26", lambda x: (math.sin(x) - x / 4) ** 3, 2, 4), + ("f27", lambda x: (81 - _P(x) * (108 - _P(x) * (54 - _P(x) * (12 - _P(x))))) * _sign(_P(x) - 3), 1, 3), + ("f28", lambda x: math.sin((x - 7.143) ** 3), 7, 8), + ("f29", lambda x: math.exp((x - 3) ** 5) - 1, 2.6, 4.6), + ("f30", lambda x: math.exp((x - 3) ** 5) - math.exp(x - 1), 4, 5), + ("f31", lambda x: pi - 1 / x, 0.05, 5), + ("f32", lambda x: 4 - math.tan(x), 0, 1.5), + ("f33", lambda x: math.cos(x) - x ** 3, 0, 4), + ("f34", lambda x: math.cos(x) - x, -11, 9), + ("f35", lambda x: math.sqrt(abs(x - 2 / 3)) * (1 if x <= 2 / 3 else -1) - 0.1, -11, 9), + ("f36", lambda x: abs(x - 2 / 3) ** 0.2 * (1 if x <= 2 / 3 else -1), -11, 9), + ("f37", lambda x: (x - 7 / 9) ** 3 + (x - 7 / 9) * 1e-3, -11, 9), + ("f38", lambda x: -0.5 if x <= 1 / 3 else 0.5, -11, 9), + ("f39", lambda x: -1e-3 if x <= 1 / 3 else 1 - 1e-3, -11, 9), + ("f40", lambda x: 0.0 if x == 0 else 1 / (x - 2 / 3), -11, 9), + ("f41", lambda x: 2 * x * math.exp(-5) - 2 * math.exp(-5 * x) + 1, 0, 10), + ("f42", lambda x: (x ** 2 - x - 6) * (x ** 2 - 3 * x + 2), 0, pi), + ("f43", lambda x: x ** 3, -1, 1.5), + ("f44", lambda x: x ** 5, -1, 1.5), + ("f45", lambda x: x ** 7, -1, 1.5), + ("f46", lambda x: (math.exp(-5 * x) - x - 0.5) / x ** 5, 0.09, 0.7), + ("f47", lambda x: 1 / math.sqrt(x) - 2 * math.log(5e3 * math.sqrt(x)) + 0.8, 0.0005, 0.5), + ("f48", lambda x: 1 / math.sqrt(x) - 2 * math.log(5e7 * math.sqrt(x)) + 0.8, 0.0005, 0.5), + ("f49", lambda x: (-x ** 3 - x - 1) if x <= 0 else (x ** (1 / 3) - x - 1), -1, 1), + ("f50", lambda x: x ** 3 - 2 * x - x + 3, -3, 2), + ("f51", lambda x: math.log(x), 0.5, 5), + ("f52", lambda x: (10 - x) * math.exp(-10 * x) - x ** 10 + 1, 0.5, 8), + ("f53", lambda x: math.exp(math.sin(x)) - x - 1, 1.0, 4), + ("f54", lambda x: 2 * math.sin(x) - 1, 0.1, pi / 3), + ("f55", lambda x: (x - 1) * math.exp(-x), 0.0, 1.5), + ("f56", lambda x: (x - 1) ** 3 - 1, 1.5, 3), + ("f57", lambda x: math.exp(x ** 2 + 7 * x - 30) - 1, 2.6, 3.5), + ("f58", lambda x: math.atan(x) - 1, 1.0, 8), + ("f59", lambda x: math.exp(x) - 2 * x - 1, 0.2, 3), + ("f60", lambda x: math.exp(-x) - x - math.sin(x), 0.0, 2), + ("f61", lambda x: x ** 2 - math.sin(x) ** 2 - 1, -1, 2), + ("f62", lambda x: math.sin(x) - x / 2, pi / 2, pi), + ("f63", lambda x: x * math.exp(x) - 1, -1, 1), + ("f64", lambda x: math.tan(x - 1 / 10), -1, 1), + ("f65", lambda x: math.sin(x) + 0.5, -1, 1), + ("f66", lambda x: 4 * x ** 5 + x * x + 1, -1, 1), + ("f67", lambda x: x + x ** 10 - 1, -1, 1), + ("f68", lambda x: pi ** x - e, -1, 1), + ("f69", lambda x: math.log(abs(x - 10 / 9)), -1, 1), + ("f70", lambda x: 1 / 3 + _sign(x) * abs(x) ** (1 / 3) + x ** 3, -1, 1), + ("f71", lambda x: (x + 2 / 3) / (x + 101 / 100), -1, 1), + ("f72", lambda x: (x * 1e6 - 1) ** 3, -1, 1), + ("f73", lambda x: math.exp(x) * (x * 1e6 - 1) ** 3, -1, 1), + ("f74", lambda x: (x - 1 / 3) ** 2 * math.atan(x - 1 / 3), -1, 1), + ("f75", lambda x: _sign(3 * x - 1) * (1 - math.sqrt(1 - (3 * x - 1) ** 2 / 81)), -1, 1), + ("f76", lambda x: (1 + 1e6) / 1e6 if x > (1 - 1e6) / 1e6 else -1.0, -1, 1), + ("f77", lambda x: 1 / (21 * x - 1) if x != 1 / 21 else 0.0, -1, 1), + ("f78", lambda x: x * x / 4 + math.ceil(x / 2) - 0.5, -1, 1), + ("f79", lambda x: math.ceil(10 * x - 1) + 0.5, -1, 1), + ("f80", lambda x: x + math.sin(x * 1e6) / 10 + 1e-3, -1, 1), + ("f81", lambda x: 1 + math.sin(1 / (x + 1)) if x > -1 else -1.0, -1, 1), + ("f82", lambda x: 202 * x - 2 * math.floor((2 * x + 1e-2) / 2e-2) - 0.1, -1, 1), + ("f83", lambda x: (202 * x - 2 * math.floor((2 * x + 1e-2) / 2e-2) - 0.1) ** 3, -1, 1), + ("f84", lambda x: (x - 1) * (x - 2) * (x - 3) * (x - 4) * (x - 5) - 0.05, 0.5, 5.5), + ("f85", lambda x: math.sin(x) - 0.5 * x - 0.3, -10.0, 10.0), + ("f86", lambda x: math.exp(x) - 1 - x - x * x / 2 - 0.005, -2.0, 2.0), + ("f87", lambda x: 1 / (x - 0.5) - 2 - 0.05, 0.6, 2.0), + ("f88", lambda x: math.log(x) - x + 2 - 0.05, 0.1, 3.0), + ("f89", lambda x: math.sin(20 * x) + 0.1 * x - 0.1, -5.0, 10.0), + ("f90", lambda x: x ** 3 - 2 * x ** 2 + x - 0.025, -1.0, 2.0), + ("f91", lambda x: x * math.sin(1 / x) - 0.1 - 0.01, 0.01, 1.0), +] + + +def _safe(f, x): + try: + v = f(x) + return v if math.isfinite(v) else None + except (OverflowError, ValueError, ZeroDivisionError): + return None + + +def overlaps_in_sample(f, a, b): + """True when f coincides with an APS or Chandrupatla function (the development sets): + equal to 1e-12 relative at 7 fixed interior points of [a, b]. Objective, no judgment.""" + from scipy.optimize._tstutils import _APS_TESTS_DICTS, _CHANDRUPATLA_TESTS_DICTS + pts = [a + (b - a) * t for t in (0.07, 0.19, 0.31, 0.43, 0.57, 0.71, 0.89)] + mine = [_safe(f, x) for x in pts] + if any(v is None for v in mine): + return False + for d in list(_APS_TESTS_DICTS) + list(_CHANDRUPATLA_TESTS_DICTS): + g = (lambda ff, aa: (lambda x: ff(x, *aa)))(d["f"], d.get("args", ())) + theirs = [_safe(g, x) for x in pts] + if any(v is None for v in theirs): + continue + if all(abs(u - v) <= 1e-12 * max(1.0, abs(u), abs(v)) for u, v in zip(mine, theirs)): + return True + return False + + +def build(): + """[(id, f, a, b, in_sample)] for the problems whose ends have values of opposite sign, + and the list of ids left out because they do not.""" + out, dropped = [], [] + for pid, f, a, b in PROBLEMS: + a, b = float(a), float(b) + fa, fb = _safe(f, a), _safe(f, b) + if fa is None or fb is None or fa == 0.0 or fb == 0.0 or (fa < 0) == (fb < 0): + dropped.append(pid) + continue + out.append((pid, f, a, b, overlaps_in_sample(f, a, b))) + return out, dropped + + +def solved(f, x, delta=1e-6): + """A sign change within delta of the answer (or an exact zero) - the bracketing notion + of a root, valid for several roots and for jumps.""" + if not math.isfinite(x): + return False + v0 = _safe(f, x) + if v0 == 0.0: + return True + lo, hi = _safe(f, x - delta), _safe(f, x + delta) + if lo is None or hi is None: + return False + return lo == 0.0 or hi == 0.0 or (lo < 0) != (hi < 0) + + +if __name__ == "__main__": + probs, dropped = build() + ins = [p[0] for p in probs if p[4]] + print(f"problems {len(probs)}, dropped (no sign change at the ends) {dropped}") + print(f"in-sample overlaps with APS/Chandrupatla ({len(ins)}): {ins}") + print(f"out-of-sample: {len(probs) - len(ins)}")