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 <noreply@anthropic.com>
This commit is contained in:
vvs
2026-09-21 19:38:26 +01:00
co-authored by Claude Opus 5
parent a86a9ed263
commit 97af3ae767
2 changed files with 218 additions and 0 deletions
+38
View File
@@ -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) — там, по прошлому опыту, наш главный выигрыш.
+180
View File
@@ -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)}")