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:
@@ -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`).
|
||||
@@ -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 ==="
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
numpy==2.4.3
|
||||
scipy==1.17.1
|
||||
mpmath==1.3.0
|
||||
pymodab==1.0.9
|
||||
pytest==9.1.1
|
||||
Reference in New Issue
Block a user