Zenodo package (open release) and the PDF rebuilt with the published history's hashes.

make_open_package.py builds the archive from HEAD - source (git archive, LF line ends), the full history (git bundle), both simbracket wheels, README, requirements, reproduce.sh, LICENSE, NOTICE, SHA256SUMS - and refuses if any commit of the branch carries a string from a private list kept outside the repository, or if a text file has CRLF line ends; cleanroom_check.sh verifies the archive on Linux. Appendix D states that the history was rewritten once before publication to remove a server address, and that Table D1 is recomputed from the published history. lib/LICENSE so the wheels carry the licence. Server: verify 548 OK / 0 BAD, regression x2 1239 passed, PDF 32 pages, 0 errors, 0 overfull.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
vvs
2026-09-22 10:17:07 +01:00
co-authored by Claude Opus 5
parent 5c0550a0de
commit 9c1638af07
9 changed files with 431 additions and 4 deletions
+70
View File
@@ -0,0 +1,70 @@
# Root Finding by Similarity and Analogy — code, data and history
Companion archive to the paper *Root Finding by Similarity and Analogy: A Bracketing Method with a
Data-Selected Decision Layer* (V. Sitnikov). Everything behind the paper is here, under the Apache
License 2.0: the method's reference implementation, a compiled library of the proposed method A′, the
benchmark harness, the problem corpora, every results file, the records written before each run, the
paper itself, and the full development history.
## Contents
| path | what |
|---|---|
| `source/` | the repository at the commit the paper was built from: `simroot/` (the reference implementation of the base rule, A′ and every variant the paper measures), `lib/` (the source of the compiled library `simbracket`), the sweep scripts, corpora (`corpus_*.py`), diagnoses, the verifier `verify_article.py`, the tests, every results file (`sweep_*.json`, `results_*.txt`), the records written before the runs (`PREREG_*.md`, `POSTHOC_*.md`), the findings (`FINDINGS_*.md`) and the paper (`article/`) |
| `sim-numerics.bundle` | the full git history of that branch (`git clone sim-numerics.bundle`); the commits cited in the paper's Appendix D are in it |
| `wheels/` | `simbracket`, A′ as a compiled library (Python ≥ 3.10; Linux and Windows builds) |
| `requirements.txt`, `reproduce.sh` | the Python packages used, and the one-command reproduction |
| `SHA256SUMS` | checksums of every file in the archive |
## Requirements
**Linux x86_64 for reproducing the numbers.** The paper's numbers come from one Linux machine (Ubuntu
24.04, glibc 2.39, Python 3.12.3). The test functions call the platform's `libm`, whose last digit
differs between platforms; on Windows, TOMS 748 needs 2635 evaluations on the Alefeld–Potra–Shi set
instead of 2636, and two of the paper's fresh recomputations move by a few evaluations. Using the
method needs no particular platform.
## Reproduce
```bash
./reproduce.sh
```
creates a virtual environment, installs `requirements.txt` and the `simbracket` wheel, and in `source/`
runs
1. `python -u verify_article.py` — recomputes or re-reads every number stated in the paper (548) and
compares it with the value printed there; it ends with *every number stated in the paper matches the
sweep data*;
2. `python -m pytest tests/test_simbracket_equivalence.py` — the compiled library equals the reference
implementation bit for bit, point and evaluation count, on all 868 problems of the six corpora.
Any table can be recomputed from scratch with its sweep script; each script's docstring names the table
it produces (for example `python -u rescore_uniform.py` for the protocol tables, `python -u
sweep_fourth.py --fourth` for corpus 4, `python -u sweep_controls.py` and `python -u sweep_estimated.py`
for the controls). The full test suite is `python -m pytest tests`.
## Use A′ on your own problem
```python
from simbracket import find_root, evaluation_bound
x, nfev, converged = find_root(f, a, b, xtol=1e-12, maxfev=200)
# f(a) and f(b) of opposite signs; stops when the bracket is no wider than xtol or f(x) == 0;
# never more than evaluation_bound(a, b, xtol) = 2*ceil(log2(|b-a|/xtol)) + 3 evaluations
```
or, from source, `simroot.pure.pure_root` with the configuration named `APRIME` in `sweep_full.py`.
## The records and their order
`PREREG_*.md` were written, and committed, before the runs they describe; `POSTHOC_*.md` after, as
marked. Appendix D of the paper lists each record with its commit and the commit of its results;
`check_prereg_order.py` recomputes that table from the history in `sim-numerics.bundle`. The order is
attested by the author's git history, not by an external registry. Before publication the history was
rewritten once to remove infrastructure details (a server address); the commit hashes cited in the paper
are those of the rewritten history in this archive.
## Licence
Apache License 2.0 (`LICENSE`); copyright Valeri Sitnikov (`NOTICE`).
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
# Clean-room check of the Zenodo archive on Linux: unzip into an empty directory, install only the
# simbracket wheel from the archive (dependencies from the existing environment, nothing downloaded),
# run the verifier and the equivalence test from source/, and recompute Appendix D from the bundled
# history. Usage: cleanroom_check.sh <zip>
set -e
export OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1
PY=~/torchenv/bin/python
Z=$(readlink -f "$1")
R=~/zenodo_cleanroom
rm -rf $R && mkdir -p $R && cd $R
$PY -c "import zipfile,sys; zipfile.ZipFile(sys.argv[1]).extractall('.')" "$Z"
D=$(ls -d sim-numerics-*/)
cd $R/$D
echo "== checksums"; sha256sum -c --quiet SHA256SUMS && echo "all match"
$PY -m pip install --quiet --no-deps --target $R/site wheels/simbracket-*-linux_x86_64.whl
cd source
export PYTHONPATH=$R/site:.
$PY -c "import simbracket, simroot.pure; print('simbracket', simbracket.__file__); print('reference', simroot.pure.__file__)"
echo "== verify_article.py"
$PY -u verify_article.py > $R/verify.log 2>&1 || true
echo "OK $(grep -c '^ OK' $R/verify.log) BAD $(grep -c '^ BAD' $R/verify.log)"
grep '^ BAD' $R/verify.log || true
grep -A6 '^Traceback' $R/verify.log || true
tail -1 $R/verify.log
echo "== equivalence test"
$PY -m pytest tests/test_simbracket_equivalence.py -q -p no:cacheprovider 2>&1 | tail -1
echo "== Appendix D from the bundled history"
cd $R && git clone --quiet $R/$D/sim-numerics.bundle hist && cd hist
$PY -u check_prereg_order.py | grep '^| `' > $R/appD_from_history.txt
grep '^| `' $R/$D/source/results_prereg_order.txt > $R/appD_in_archive.txt
if diff -q $R/appD_from_history.txt $R/appD_in_archive.txt; then echo "Appendix D reproduced from the history: identical"; else echo "Appendix D DIFFERS"; fi
grep -c '^| `' $R/appD_from_history.txt
echo "=== CLEANROOM DONE ==="
+96
View File
@@ -0,0 +1,96 @@
"""Build the Zenodo archive (open release, Apache-2.0): the repository at HEAD of release
(`git archive`), its full history (`git bundle`), the simbracket wheels, README, requirements, the
reproduction script, LICENSE, NOTICE and SHA256SUMS - zipped as sim-numerics-<version>.zip.
Refuses to build if the tree or the bundled history contains the server address or the
specifics of the next method, if HEAD is not release, or if a text file of the archive has
Windows line ends.
python -u zenodo/make_open_package.py 1.0.0
"""
from __future__ import annotations
import hashlib
import os
import shutil
import subprocess
import sys
import zipfile
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
VERSION = sys.argv[1] if len(sys.argv) > 1 else "1.0.0"
NAME = f"sim-numerics-{VERSION}"
OUT = os.path.join(ROOT, "zenodo_build")
PKG = os.path.join(OUT, NAME)
# The strings that must appear in no commit are read from a file OUTSIDE the repository (named by
# $ZENODO_FORBIDDEN): listing them here would put them into the history this check protects.
FORBIDDEN_FILE = os.environ.get("ZENODO_FORBIDDEN", "")
WHEELS = ["lib/dist/simbracket-1.0.0-cp310-abi3-linux_x86_64.whl",
"lib/dist/simbracket-1.0.0-cp310-abi3-win_amd64.whl"]
CRLF = bytes([13, 10])
def git(*args, **kw):
return subprocess.run(["git", "-C", ROOT, *args], check=True, capture_output=True, **kw)
def main():
branch = git("branch", "--show-current", text=True).stdout.strip()
assert branch == "release", branch
assert not git("status", "--porcelain", "--untracked-files=no", text=True).stdout.strip(), \
"commit everything first: the archive is HEAD"
assert FORBIDDEN_FILE and os.path.isfile(FORBIDDEN_FILE), "set ZENODO_FORBIDDEN to the private list"
assert not os.path.abspath(FORBIDDEN_FILE).startswith(ROOT), "the list must live outside the repository"
forbidden = [ln.strip() for ln in open(FORBIDDEN_FILE, encoding="utf8") if ln.strip()]
for s in forbidden: # every commit of the branch
hits = subprocess.run(["git", "-C", ROOT, "log", "release", "-p", "-S", s, "--format=%h"],
capture_output=True, text=True, encoding="utf8").stdout.split()
assert not hits, (s, hits[:3])
if os.path.isdir(OUT):
shutil.rmtree(OUT)
os.makedirs(os.path.join(PKG, "wheels"))
tar = os.path.join(OUT, "source.tar")
# core.autocrlf=false: the archive carries the repository's LF line ends, not Windows' CRLF
git("-c", "core.autocrlf=false", "archive", "--format=tar", "--prefix=source/", "-o", tar, "HEAD")
shutil.unpack_archive(tar, PKG)
os.remove(tar)
git("bundle", "create", os.path.join(PKG, "sim-numerics.bundle"), "release")
for w in WHEELS:
shutil.copy(os.path.join(ROOT, w), os.path.join(PKG, "wheels"))
src = os.path.join(PKG, "source") # from the archive: LF, exactly as committed
for f in ("README.md", "requirements.txt", "reproduce.sh"):
shutil.copy(os.path.join(src, "zenodo", f), os.path.join(PKG, f))
for f in ("LICENSE", "NOTICE"):
shutil.copy(os.path.join(src, f), os.path.join(PKG, f))
bad = []
for d, _, files in os.walk(PKG):
for f in files:
if f.endswith((".py", ".sh", ".md", ".txt")) or f in ("LICENSE", "NOTICE"):
if CRLF in open(os.path.join(d, f), "rb").read():
bad.append(os.path.relpath(os.path.join(d, f), PKG))
assert not bad, ("Windows line ends", bad[:5])
sums = []
for d, _, files in os.walk(PKG):
for f in sorted(files):
p = os.path.join(d, f)
rel = os.path.relpath(p, PKG).replace("\\", "/")
sums.append(f"{hashlib.sha256(open(p, 'rb').read()).hexdigest()} {rel}")
with open(os.path.join(PKG, "SHA256SUMS"), "w", encoding="ascii", newline="\n") as fh:
fh.write("\n".join(sorted(sums, key=lambda s: s[66:])) + "\n")
zp = os.path.join(OUT, NAME + ".zip")
with zipfile.ZipFile(zp, "w", zipfile.ZIP_DEFLATED) as z:
for d, _, files in os.walk(PKG):
for f in files:
p = os.path.join(d, f)
info = zipfile.ZipInfo.from_file(p, os.path.relpath(p, OUT).replace("\\", "/"))
if f.endswith(".sh"):
info.external_attr = 0o755 << 16
with open(p, "rb") as src_fh:
z.writestr(info, src_fh.read(), zipfile.ZIP_DEFLATED)
head = git("rev-parse", "--short", "HEAD", text=True).stdout.strip()
print(f"{zp}: {os.path.getsize(zp) / 1e6:.1f} MB, {len(sums)} files, source at {head}")
print("zenodo package done")
if __name__ == "__main__":
main()
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
# Reproduce the paper's numbers (Linux x86_64, Python 3.12). See README.md.
set -e
cd "$(dirname "$0")"
python3.12 -m venv .venv
. .venv/bin/activate
python -m pip install --quiet -r requirements.txt
python -m pip install --quiet --no-deps wheels/simbracket-*-linux_x86_64.whl
export OMP_NUM_THREADS=1
cd source
echo "== verify_article.py (every number stated in the paper) =="
python -u verify_article.py > ../verify.log 2>&1 || true
echo "OK $(grep -c '^ OK' ../verify.log) BAD $(grep -c '^ BAD' ../verify.log)"
tail -1 ../verify.log
echo "== simbracket against the reference implementation, bit for bit =="
python -m pytest tests/test_simbracket_equivalence.py -q -p no:cacheprovider | tail -1
+5
View File
@@ -0,0 +1,5 @@
numpy==2.4.3
scipy==1.17.1
mpmath==1.3.0
pymodab==1.0.9
pytest==9.1.1