Controls K1/K2: code and two PREREG amendments, committed before the full run.

simroot/pure.py: flatlagmempt / flatbandmempt / flatflagmempt - A' with only the flat detector
swapped (Lagouanelle without declining outside (0,1), with memory; Chandrupatla's band; none).
A' itself is untouched: bit-identical to the compiled simbracket on all 868 problems (x2).
sweep_controls.py: K2 through sweep_fourth's harness, K1 with the oracle order m*.
Amendment 1: m* <= 0 is undefined (aps.15.22-30, C0.04 crashed the first full attempt).
Amendment 2 (after the smokes; only corpus-level K1 sums and corpus-4 K2 sums were seen): each
side's slope rounded to 0.01, m* defined only when both sides show a zero (jumps C0.03,
aps.15.21), group 'multiple/fractional' = m* >= 2 or m* <= 2/3, the branches of section 8.1
(simple roots measured at 0.99999 fell under the registered 'm* < 1').
tests/test_article_numbers.py: the verifier test runs on Linux only (the paper's numbers are a
Linux run; libm's last digit differs elsewhere, e.g. 4461 vs 4462).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
vvs
2026-09-22 07:37:41 +01:00
co-authored by Claude Opus 5
parent 77312bedcf
commit 60294e84d9
4 changed files with 319 additions and 8 deletions
+29
View File
@@ -88,6 +88,35 @@ m_L — оценка Лагуанеля по трём последним точ
любая новая идея — отдельная предрегистрация. A′ заморожен: его код и результаты не трогаются, в любая новая идея — отдельная предрегистрация. A′ заморожен: его код и результаты не трогаются, в
прогоне он воспроизводится бит в бит (проверяется: суммы A′ = таблица 10). прогоне он воспроизводится бит в бит (проверяется: суммы A′ = таблица 10).
## Поправка 1 (после смоука, до полного прогона; результатов К1 и К2 на полных корпусах нет)
Полный прогон остановился делением на ноль: на десяти задачах оракул дал m\* ≤ 0 — `aps.15.22`–
`aps.15.30` (кусочно-линейная функция APS 15, линейный участок у корня уже h, дальше |f| постоянна:
m\* = 0) и `C0.04` нового корпуса 1 (|f| растёт к корню: m\* = −1). Степенной модели нуля в масштабе h
там нет, так что это случай «не определено»: **m\* ≤ 0 → m\* = 1**, преобразование не применяется.
Определение h и всё остальное не меняются.
## Поправка 2 (после второго смоука, до полного прогона)
Видено к этому моменту: смоук К2 только на корпусе 4 (суммы и w/t/l всех трёх вариантов) и смоук К1 —
**суммы по корпусам целиком**, без разбиения на группы. Проверка правдоподобия оракула нашла два
изъяна определения, оба — не в пользу какой-либо стороны:
1. **Скачки.** `C0.03` (f прыгает от −0.9 к 1.1 в корне) даёт m\* = 1.5·10⁻⁶; `aps.15.21` — наклон 0 с
одной стороны и 0.22 с другой, среднее 0.11. Нуля там нет, «порядок» — шум у нуля.
2. **Граница группы в точке шума.** Простой корень измеряется как 0.99999…, и правило «m\* < 1»
относило его к дробным; порядок 2 (например, log(1+q²) в D5) измеряется как 1.99999… или 2.00001.
Исправленное определение:
- наклон с каждой стороны **округляется до 0.01** (разрешение объявлено: у двухточечного наклона при
h = 10⁻⁵ ошибка порядка 10⁻⁵, так что 0.01 его не задевает); m\* **определён, только если обе
стороны положительны** (нуль виден с обеих сторон), и равен среднему двух округлённых наклонов;
иначе m\* = 1. Поправка 1 (m\* ≤ 0 → 1) входит сюда как частный случай;
- группа «кратные/дробные» — **m\* ≥ 2 или m\* ≤ ⅔**: ровно ветви §8.1, где касательный шаг проигрывает
делению пополам (те же константы, что в A′ и в К2; нового числа нет). Дробные порядки из (⅔, 1)
попадают в «остальные» — по теории §8.1 там касательный шаг сам бьёт деление.
## Реализация (план, до кода) ## Реализация (план, до кода)
- `simroot/pure.py`: три новых варианта плоского режима — `flatlagmempt`, `flatbandmempt`, - `simroot/pure.py`: три новых варианта плоского режима — `flatlagmempt`, `flatbandmempt`,
+60 -7
View File
@@ -634,6 +634,31 @@ def _analogy_order(cur, past, translation=False):
return math.log(lf) / math.log(lx) return math.log(lf) / math.log(lx)
def _lagouanelle_raw(x2, f2, x1, f1, x0, f0):
"""PREREG_controls_2026-09-22 (control K2): Lagouanelle's order from the parabola through the
last three points WITHOUT declining outside (0, 1): theta = f f''/f'^2, m = 1/(1 - theta) for
theta < 1 (so 0 < m < 1 when theta < 0, the fractional branch), m = inf for theta >= 1. None
when f' is zero, not finite, or its square underflows. A control, not part of A'."""
if x0 is None or len({x0, x1, x2}) < 3:
return None
d01 = (f1 - f0) / (x1 - x0)
d12 = (f2 - f1) / (x2 - x1)
d012 = (d12 - d01) / (x2 - x0)
fp = d12 + d012 * (x2 - x1)
fpp = 2.0 * d012
if fp == 0.0 or not math.isfinite(fp) or not math.isfinite(fpp):
return None
den = fp * fp
if den == 0.0:
return None
theta = f2 * fpp / den
if not math.isfinite(theta):
return None
if theta >= 1.0:
return math.inf
return 1.0 / (1.0 - theta)
def _analogous_and_steep(cur, past): def _analogous_and_steep(cur, past):
"""Is the configuration nearer to its own past than to the line phi = xi, and does the """Is the configuration nearer to its own past than to the line phi = xi, and does the
analogous pair scale like a power of order m >= 2? Two ordinal judgments and the analogous pair scale like a power of order m >= 2? Two ordinal judgments and the
@@ -772,15 +797,18 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
parts = prestep.split("+") parts = prestep.split("+")
cats = ({"defect"}, {"contra"}, {"exp"}, {"half"}, {"close"}, {"sub"}, cats = ({"defect"}, {"contra"}, {"exp"}, {"half"}, {"close"}, {"sub"},
{"flatanalog", "flatanalogmem", "flatanalogpt", {"flatanalog", "flatanalogmem", "flatanalogpt",
"flatanalogmempt"}) "flatanalogmempt", "flatlagmempt", "flatbandmempt",
"flatflagmempt"})
if (len(parts) != len(set(parts)) or any(not any(p in c for c in cats) for p in parts) if (len(parts) != len(set(parts)) or any(not any(p in c for c in cats) for p in parts)
or any(sum(p in c for p in parts) > 1 for c in cats)): or any(sum(p in c for p in parts) > 1 for c in cats)):
raise ValueError(f"unknown composite prestep {prestep!r}") raise ValueError(f"unknown composite prestep {prestep!r}")
if (("exp" in parts or "sub" in parts) if "exp" in parts and not any(p.startswith("flatanalog") for p in parts):
and not any(p.startswith("flatanalog") for p in parts)):
# the translation limit changes how the analogy reads its order; without the # the translation limit changes how the analogy reads its order; without the
# flat regime there is no analogy to read (PREREG_flat_exp_limit) # flat regime there is no analogy to read (PREREG_flat_exp_limit)
raise ValueError("prestep component 'exp' needs a flatanalog* component") raise ValueError("prestep component 'exp' needs a flatanalog* component")
if "sub" in parts and not any(p.startswith("flat") for p in parts):
# the overshoot branch enters the flat regime; it needs one (PREREG_close_sub)
raise ValueError("prestep component 'sub' needs a flat* regime component")
elif prestep not in ("none", "progress", "slope", "band", "defect", "simpair", "flat", elif prestep not in ("none", "progress", "slope", "band", "defect", "simpair", "flat",
"flat2", "flatcreep", "flatstick", "flatstickmid", "flatstickexit", "flat2", "flatcreep", "flatstick", "flatstickmid", "flatstickexit",
"flatjudge", "flatanalog", "flatanalogmem", "contra", "flatjudge", "flatanalog", "flatanalogmem", "contra",
@@ -789,10 +817,17 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
parts = prestep.split("+") parts = prestep.split("+")
pre_dec = next((p for p in parts if p in ("band", "defect", "simpair")), "none") pre_dec = next((p for p in parts if p in ("band", "defect", "simpair")), "none")
pre_flat = next((p for p in parts if p in ("flatanalog", "flatanalogmem", "flatanalogpt", pre_flat = next((p for p in parts if p in ("flatanalog", "flatanalogmem", "flatanalogpt",
"flatanalogmempt")), "none") "flatanalogmempt", "flatlagmempt",
"flatbandmempt", "flatflagmempt")), "none")
# PREREG_flat_order_point: the "pt" twins split at the point from the pair's order # PREREG_flat_order_point: the "pt" twins split at the point from the pair's order
flat_mem = pre_flat in ("flatanalogmem", "flatanalogmempt") # PREREG_controls_2026-09-22: flatlag/flatband/flatflag are A' with ONLY the detector swapped
flat_pt = pre_flat in ("flatanalogpt", "flatanalogmempt") # (Lagouanelle with memory, Chandrupatla's band, no detector); memory, split, exit unchanged
detector = {"flatlagmempt": "lag", "flatbandmempt": "band",
"flatflagmempt": "flag"}.get(pre_flat, "analogy")
flat_mem = pre_flat in ("flatanalogmem", "flatanalogmempt", "flatlagmempt", "flatbandmempt",
"flatflagmempt")
flat_pt = pre_flat in ("flatanalogpt", "flatanalogmempt", "flatlagmempt", "flatbandmempt",
"flatflagmempt")
pre_contra = "contra" in parts pre_contra = "contra" in parts
pre_exp = "exp" in parts # PREREG_flat_exp_limit: translation similarity, m = inf pre_exp = "exp" in parts # PREREG_flat_exp_limit: translation similarity, m = inf
pre_half = "half" in parts # PREREG_half_contra: the half promised vs the bracket pre_half = "half" in parts # PREREG_half_contra: the half promised vs the bracket
@@ -1089,7 +1124,11 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
cur = _config3(x, fx, x_prev, f_prev, x_prev2, f_prev2) cur = _config3(x, fx, x_prev, f_prev, x_prev2, f_prev2)
flag = (sticky_m is None and last_model and f_prev != 0.0 flag = (sticky_m is None and last_model and f_prev != 0.0
and fx / f_prev >= 0.25) and fx / f_prev >= 0.25)
m_now = None # controls band/flag: the split reads the current m_L
if detector == "analogy":
m_pair = _analogy_order(cur, configs, translation=pre_exp) m_pair = _analogy_order(cur, configs, translation=pre_exp)
else:
m_pair = _lagouanelle_raw(x, fx, x_prev, f_prev, x_prev2, f_prev2)
steep = m_pair is not None and m_pair >= 2.0 steep = m_pair is not None and m_pair >= 2.0
# PREREG_close_sub 2026-09-21 (sub): the tangent step loses to a halving # PREREG_close_sub 2026-09-21 (sub): the tangent step loses to a halving
# when |1 - 1/m| > 1/2 - for m > 2 (undershoot, the branch above) AND # when |1 - 1/m| > 1/2 - for m > 2 (undershoot, the branch above) AND
@@ -1097,6 +1136,19 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
# a sign change; the pair's order says which branch it is. # a sign change; the pair's order says which branch it is.
shallow = (pre_sub and m_pair is not None shallow = (pre_sub and m_pair is not None
and 0.0 < m_pair <= 2.0 / 3.0) and 0.0 < m_pair <= 2.0 / 3.0)
if detector in ("band", "flag"):
# PREREG_controls K2-P / K2-F: the regime is recognised by
# Chandrupatla's band (band rejects the triple) or not at all;
# the split uses m_L of the current state, inf if undefined
m_now = math.inf if m_pair is None else m_pair
if detector == "band":
rej = _decide_refuse("band", S, z_now, x, fx, x_prev, f_prev,
x_prev2, f_prev2)
else:
rej = True
steep = rej
shallow = pre_sub and rej
m_pair = m_now
flag_over = (pre_sub and sticky_m is None and last_model flag_over = (pre_sub and sticky_m is None and last_model
and f_prev != 0.0 and fx / f_prev <= -0.25) and f_prev != 0.0 and fx / f_prev <= -0.25)
if flat_mem: if flat_mem:
@@ -1129,7 +1181,8 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
if flat_pt: if flat_pt:
# PREREG_flat_order_point 2026-09-21: split where |f| ~ # PREREG_flat_order_point 2026-09-21: split where |f| ~
# |x - r|^m puts the root, m read from the analogous pair # |x - r|^m puts the root, m read from the analogous pair
regime_target = _order_split(A, FA, B, FB, analogy_m) regime_target = _order_split(
A, FA, B, FB, analogy_m if m_now is None else m_now)
step = regime_target - x step = regime_target - x
else: else:
step = 0.5 * (A + B) - x step = 0.5 * (A + B) - x
+225
View File
@@ -0,0 +1,225 @@
"""PREREG_controls_2026-09-22: the controls, as registered (commit 77312be).
K2 (point 6): A' with only the flat detector swapped - Lagouanelle with memory (K2L),
Chandrupatla's band (K2P), no detector (K2F) - on the six corpora of section 9, harness of
sweep_fourth.py (same problems, seeding, charging, uniform criterion).
K1 (point 5): library Chandrupatla and Brent on g = sign(f)|f|^(1/m*), m* the true order read at
the known root, on the four corpora whose roots are known.
python -u sweep_controls.py run -> sweep_controls.json, then the report
python -u sweep_controls.py --smoke K2 on corpus 4 and K1 on all four corpora, no file
python -u sweep_controls.py --report
"""
from __future__ import annotations
import json
import math
import random
import sys
from collections import defaultdict
from multiprocessing import Pool
import sweep_fourth
from corpus_roots_jl import solved
from sweep_full import MAXFEV, XTOL
from sweep_oos import sign_p
TAIL = "+half+close+sub"
K2 = {"K2L": "contra+flatlagmempt" + TAIL, "K2P": "contra+flatbandmempt" + TAIL,
"K2F": "contra+flatflagmempt" + TAIL}
for _k, _pre in K2.items():
sweep_fourth.CONFIGS[_k] = sweep_fourth.spec(_pre)
SETS = ("APS", "CHA", "NEW", "RJL", "APP", "D")
K1_SETS = ("APS", "CHA", "NEW", "D")
def m_star(f, r):
"""The order at the known root: slope of ln|f| against ln|x - r|, both sides averaged,
h = 1e-5 max(1, |r|) (registered). 1 when undefined."""
# amendment 2 of the PREREG (after the smokes, before any full result): each side's slope
# is rounded to 0.01 and m* is defined only when BOTH sides show a zero (slope > 0) -
# a jump (C0.03), a flat side (aps.15.21-30) or a pole-like side (C0.04) is undefined
h = 1e-5 * max(1.0, abs(r))
vals = []
for s in (1.0, -1.0):
try:
f1, f2 = abs(f(r + s * h)), abs(f(r + s * 2 * h))
m = (math.log(f2) - math.log(f1)) / math.log(2.0)
except (ValueError, OverflowError, ZeroDivisionError):
return 1.0
if not math.isfinite(m) or round(m, 2) <= 0.0:
return 1.0
vals.append(round(m, 2))
return (vals[0] + vals[1]) / 2.0
def nonsimple(m):
"""The group 'multiple/fractional': the branches of section 8.1 where a tangent step loses to
a halving, m >= 2 or m <= 2/3 (amendment 2)."""
return m >= 2.0 or m <= 2.0 / 3.0
def k1_problems(which):
from corpus_fourth import build as b4
from corpus_new import build as b1
from sweep_full import load_problems
out = []
if which in ("APS", "CHA"):
out = [(pid, f, a, b, r) for s, pid, f, a, b, r in load_problems() if s == which]
elif which == "NEW":
out = [(pid, f, a, b, r) for _c, pid, f, a, b, r in b1()[0]]
else:
out = [(pid, f, a, b, r) for _c, pid, f, a, b, r in b4()[0]]
return out
def k1_job(which):
import scipy.optimize._tstutils as T
from simroot.baselines import brent_scipy, chandrupatla_lib
res = {"chandrupatla_g": {}, "brent_g": {}, "mstar": {}}
for pid, f, a, b, r in k1_problems(which):
T._f6_cache.clear()
random.seed(20260921)
m = m_star(f, r)
res["mstar"][pid] = m
inv = 1.0 / m
def g(x, _f=f, _i=inv):
v = _f(x)
return 0.0 if v == 0 else math.copysign(abs(v) ** _i, v)
for name, meth in (("chandrupatla_g", chandrupatla_lib), ("brent_g", brent_scipy)):
T._f6_cache.clear()
random.seed(20260921)
try:
x, n, conv = meth(g, a, b, XTOL, MAXFEV)
x = float(x)
except Exception:
x, n, conv = float("nan"), MAXFEV, False
ok = bool(conv and solved(f, x))
over = ok and int(n) > 2 * math.ceil(math.log2(abs(b - a) / XTOL)) + 4
res[name][pid] = [int(n), ok, bool(over)]
return which, res
def k2_job(j):
name, which = j
return name, which, sweep_fourth.evaluate((name, which))[1]
def cost(v):
return v[0] if v[1] else MAXFEV
def h2h(a, b, keep=None):
ps = [p for p in a if keep is None or keep(p)]
w = sum(cost(a[p]) < cost(b[p]) for p in ps)
l = sum(cost(a[p]) > cost(b[p]) for p in ps)
return w, len(ps) - w - l, l
def tot(r, keep=None):
return sum(cost(v) for p, v in r.items() if keep is None or keep(p))
def reference():
seen = json.load(open("sweep_fourth_seen.json", encoding="utf8"))
four = json.load(open("sweep_fourth_fourth.json", encoding="utf8"))
ref = {}
for k in ("Aprime", "chandrupatla"):
ref[k] = dict(seen[k])
ref[k].update(four[k])
return ref
def report(R):
ref = reference()
ap = ref["Aprime"]
print("C2 - six corpora, A' against each detector (total; A' w/t/l, p); violations in ()")
for k in K2:
line = [f" {k}:"]
for s in SETS:
w, t, l = h2h(ap[s], R["K2"][k][s])
line.append(f"{s} {tot(R['K2'][k][s])}({sum(v[2] for v in R['K2'][k][s].values())})"
f" {w}/{t}/{l}")
allk = sum(tot(R["K2"][k][s]) for s in SETS)
print(" ".join(line) + f" | all {allk} vs A' {sum(tot(ap[s]) for s in SETS)}")
print("\nC1 - corpus 4 (decisive): A' total, control total, A' w/t/l, p -> verdict")
passed = {}
for k in K2:
a, c = ap["D"], R["K2"][k]["D"]
w, t, l = h2h(a, c)
passed[k] = tot(a) < tot(c) and w > l and sign_p(w, l) < 0.05
print(f" {k}: A' {tot(a)} {k} {tot(c)} {w}/{t}/{l} p={sign_p(w, l):.2g} -> "
+ ("PASSED" if passed[k] else "NOT passed"))
print(" C1 overall: " + ("PASSED for every control" if all(passed.values())
else "NOT passed for " + ", ".join(k for k in K2 if not passed[k])))
print("\nC4 - violations of the 2x bound, all K2 variants, six corpora:",
sum(v[2] for k in K2 for s in SETS for v in R["K2"][k][s].values()))
best = min(K2, key=lambda k: tot(R["K2"][k]["D"]))
print(f"\nC5 - corpus 4 by class, A' against the best control ({best}): totals, w/t/l")
for cls in ("D1", "D2", "D3", "D4", "D5", "D6"):
keep = lambda p, c=cls: p.startswith(c) # noqa: E731
w, t, l = h2h(ap["D"], R["K2"][best]["D"], keep)
print(f" {cls}: A' {tot(ap['D'], keep)} {best} {tot(R['K2'][best]['D'], keep)} {w}/{t}/{l}")
print("\nC3 - K1: transform with the true order (m* >= 2 or m* <= 2/3 = 'multiple/fractional')")
for s in K1_SETS:
k1 = R["K1"][s]
mult = lambda p, _m=k1["mstar"]: nonsimple(_m[p]) # noqa: E731
rest = lambda p, _m=k1["mstar"]: not nonsimple(_m[p]) # noqa: E731
for title, keep in (("multiple/fractional", mult), ("rest", rest)):
n = sum(1 for p in k1["mstar"] if keep(p))
if n == 0:
continue
w, t, l = h2h({p: v for p, v in ap[s].items() if keep(p)}, k1["chandrupatla_g"])
print(f" {s:<4} {title:<20} n={n:3d} A' {tot(ap[s], keep):5d} "
f"Chandrupatla(g) {tot(k1['chandrupatla_g'], keep):5d} "
f"Brent(g) {tot(k1['brent_g'], keep):5d} "
f"Chandrupatla(f) {tot(ref['chandrupatla'][s], keep):5d} "
f"A' vs Ch(g) {w}/{t}/{l} p={sign_p(w, l):.2g}")
fam = defaultdict(list)
for s in K1_SETS:
for p, m in R["K1"][s]["mstar"].items():
if nonsimple(m):
fam[s].append(round(m, 2))
print(" m* on the multiple/fractional problems (first 8 per corpus):",
{s: v[:8] for s, v in fam.items()})
def main():
smoke = "--smoke" in sys.argv
if "--report" not in sys.argv:
k2_jobs = [(k, "fourth") for k in K2] + ([] if smoke else [(k, "seen") for k in K2])
k1_sets = K1_SETS # the oracle must meet every corpus, also in the smoke
with Pool(min(6, len(k2_jobs) + len(k1_sets))) as pool:
k2 = pool.map_async(k2_job, k2_jobs)
k1 = pool.map_async(k1_job, k1_sets)
k2, k1 = k2.get(), k1.get()
R = {"K2": {}, "K1": {}}
for name, _which, res in k2:
R["K2"].setdefault(name, {}).update(res)
for which, res in k1:
R["K1"][which] = res
if smoke:
ap = reference()["Aprime"]
for k in K2:
a, c = ap["D"], R["K2"][k]["D"]
print(f"smoke {k}: corpus 4 total {tot(c)} (A' {tot(a)}), A' w/t/l {h2h(a, c)}, "
f"violations {sum(v[2] for v in c.values())}")
for s in K1_SETS:
k1c = R["K1"][s]
print(f"smoke K1 {s}: Chandrupatla(g) {tot(k1c['chandrupatla_g'])} Brent(g) "
f"{tot(k1c['brent_g'])} m* range {min(k1c['mstar'].values()):.3g}.."
f"{max(k1c['mstar'].values()):.3g}")
print("controls smoke done")
return
with open("sweep_controls.json", "w", encoding="utf8") as fh:
json.dump(R, fh)
with open("sweep_controls.json", encoding="utf8") as fh:
R = json.load(fh)
report(R)
print("controls done")
if __name__ == "__main__":
main()
+4
View File
@@ -22,6 +22,10 @@ CREDIT_LABEL = "pure.ratio.derived3.credit"
@pytest.mark.skipif(not os.path.exists(os.path.join(ROOT, "sweep_full.json")), @pytest.mark.skipif(not os.path.exists(os.path.join(ROOT, "sweep_full.json")),
reason="sweep_full.json not present; run sweep_full.py") reason="sweep_full.json not present; run sweep_full.py")
@pytest.mark.skipif(not sys.platform.startswith("linux"),
reason="the paper's numbers come from a Linux run (section 4, Reproducibility); "
"verify_article.py recomputes some of them, and libm's last digit "
"differs on other platforms (e.g. 4461 vs 4462 in Table 1b)")
def test_every_number_in_the_paper_matches_the_sweep(): def test_every_number_in_the_paper_matches_the_sweep():
r = subprocess.run([sys.executable, "verify_article.py"], cwd=ROOT, r = subprocess.run([sys.executable, "verify_article.py"], cwd=ROOT,
capture_output=True, text=True, timeout=600) capture_output=True, text=True, timeout=600)