The .tex is no longer stale: regenerated from the canonical .md by build_tex.py (pandoc 3.1.3 + pdflatex on the server; marked GENERATED, never edited by hand). PDF: 28 pages, 0 errors, 0 undefined references, 0 overfull boxes. For LaTeX the .md got braces on four n_\max subscripts (fix_md_for_latex.py; rendering identical) and Table 10 was split into totals (10) and per-problem counts (10b) so that it fits a page; numbers unchanged. verify_article on the server 467 OK / 0 BAD; regression x2 1226 passed, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
vvs
2026-09-21 22:07:48 +01:00
co-authored by Claude Opus 5
parent d2a142c39e
commit 23d0c73231
6 changed files with 2361 additions and 878 deletions
+50
View File
@@ -0,0 +1,50 @@
"""Split Table 10 of article/sim_numerics.md in two - totals (with bound violations) and the
per-problem counts - so that it fits a printed page. The numbers are unchanged.
python -u apply_article_table10_split.py
"""
from __future__ import annotations
PATH = "article/sim_numerics.md"
OLD_START = "**Table 10.** A′ against the library rivals on all six corpora (uniform criterion)."
OLD_END = "| all 855 | **13032** | 16481 | 16657 | 24565 | 23649 | | |"
NEW = """**Table 10.** A′ against the library rivals on all six corpora (uniform criterion): total evaluations; in brackets the runs that break the $2\\times$-bisection bound.
| corpus | A′ | Chandrupatla | ModAB | Brent | TOMS 748 |
|----------------------|------------|--------------|------------|------------|------------|
| APS (154) | **2259** (0) | 2595 (0) | 2726 (0) | 2707 (0) | 2636 (0) |
| Chandrupatla (45) | **1074** (0) | 1505 (0) | 1681 (0) | 3238 (18) | 3088 (15) |
| new corpus 1 (206) | **3290** (0) | 3716 (0) | 3809 (1) | 6395 (38) | 5746 (22) |
| Roots.jl (78) | **1371** (0) | 1566 (0) | 1446 (0) | 2455 (11) | 2339 (9) |
| applied + GSL (132) | 1382 (0) | 1382 (0) | 1460 (0) | 1503 (1) | 1756 (1) |
| **corpus 4 (240), the one test** | **3656** (0) | 5717 (0) | 5535 (8) | 8267 (50) | 8084 (37) |
| all 855 | **13032** | 16481 | 16657 | 24565 | 23649 |
**Table 10b.** The same runs problem by problem: A′'s wins/ties/losses against Chandrupatla and against ModAB (exact two-sided sign test).
| corpus | A′ vs Chandrupatla | A′ vs ModAB |
|----------------------|------------------------------|--------------|
| APS (154) | 103/20/31, $p=3\\cdot10^{-10}$ | 118/10/26 |
| Chandrupatla (45) | 31/4/10, $p=0.001$ | 34/4/7 |
| new corpus 1 (206) | 108/33/65, $p=0.001$ | 125/26/55 |
| Roots.jl (78) | 44/14/20, $p=0.004$ | 47/11/20 |
| applied + GSL (132) | 48/52/32, $p=0.09$ | 76/35/21 |
| **corpus 4 (240), the one test** | **177/35/28, $p=10^{-27}$** | **183/23/34** |"""
def main():
with open(PATH, encoding="utf8") as fh:
text = fh.read()
if text.count(OLD_START) != 1 or text.count(OLD_END) != 1:
raise SystemExit("Table 10 anchors not found exactly once")
i, j = text.index(OLD_START), text.index(OLD_END) + len(OLD_END)
text = text[:i] + NEW + text[j:]
with open(PATH, "w", encoding="utf8") as fh:
fh.write(text)
print("Table 10 split")
if __name__ == "__main__":
main()
+25 -14
View File
@@ -135,15 +135,15 @@ Every quantity in it is either an evaluation of $f$, a divided difference of eva
An interpolation-only rule has no worst-case bound. The obvious safeguards *replace* the computed step with a midpoint whenever some progress test fails, and all of them are ruinous here, for a reason specific to a fast method: a step that collapses the *error* need not halve the *bracket*, so a bracket-based test fires on exactly the steps that were best. With the three-point scale, replacing on that test drops the observed order from 1.94 to 1.00 and the Alefeld–Potra–Shi total from 2806 to 7260 — worse than bisection.
The remedy is to keep as much of the computed step as the budget allows. Let $n_\max=2\lceil\log_2(w_0/\varepsilon_x)\rceil$ be twice the bisection step count for the initial width $w_0$ and $x$-tolerance $\varepsilon_x$, and let $\epsilon=\varepsilon_x/2$. At step $j$, with the bracket $[\ell,h]$, define the allowance and the projection
The remedy is to keep as much of the computed step as the budget allows. Let $n_{\max}=2\lceil\log_2(w_0/\varepsilon_x)\rceil$ be twice the bisection step count for the initial width $w_0$ and $x$-tolerance $\varepsilon_x$, and let $\epsilon=\varepsilon_x/2$. At step $j$, with the bracket $[\ell,h]$, define the allowance and the projection
$$\alpha_j = 2^{\,n_\max-j}\epsilon, \qquad x \;=\; \operatorname{clamp}\bigl(x_\mathrm{sim},\; h-\alpha_j,\; \ell+\alpha_j\bigr). \tag{3}$$
$$\alpha_j = 2^{\,n_{\max}-j}\epsilon, \qquad x \;=\; \operatorname{clamp}\bigl(x_\mathrm{sim},\; h-\alpha_j,\; \ell+\alpha_j\bigr). \tag{3}$$
The next bracket is at most $\max(x-\ell,\,h-x)$, so (3) gives $w_{j+1}\le\alpha_j$ directly, and induction bounds the run by $n_\max$ steps in exact arithmetic. In floating point the bracket can land exactly on the boundary, and the termination test then asks for one more; the guarantee stated and verified is therefore $2\lceil\log_2(w_0/\varepsilon_x)\rceil+3$ evaluations.
The next bracket is at most $\max(x-\ell,\,h-x)$, so (3) gives $w_{j+1}\le\alpha_j$ directly, and induction bounds the run by $n_{\max}$ steps in exact arithmetic. In floating point the bracket can land exactly on the boundary, and the termination test then asks for one more; the guarantee stated and verified is therefore $2\lceil\log_2(w_0/\varepsilon_x)\rceil+3$ evaluations.
**The multiplier is the method's one free constant.** An earlier version of this section argued that it is "the factor already claimed in the bound". That is circular — the bound is twice bisection because two was chosen — and measurement settles it (`measure_budget_factor.py`, Linux):
**Table 1a.** The scheduled projection at budget multiplier $c$, i.e. $n_\max=\lceil c\cdot\log_2(w_0/\varepsilon_x)\rceil$. Total evaluations, failures charged.
**Table 1a.** The scheduled projection at budget multiplier $c$, i.e. $n_{\max}=\lceil c\cdot\log_2(w_0/\varepsilon_x)\rceil$. Total evaluations, failures charged.
| $c$ | APS-154 | CHA-45 | both |
|---|---|---|---|
@@ -532,17 +532,28 @@ ModAB, the most recent bracketing method we know of, is run from its authors' pa
**The history is part of the result.** The variant carried to corpora 1–3 was A′ without the tolerance step and the overshoot branch. It transferred to corpus 1 by every pre-registered criterion (total 10% below Chandrupatla, per problem 105/34/67, $p=0.005$), did *not* transfer to Roots.jl (total 6.5% below, but per problem 38/10/30, $p=0.4$) and lost on the applied corpus (14% above). The losses there were traced to two gaps of the base step — no closing of the bracket once the root is reached, and no recognition of roots of order below one — which became the last two readings of §8.4; they were developed on the seen corpora and tested once, on corpus 4.
**Table 10.** A′ against the library rivals on all six corpora (uniform criterion). Totals; in brackets the runs that break the $2\times$-bisection bound. Per problem against Chandrupatla: wins/ties/losses.
**Table 10.** A′ against the library rivals on all six corpora (uniform criterion): total evaluations; in brackets the runs that break the $2\times$-bisection bound.
| corpus | A′ | Chandrupatla | ModAB | Brent | TOMS 748 | A′ vs Chandrupatla | A′ vs ModAB |
|---|---|---|---|---|---|---|---|
| APS (154) | **2259** (0) | 2595 (0) | 2726 (0) | 2707 (0) | 2636 (0) | 103/20/31, $p=3\cdot10^{-10}$ | 118/10/26 |
| Chandrupatla (45) | **1074** (0) | 1505 (0) | 1681 (0) | 3238 (18) | 3088 (15) | 31/4/10, $p=0.001$ | 34/4/7 |
| new corpus 1 (206) | **3290** (0) | 3716 (0) | 3809 (1) | 6395 (38) | 5746 (22) | 108/33/65, $p=0.001$ | 125/26/55 |
| Roots.jl (78) | **1371** (0) | 1566 (0) | 1446 (0) | 2455 (11) | 2339 (9) | 44/14/20, $p=0.004$ | 47/11/20 |
| applied + GSL (132) | 1382 (0) | 1382 (0) | 1460 (0) | 1503 (1) | 1756 (1) | 48/52/32, $p=0.09$ | 76/35/21 |
| **corpus 4 (240), the one test** | **3656** (0) | 5717 (0) | 5535 (8) | 8267 (50) | 8084 (37) | **177/35/28, $p=10^{-27}$** | **183/23/34** |
| all 855 | **13032** | 16481 | 16657 | 24565 | 23649 | | |
| corpus | A′ | Chandrupatla | ModAB | Brent | TOMS 748 |
|----------------------|------------|--------------|------------|------------|------------|
| APS (154) | **2259** (0) | 2595 (0) | 2726 (0) | 2707 (0) | 2636 (0) |
| Chandrupatla (45) | **1074** (0) | 1505 (0) | 1681 (0) | 3238 (18) | 3088 (15) |
| new corpus 1 (206) | **3290** (0) | 3716 (0) | 3809 (1) | 6395 (38) | 5746 (22) |
| Roots.jl (78) | **1371** (0) | 1566 (0) | 1446 (0) | 2455 (11) | 2339 (9) |
| applied + GSL (132) | 1382 (0) | 1382 (0) | 1460 (0) | 1503 (1) | 1756 (1) |
| **corpus 4 (240), the one test** | **3656** (0) | 5717 (0) | 5535 (8) | 8267 (50) | 8084 (37) |
| all 855 | **13032** | 16481 | 16657 | 24565 | 23649 |
**Table 10b.** The same runs problem by problem: A′'s wins/ties/losses against Chandrupatla and against ModAB (exact two-sided sign test).
| corpus | A′ vs Chandrupatla | A′ vs ModAB |
|----------------------|------------------------------|--------------|
| APS (154) | 103/20/31, $p=3\cdot10^{-10}$ | 118/10/26 |
| Chandrupatla (45) | 31/4/10, $p=0.001$ | 34/4/7 |
| new corpus 1 (206) | 108/33/65, $p=0.001$ | 125/26/55 |
| Roots.jl (78) | 44/14/20, $p=0.004$ | 47/11/20 |
| applied + GSL (132) | 48/52/32, $p=0.09$ | 76/35/21 |
| **corpus 4 (240), the one test** | **177/35/28, $p=10^{-27}$** | **183/23/34** |
Only the last line of corpora is evidence for A′ in the strict sense; the first five were seen while it was developed and are reported as a description. On corpus 4 A′ is 36% below Chandrupatla and wins 177 problems to 28. Without the two classes aimed at by the last two readings it is 2403 against 3569, per problem 107/31/22 ($p=10^{-14}$); on the simple-root classes alone, 737 against 747 (−1.3%), per problem 43/22/15 ($p=3\cdot10^{-4}$).
Binary file not shown.
+2162 -864
View File
File diff suppressed because it is too large Load Diff
+93
View File
@@ -0,0 +1,93 @@
"""Regenerate article/sim_numerics.tex (and the PDF) from the canonical article/sim_numerics.md.
The .md is the text; the .tex is a build product and is never edited by hand. Title, author
and abstract become pandoc metadata; the body is converted as it stands (section numbers are
part of the headings, so LaTeX numbering is off). Unicode that pdflatex does not know is
declared in the header. Run on the server (pandoc 3.1.3, pdflatex):
python -u build_tex.py -> article/sim_numerics.tex, article/sim_numerics.pdf
"""
from __future__ import annotations
import json
import os
import subprocess
import sys
MD = "article/sim_numerics.md"
TEX = "article/sim_numerics.tex"
HEADER = r"""
\usepackage{amsmath,amssymb}
\usepackage{textcomp}
\DeclareUnicodeCharacter{2032}{\ensuremath{'}}
\DeclareUnicodeCharacter{2212}{\ensuremath{-}}
\DeclareUnicodeCharacter{00D7}{\ensuremath{\times}}
\DeclareUnicodeCharacter{00B5}{\textmu}
\DeclareUnicodeCharacter{2192}{\ensuremath{\rightarrow}}
\DeclareUnicodeCharacter{0394}{\ensuremath{\Delta}}
\DeclareUnicodeCharacter{2082}{\textsubscript{2}}
\usepackage[margin=2.5cm]{geometry}
\usepackage[htt]{hyphenat}
\setlength{\emergencystretch}{3em}
\usepackage{etoolbox}
\AtBeginEnvironment{longtable}{\small}
"""
BANNER = ("%% GENERATED from article/sim_numerics.md by build_tex.py - do not edit by hand.\n"
"%% The .md is the canonical text; every number in it is checked by verify_article.py.\n")
def split_front(md):
lines = md.split("\n")
title = lines[0].lstrip("# ").strip()
rest = lines[1:]
author = next(ln.strip() for ln in rest if ln.strip())
text = "\n".join(rest)
head, _, body = text.partition("## Abstract")
abstract, _, body = body.partition("\n---\n")
return title, author, abstract.strip(), body.lstrip("\n")
def main():
with open(MD, encoding="utf8") as fh:
title, author, abstract, body = split_front(fh.read())
meta = {"title": title, "author": author, "abstract": abstract,
"header-includes": HEADER, "documentclass": "article",
"classoption": ["11pt", "a4paper"]}
os.makedirs("build", exist_ok=True)
with open("build/meta.json", "w", encoding="utf8") as fh:
json.dump(meta, fh)
with open("build/body.md", "w", encoding="utf8") as fh:
fh.write(body)
subprocess.run(["pandoc", "build/body.md", "--metadata-file=build/meta.json", "-s",
"-f", "markdown+tex_math_dollars+pipe_tables", "-t", "latex",
"-o", "build/body.tex"], check=True)
with open("build/body.tex", encoding="utf8") as fh:
tex = fh.read()
with open(TEX, "w", encoding="utf8") as fh:
fh.write(BANNER + tex)
for _ in range(2):
r = subprocess.run(["pdflatex", "-interaction=nonstopmode", "-halt-on-error",
"-output-directory", "article", TEX],
capture_output=True, text=True)
log = open("article/sim_numerics.log", encoding="utf8", errors="replace").read()
errors = [ln for ln in log.splitlines() if ln.startswith("!")]
# only undefined references / citations count; 'Font shape ... undefined' is a font
# substitution warning, not a defect of the text
undefined = [ln for ln in log.splitlines()
if "undefined" in ln.lower() and ("reference" in ln.lower()
or "citation" in ln.lower())]
overfull = sum(1 for ln in log.splitlines() if ln.startswith("Overfull"))
pages = next((ln for ln in log.splitlines() if "Output written" in ln), "no output")
print(f"pdflatex exit {r.returncode}; errors {len(errors)}; undefined {len(undefined)}; "
f"overfull boxes {overfull}")
for ln in errors[:10] + undefined[:10]:
print(" ", ln)
print(pages)
print("build done")
sys.exit(0 if r.returncode == 0 and not errors else 1)
if __name__ == "__main__":
main()
+31
View File
@@ -0,0 +1,31 @@
"""Make the math of article/sim_numerics.md valid LaTeX as well as MathJax: a sub- or
superscript that starts with a bare control word (n_\\max) needs braces in LaTeX (n_{\\max}).
The rendered formula is identical. Reports what it finds; --apply writes.
python -u fix_md_for_latex.py [--apply]
"""
from __future__ import annotations
import re
import sys
PATH = "article/sim_numerics.md"
# only argument-less control words (n_\max); a macro with an argument (x_\mathrm{far},
# r_\text{new}) is valid LaTeX as written, and bracing it would detach its argument
PAT = re.compile(r"([_^])(\\[A-Za-z]+)(?![A-Za-z{])")
def main():
with open(PATH, encoding="utf8") as fh:
text = fh.read()
hits = PAT.findall(text)
print(f"{len(hits)} bare control words in sub/superscripts:", sorted(set(h[1] for h in hits)))
if "--apply" in sys.argv:
text = PAT.sub(lambda m: f"{m.group(1)}{{{m.group(2)}}}", text)
with open(PATH, "w", encoding="utf8") as fh:
fh.write(text)
print("braced")
if __name__ == "__main__":
main()