Files
sim-numerics/rescore_uniform.py
T
vvsandClaude Opus 5 862feaff86 Revision 1, step 1: one scoring criterion, the correct order, and the internal inconsistencies.
Point 1. The base step's order is Muller's 1.839, not 2: the derivative of the parabola through
the last three points errs by f'''/6 (x_n - x_{n-1})(x_n - x_{n-2}) = O(e_{n-1} e_{n-2}).
order_high_precision.py (3000 digits, to 1e-2900): 1.840 on cos x - x, e^x - 2, x^3 - x - 2,
sin x - 1/2; 2.000 only on x^2 - 2; two-point 1.618. The double-precision medians (2.000, 1.94)
are removed from the text.

Point 3. Every protocol number is re-scored under the criterion of section 9 - a sign change
within 1e-6 or f(x) == 0. rescore_uniform.py re-runs all 2779 distinct configurations of the 25
protocol sweeps on APS and CHA (the runs did not keep x) -> sweep_uniform.json, same row format;
it reproduces the APS/CHA lines of Table 10 exactly (built-in check). verify_article.py,
audit_similarity_places.py and the gap diagnostics read the re-scored data; the multiplier and
endowment measurements score by the same criterion. Table 7 (two criteria side by side) is gone;
7.3 now explains why the criterion is a sign change. Consequences stated in the text: the base
rule 2634 / 2357 against Chandrupatla 2595 / 1505 (1.5% behind on APS, no longer "level"); A'
2259 / 1074 (12.9% and 28.6% below Chandrupatla); the defect reading 2351 is second to the
shape reading 2337, not the record; the remembered analogy is level with no regime (2633 vs
2634) under this criterion; Table 9a's range is 19% to 425%.

Point 8. report_sweep.head_to_head counts every problem, failures charged (it counted only the
problems both methods solved, which is why Table 8 rows summed to 153 and 40). Family-level
comparisons added (A' vs Chandrupatla 8/1/6 of 15 and 7/0/2 of 9 families, not significant), and
an independence paragraph in section 9.

Point 11. sweep_itp.py: ITP (recommended constants and per-problem oracle) on all six corpora ->
Table 10; behind A' on every corpus, 0 bound violations.

Points 7, 9, 10 and the internals: the 2x bound is ours, not the rivals' promise; the law
predicts the group order only; "outside the proven bracket" explained (narrowest bracket of all
observed signs); 7.4's fun1 paragraph corrected (five costs under safe; the unguarded numbers
belong to fun1.4, fun1.5 and to fun3.1, fun5.1 separately); Table 1's last cell; the corpus-4
composition and odd multiplicities stated, also in the abstract.

build_tex.py: pdflatex output decoded with errors="replace" (a UTF-8 character cut by TeX's log
wrap crashed the build). Table 10 column widths rebalanced.

Checks: verify_article on the server 500 OK / 0 BAD (467 existing with the new values, 33 new);
regression x2 on the server 1232 passed, 0 failed; PDF 28 pages, 0 errors, 0 undefined, 0 overfull.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 07:01:36 +01:00

141 lines
5.8 KiB
Python

"""Revision point 3 (2026-09-22): ONE scoring criterion for the whole paper.
The protocol tables were scored against the published root (|x - root| <= 1e-6), which charges
every method the full budget on six problems where f vanishes identically near the root
(section 7.3). Section 9 uses the bracketing criterion: a sign change of f within 1e-6 of the
answer, or f(x) == 0 (corpus_roots_jl.solved). The runs did not keep x, so they cannot be
re-scored from the files; every distinct configuration of every protocol sweep is run again
here, on APS and CHA, under the bracketing criterion, and written in the SAME row format
(label, spec, APS/CHA: total, solved, n, over, per[pid] = [n, ok]) so that every table and
check can switch files without changing code.
Built-in check: A', the base rule and the library rivals must reproduce the APS and CHA
lines of Table 10 exactly (those runs already use this criterion).
python -u rescore_uniform.py all configurations -> sweep_uniform.json
python -u rescore_uniform.py --smoke 12 configurations, local code check
python -u rescore_uniform.py --workers N
"""
from __future__ import annotations
import glob
import json
import math
import os
import random
import sys
import time
from multiprocessing import get_context
os.environ.setdefault("OMP_NUM_THREADS", "1")
from corpus_roots_jl import solved
from sweep_full import MAXFEV, XTOL, label, load_problems, make_method
OUT = "sweep_uniform.json"
def sources():
"""Every row-format protocol sweep and the distinct specs in it (JSON-encoded)."""
files = {}
for p in sorted(glob.glob("sweep_*.json")):
if "smoke" in p or p == OUT:
continue
with open(p, encoding="utf8") as fh:
d = json.load(fh)
if isinstance(d, list) and d and "spec" in d[0]:
files[p] = [json.dumps(r["spec"]) for r in d]
return files
def evaluate(spec_json):
import scipy.optimize._tstutils as T
spec = tuple(json.loads(spec_json))
method = make_method(spec)
res = {"label": label(spec), "spec": list(spec)}
for setname in ("APS", "CHA"):
total = nsolved = over = n_probs = 0
per = {}
for s, pid, f, a, b, _root in load_problems():
if s != setname:
continue
n_probs += 1
T._f6_cache.clear()
random.seed(20260921)
try:
x, n, conv = method(f, a, b, XTOL, MAXFEV)
x = float(x)
except Exception:
x, n, conv = float("nan"), MAXFEV, False
# no reset here: the random step function is checked on the realisation the
# method met, exactly as in sweep_fourth.evaluate (Table 10)
ok = bool(conv and solved(f, x))
n = int(n)
total += n if ok else MAXFEV
per[pid] = [n, ok]
if ok:
nsolved += 1
if n > 2 * math.ceil(math.log2(abs(b - a) / XTOL)) + 4:
over += 1
res[setname] = {"total": total, "solved": nsolved, "n": n_probs, "over": over,
"per": per}
return spec_json, res
_CACHE = {}
def load_uniform(path):
"""The rows of protocol sweep `path`, re-scored under the bracketing criterion. The
15 MB file is read once per process; callers get fresh lists of the shared rows."""
if "U" not in _CACHE:
with open(OUT, encoding="utf8") as fh:
_CACHE["U"] = json.load(fh)
U = _CACHE["U"]
return [U["by_spec"][s] for s in U["files"][path]]
def main():
workers = int(sys.argv[sys.argv.index("--workers") + 1]) if "--workers" in sys.argv else 6
files = sources()
specs = sorted({s for v in files.values() for s in v})
if "--smoke" in sys.argv:
must = [json.dumps(list(x)) for x in (
("base", "chandrupatla"), ("base", "brent"), ("base", "toms748"),
("pure", "ratio", "derived3", "project"),
("pure", "ratio", "derived3", "project", "x",
"contra+flatanalogmempt+exp+half+close+sub", "recent", "distance", "none",
"pattern"))]
specs = [s for s in must if s in specs] + [s for s in specs if s not in must][:7]
print(f"{len(files)} sweep files, {sum(map(len, files.values()))} rows, "
f"{len(specs)} distinct configurations, {workers} workers", flush=True)
t0 = time.time()
by_spec = {}
with get_context("spawn").Pool(workers, maxtasksperchild=40) as pool:
for i, (s, r) in enumerate(pool.imap_unordered(evaluate, specs, chunksize=2), 1):
by_spec[s] = r
if i % 25 == 0 or i == len(specs):
el = time.time() - t0
print(f" {i}/{len(specs)} done, {el:.0f} s, ETA {el / i * (len(specs) - i):.0f} s",
flush=True)
files = {p: v for p, v in files.items() if all(s in by_spec for s in v)}
with open(OUT if "--smoke" not in sys.argv else "sweep_uniform_smoke.json", "w",
encoding="utf8") as fh:
json.dump({"criterion": "sign change within 1e-6 or f(x) == 0",
"files": files, "by_spec": by_spec}, fh)
# the built-in check against Table 10 (APS, CHA lines, same criterion)
want = {'["pure", "ratio", "derived3", "project", "x", "contra+flatanalogmempt+exp+half+close'
'+sub", "recent", "distance", "none", "pattern"]': (2259, 1074),
'["base", "chandrupatla"]': (2595, 1505), '["base", "brent"]': (2707, 3238),
'["base", "toms748"]': (2636, 3088)}
for s, (aps, cha) in want.items():
if s in by_spec:
got = (by_spec[s]["APS"]["total"], by_spec[s]["CHA"]["total"])
print(f" check vs Table 10: {by_spec[s]['label']:<60} {got} want {(aps, cha)} "
+ ("OK" if got == (aps, cha) else "MISMATCH"), flush=True)
print("rescore done", flush=True)
if __name__ == "__main__":
main()