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>
141 lines
5.8 KiB
Python
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()
|