From d363ce836b29c2f23c2375c4caec0bbff518607e Mon Sep 17 00:00:00 2001 From: Hydrodynamical Date: Wed, 21 Oct 2026 15:49:50 -0400 Subject: [PATCH 1/2] Add saved-array appendix reproduction workflow --- .gitignore | 6 + README.md | 10 + appendix_manifest.json | 61 ++++ docs/APPENDIX_REPRODUCTION.md | 84 +++++ export_appendix_panel.py | 135 ++++++++ graphon_control/experiments/kuramoto_plots.py | 59 ++++ reproduce_appendix.py | 322 ++++++++++++++++++ tests/test_appendix_panel_cli.py | 174 ++++++++++ tests/test_appendix_rw_panel.py | 105 ++++++ tests/test_reproduce_appendix.py | 278 +++++++++++++++ 10 files changed, 1234 insertions(+) create mode 100644 appendix_manifest.json create mode 100644 docs/APPENDIX_REPRODUCTION.md create mode 100644 export_appendix_panel.py create mode 100644 reproduce_appendix.py create mode 100644 tests/test_appendix_panel_cli.py create mode 100644 tests/test_appendix_rw_panel.py create mode 100644 tests/test_reproduce_appendix.py diff --git a/.gitignore b/.gitignore index d0c4d6d..2e5e9b4 100644 --- a/.gitignore +++ b/.gitignore @@ -236,3 +236,9 @@ STYLE.md *.bib.bak* figures/ references/ + +# Original appendix input archives are supplied separately. +/data/appendix/ + +# Saved-array exports are generated, never source inputs. +/out/ diff --git a/README.md b/README.md index 9df3ad6..0c5af95 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,16 @@ python e1_kuramoto.py --smoke --device cpu --no-mirror This runs a 20-iteration workflow check on a small grid and network. Results appear under `out_1/`, including `untwist.gif`, `kernel_time.gif`, and `metrics.json`. +## Reproduce the appendix + +Regenerate the existing appendix figures and table statistics from the original results (supplied separately), without training or simulation: + +```bash +python reproduce_appendix.py --manifest appendix_manifest.json --output out/appendix-regenerated +``` + +See [appendix reproduction](docs/APPENDIX_REPRODUCTION.md) for the archived runs, verification checks, and noise-data limitation. + ## Code | File | Contents | diff --git a/appendix_manifest.json b/appendix_manifest.json new file mode 100644 index 0000000..27590f0 --- /dev/null +++ b/appendix_manifest.json @@ -0,0 +1,61 @@ +{ + "schema_version": 1, + "description": "Original runs for the existing Miller\u2013Su numerical appendix. Historical inputs retained unchanged; no retraining.", + "runs": { + "bc": { + "path": "data/appendix/runs/e2_eps1_T10", + "sha256": { + "config.json": "d1ca4cb1465da0066238d5aad0f6a8db19db384930861453fc69b4dac952ed3c", + "metrics.json": "5eb0369d0221b785a7ad2df440651030e663cc0c74b602f1bcb6ebaf403dd4b7", + "trajectories.npz": "994ee45d108fa67ca96859322fe1839ecc326693fffa93d7f02645f071d28f9a" + } + }, + "k1": { + "path": "data/appendix/runs/e1_k1_T5", + "sha256": { + "config.json": "18fe4364db0476d7456f309a0f633c0b537431aa0f3f5207c117f8238ce2013f", + "metrics.json": "b42fcc26c74ab045557b2cefb5e9c3aa8b41ba06735208f7dcdd746d04a01bc6", + "trajectories.npz": "f91fa4e9bd80514d2f5f1a4ca2d4b947af0bff483a1600611987b6199d7b04a7" + } + }, + "k2": { + "path": "data/appendix/runs/e1_k2_T12", + "sha256": { + "config.json": "8fc0b04e36aca41d50332ed1344f4093650ffd148185a1e612ca73b311759731", + "metrics.json": "5787be6e8cf28f4bc39997318705abf71424fdc92578b0e7a4e07cd3d3e70b44", + "trajectories.npz": "d982c7da096d611948220f7ee22e8caede394f7a63b9b91b3e4a6c47e2602032" + } + }, + "k3": { + "path": "data/appendix/runs/e1_k3_T20", + "sha256": { + "config.json": "4a13f2ec59bd6b06e03dc6027b9a9c1d99f1ff8f27d58a100f6d66f85553c0c6", + "metrics.json": "78e161f2bbef70e20100b145eee8fae1e34051542a37ca5057b24d0d883fec63", + "trajectories.npz": "d599a52abc17b22ead159bd527096afedc081b5286ff9e2c661de59be97ed15b" + } + }, + "noise": { + "path": "data/appendix/runs/e1_k2_T12_sig03", + "sha256": { + "config.json": "0e3e703619b9876159949ebdf13a389a570400c11d26e580a48e25a9d214e77c", + "metrics.json": "31212452d9d34e9bba01b772aecf8e1e6eef2cc464c0133f1254fa8644fadf2d", + "trajectories.npz": "58c6322f096cbd34c240a25119efb2b0b76368c502801bae986dff625dd0f9b7" + } + } + }, + "output_dir": "out/appendix-reference", + "figures": { + "bc_opinion_traj.pdf": "data/appendix/reference_figures/bc_opinion_traj.pdf", + "bc_kernel_strip.pdf": "data/appendix/reference_figures/bc_kernel_strip.pdf", + "kur_rw_panel.pdf": "data/appendix/reference_figures/kur_rw_panel.pdf", + "kur_kernel_strip.pdf": "data/appendix/reference_figures/kur_kernel_strip.pdf", + "kur_noise_panel.pdf": "data/appendix/reference_figures/kur_noise_panel.pdf" + }, + "figures_sha256": { + "bc_opinion_traj.pdf": "13fea9efb675fe284756047ecfe6b40d14d0c7bb5efd537ca944c0599253c253", + "bc_kernel_strip.pdf": "ae25938cb270330ab7a12264432c168c66c2d97f9e07ec9c758a6350d7d2e139", + "kur_rw_panel.pdf": "8fbd92e2bed0ac2524c648999752e915a760ad3ab9e86d0ccd624dd6219d47b3", + "kur_kernel_strip.pdf": "75e93c47be3df16626003550ec8eaf8d1957d6f2041d751281edb91c6f9160ae", + "kur_noise_panel.pdf": "54db656b1fd1c2f48fd8367a0b63af6f25a66c276af53b4c13b2ce57d82b86ea" + } +} diff --git a/docs/APPENDIX_REPRODUCTION.md b/docs/APPENDIX_REPRODUCTION.md new file mode 100644 index 0000000..de7b690 --- /dev/null +++ b/docs/APPENDIX_REPRODUCTION.md @@ -0,0 +1,84 @@ +# Reproducing the existing numerical appendix + +The original saved results are supplied separately; this PR contains no archived +run arrays or reference PDFs. Put the selected original inputs under +`data/appendix/` (about 17 MB), following the manifest paths below. +`appendix_manifest.json` selects the five runs and historical figures by path +and SHA-256. Reproduction uses saved arrays only: no training, checkpoint +loading, or integration of the dynamics. + +| Manifest key | Original run | Appendix result | +| --- | --- | --- | +| `bc` | `e2_eps1_T10` | bounded-confidence table and two figures | +| `k1` | `e1_k1_T5` | depth-comparison table, k=1 | +| `k2` | `e1_k2_T12` | depth-comparison table and noiseless reference, k=2 | +| `k3` | `e1_k3_T20` | headline Kuramoto table and two figures, k=3 | +| `noise` | `e1_k2_T12_sig03` | noisy Kuramoto ensemble panel | + +For each run, copy its unchanged `config.json`, `metrics.json`, and +`trajectories.npz` into `data/appendix/runs//`. Copy the five +matching historical figure PDFs into `data/appendix/reference_figures/`. Checkpoints and training histories are unnecessary for +this workflow and are omitted. Only the five numerical figure PDFs are needed; manuscript sources are not +inputs. `appendix_manifest.json` records every required hash; missing or changed +run inputs are refused before any export. The archive directory is ignored by Git. + +All runs record N=64, dt=0.05, seed 0 and training jitter 0.1. Deterministic +evaluations use the exact initial profiles stated in the appendix. BC and the +k=1/2 Kuramoto runs use 2,000 Adam updates; k=3 uses 3,000. The archived results +match the rounded entries in the three appendix tables. + +## Regenerate from saved arrays + +Install the package from the repository root as described in +[DEVELOPMENT.md](DEVELOPMENT.md), then run: + +```sh +python reproduce_appendix.py \ + --manifest appendix_manifest.json \ + --output out/appendix-regenerated +``` + +The command checks input hashes before creating a new output directory and +writes five figure PDFs plus: + +- `tables.csv`: recomputed statistics for both paper arms in all four deterministic runs. +- `verification.json`: 131 checks against archived metrics and diagnostic arrays, plus objective rounding checks. +- `noise_summary.json`: retained ensemble statistics and their limitations. +- `provenance.json`: input and output hashes for this export. + +Diagnostics and objectives are recomputed by arithmetic on archived state and +kernel arrays. Small differences from stored metrics arise when float64 +reductions replace the original float32 reductions. When the original inputs are available, the real-data regression test also +compares the other rounded table entries. It skips when no archive is present; +fixture tests run without archived data. Existing output +directories are refused; choose a new name for another export. Generated +outputs under `out/` are ignored by Git. + +## Recover the exact historical PDFs + +```sh +./collect_paper_figures.sh appendix_manifest.json +``` + +This copies the selected historical PDFs into `out/appendix-reference/`. +The selected historical reference PDFs match the manuscript assets byte-for-byte. The original +BC run's kernel PDF differed from its figure mirror only in one creation-date +byte; the bundle retains the matching mirror. Regenerated figures match the +reference renders at 100 dpi in the tested environment. PDF creation dates and +rendering-library versions can change file bytes; use the reference copies +when identical PDF files are required. + +The standalone combined-panel exporter is also available: + +```sh +python export_appendix_panel.py \ + --run data/appendix/runs/e1_k3_T20 \ + --output out/appendix-panel-review/kur_rw_panel.pdf +``` + +## Archive limitation + +The noisy run retains ensemble means and 10–90% quantiles, not the 32 individual +paths. Its panel and terminal mean r=0.974 are reproducible from those aggregates. +The statement that every individual path has terminal winding zero cannot be +independently checked from the retained files. Missing paths are not synthesized. diff --git a/export_appendix_panel.py b/export_appendix_panel.py new file mode 100644 index 0000000..a88407e --- /dev/null +++ b/export_appendix_panel.py @@ -0,0 +1,135 @@ +"""Export a combined Kuramoto panel from an explicitly selected saved run. + +Only config.json and trajectories.npz are read. This command neither identifies +a run as a manuscript result nor reconstructs missing numerical data. +""" + +import argparse +import hashlib +import io +import json +import os +from pathlib import Path +import tempfile + +import numpy as np + +from graphon_control.experiments.kuramoto_plots import fig_rw_panel + + +ARMS = ("meanfield", "trained") + + +def _vector(value, name): + if np.iscomplexobj(value): + raise ValueError(f"{name} must be real") + value = np.asarray(value, dtype=float) + if value.ndim != 1 or not np.isfinite(value).all(): + raise ValueError(f"{name} must be a finite vector") + return value + + +def export_panel(run_dir, output): + """Write a new PDF and export sidecar, preserving the selected source run.""" + run_dir = Path(run_dir).expanduser().resolve() + inputs = {"config": run_dir / "config.json", + "trajectories": run_dir / "trajectories.npz"} + output = Path(output).expanduser().absolute() + sidecar = output.with_suffix(output.suffix + ".provenance.json") + for destination in (output, sidecar): + if destination.resolve() in {source.resolve() for source in inputs.values()}: + raise ValueError(f"Output collides with a source artifact: {destination}") + if destination.exists() or destination.is_symlink(): + raise FileExistsError(f"Output already exists: {destination}") + if output.suffix.lower() != ".pdf": + raise ValueError("--output must name a new .pdf file") + + # Read once: hashes describe the same bytes used to load the figure inputs. + payloads = {name: source.read_bytes() for name, source in inputs.items()} + config = json.loads(payloads["config"]) + if not isinstance(config, dict): + raise ValueError("config.json must contain a JSON object") + missing = {"k", "N", "T", "dt"} - set(config) + if missing: + raise ValueError(f"Missing configuration fields: {sorted(missing)}") + k, N = config["k"], config["N"] + if (isinstance(k, bool) or not isinstance(k, int) or k < 1 + or isinstance(N, bool) or not isinstance(N, int) or N <= 2 * k): + raise ValueError("Configuration must have integer k >= 1 and N > 2*k") + for name in ("T", "dt"): + value = config[name] + if (isinstance(value, bool) or not isinstance(value, (int, float)) + or not np.isfinite(value) or value <= 0): + raise ValueError(f"Configuration {name} must be finite and positive") + T, dt = config["T"], config["dt"] + steps = T / dt + if not np.isfinite(steps) or not np.isclose(steps, round(steps), rtol=1e-10, atol=1e-10): + raise ValueError("Configuration T/dt must be an integer") + NT = round(steps) + keys = {"t", "xi", *(f"{name}_{arm}" for name in ("r", "winding") for arm in ARMS)} + with np.load(io.BytesIO(payloads["trajectories"]), allow_pickle=False) as archive: + missing = keys - set(archive.files) + if missing: + raise ValueError(f"Missing saved arrays: {sorted(missing)}") + data = {key: _vector(archive[key], key) for key in keys} + t, xi = data["t"], data["xi"] + if (t.shape != (NT + 1,) or len(t) < 2 or not np.all(np.diff(t) > 0) + or not np.allclose(t, np.arange(NT + 1) * dt, rtol=1e-7, atol=1e-8)): + raise ValueError("Saved t grid does not match configuration T and dt") + if (xi.shape != (N,) or not np.all(np.diff(xi) > 0) + or np.any((xi < 0) | (xi > 1))): + raise ValueError("Saved xi grid does not match configuration N on [0, 1]") + for name in ("r", "winding"): + for arm in ARMS: + values = data[f"{name}_{arm}"] + if values.shape != t.shape: + raise ValueError(f"Saved {name}_{arm} does not match the time grid") + if name == "r" and np.any((values < -1e-6) | (values > 1 + 1e-6)): + raise ValueError(f"Saved r_{arm} must lie in [0, 1]") + if name == "winding" and not np.isclose(values[0], k, rtol=0, atol=1e-5): + raise ValueError(f"Saved winding_{arm} does not start at configuration k") + + provenance = { + "schema_version": 1, + "operation": "saved_array_export", + "inputs": {name: {"path": str(inputs[name]), + "sha256": hashlib.sha256(payload).hexdigest()} + for name, payload in payloads.items()}, + "selection": {"arms": list(ARMS), "k": k, "N": N, "T": T, "dt": dt}, + } + output.parent.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory(prefix=".appendix-panel-", dir=output.parent) as stage: + staged_pdf = Path(stage) / output.name + fig_rw_panel(t, {arm: data[f"r_{arm}"] for arm in ARMS}, + {arm: data[f"winding_{arm}"] for arm in ARMS}, k, staged_pdf) + provenance["output"] = {"path": str(output), + "sha256": hashlib.sha256(staged_pdf.read_bytes()).hexdigest()} + staged_sidecar = Path(stage) / sidecar.name + staged_sidecar.write_text(json.dumps(provenance, indent=2, sort_keys=True, + allow_nan=False) + "\n", encoding="utf-8") + # Exclusive publication also refuses destinations created after preflight. + os.link(staged_pdf, output) + try: + os.link(staged_sidecar, sidecar) + except OSError: + if output.exists() and output.samefile(staged_pdf): + output.unlink() + raise + return output, sidecar + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--run", type=Path, required=True, help="existing archived run directory") + parser.add_argument("--output", type=Path, required=True, help="new PDF path") + args = parser.parse_args(argv) + try: + paths = export_panel(args.run, args.output) + except (OSError, ValueError) as exc: + parser.exit(1, f"{exc}\n") + for path in paths: + print(path) + + +if __name__ == "__main__": + main() diff --git a/graphon_control/experiments/kuramoto_plots.py b/graphon_control/experiments/kuramoto_plots.py index 2eae907..e7fd7a1 100644 --- a/graphon_control/experiments/kuramoto_plots.py +++ b/graphon_control/experiments/kuramoto_plots.py @@ -1,5 +1,6 @@ """Model-specific figures, loaded only when rendering archived runs.""" import math +from collections.abc import Mapping from typing import Dict, List import numpy as np import torch @@ -56,6 +57,64 @@ def fig_r_combined(t_grid, r_series, k: int, path: str, arms: List[str] = None): plt.close(fig) +def fig_rw_panel(t_grid, r_series: Dict[str, np.ndarray], + winding_series: Dict[str, np.ndarray], k: int, path: str): + """Plot archived order parameters and winding numbers without a rollout. + + Both mappings must contain mean-field and trained curves and the same arms. + Every supplied arm is shown; the optional ring baseline retains its style. + """ + if isinstance(k, bool) or not isinstance(k, (int, np.integer)) or k < 1: + raise ValueError("k must be a positive integer") + t_grid = np.asarray(t_grid, dtype=float) + if (t_grid.ndim != 1 or len(t_grid) < 2 or not np.isfinite(t_grid).all() + or not np.all(np.diff(t_grid) > 0)): + raise ValueError("t_grid must be a finite, strictly increasing vector") + if not isinstance(r_series, Mapping) or not isinstance(winding_series, Mapping): + raise ValueError("r_series and winding_series must be arm mappings") + if set(r_series) != set(winding_series): + raise ValueError("Order-parameter and winding mappings must contain the same arms") + if not set(PAPER_ARMS).issubset(r_series): + raise ValueError("Both meanfield and trained arms are required") + unknown = set(r_series) - set(ARM_LABELS) + if unknown: + raise ValueError(f"Unknown arms: {sorted(unknown)}") + arms = [arm for arm in ARM_LABELS if arm in r_series] + curves = [] + for name, series in (("r", r_series), ("winding", winding_series)): + values = {} + for arm in arms: + value = np.asarray(series[arm], dtype=float) + if value.shape != t_grid.shape or not np.isfinite(value).all(): + raise ValueError(f"Invalid {name} curve for {arm}: expected finite values matching t_grid") + if name == "r" and np.any((value < -1e-6) | (value > 1 + 1e-6)): + raise ValueError(f"Order parameter for {arm} must lie in [0, 1]") + values[arm] = value + curves.append(values) + + fig, axes = plt.subplots(1, 2, figsize=(11, 3.8)) + try: + for ax, series in zip(axes, curves): + for arm in arms: + ax.plot(t_grid, series[arm], color=ARM_COLORS[arm], lw=2.0, + label=ARM_LABELS[arm]) + ax.set_xlabel(r"$t$") + ax.grid(alpha=0.2) + axes[0].set_ylabel(r"order parameter $r(t)$") + axes[0].set_ylim(-0.03, 1.03) + for q in range(k + 1): + axes[1].axhline(q, color="0.85", lw=0.8, zorder=0) + axes[1].set_ylabel(r"winding number $\mathcal{W}(t)$") + axes[1].set_ylim(min(-0.25, min(v.min() for v in curves[1].values()) - 0.05), + max(k + 0.25, max(v.max() for v in curves[1].values()) + 0.05)) + axes[0].legend(loc="upper left", fontsize=9) + fig.suptitle(rf"Untwisting the $k = {k}$ twisted state ($T = {t_grid[-1]:g}$)", y=1.02) + fig.tight_layout() + fig.savefig(path, bbox_inches="tight") + finally: + plt.close(fig) + + def fig_winding(t_grid, wind_series: Dict[str, np.ndarray], k: int, path: str, arms: List[str] = None): fig, ax = plt.subplots(figsize=(6.4, 4.0)) diff --git a/reproduce_appendix.py b/reproduce_appendix.py new file mode 100644 index 0000000..0b12192 --- /dev/null +++ b/reproduce_appendix.py @@ -0,0 +1,322 @@ +"""Export the existing appendix from a hash-pinned archive, without simulation. + +The manifest records the original five runs. Only JSON and NPZ contents are +interpreted; optional checkpoint, history, and summary files are hashed as bytes. +This command never deserializes a controller or executes training or dynamics. +""" + +import argparse +import csv +import hashlib +import io +import json +import os +from pathlib import Path +import shutil +import tempfile + +import numpy as np + +RUN_NAMES = ("bc", "k1", "k2", "k3", "noise") +INPUT_FILES = ("config.json", "metrics.json", "trajectories.npz") +ARMS = ("meanfield", "trained") +RTOL, ATOL = 2e-5, 1e-6 # archived computations used float32 + + +def sha256(data): + return hashlib.sha256(data).hexdigest() + + +def _json(path, value): + path.write_text(json.dumps(value, indent=2, allow_nan=False) + "\n", encoding="utf-8") + + +def load_manifest(manifest_path): + """Capture and hash every input before parsing arrays or creating outputs.""" + manifest_path = Path(manifest_path).expanduser().resolve() + raw_manifest = manifest_path.read_bytes() + manifest = json.loads(raw_manifest) + if manifest.get("schema_version") != 1 or set(manifest.get("runs", {})) != set(RUN_NAMES): + raise ValueError("Manifest must have schema_version 1 and runs bc, k1, k2, k3, noise") + captured, inputs = {}, {} + for name in RUN_NAMES: + spec = manifest["runs"][name] + directory = Path(spec["path"]).expanduser() + if not directory.is_absolute(): + directory = manifest_path.parent / directory + directory = directory.resolve() + if not set(INPUT_FILES).issubset(spec.get("sha256", {})): + raise ValueError(f"{name}: manifest must pin all three input files") + captured[name], inputs[name] = {}, {} + for filename in spec["sha256"]: + if Path(filename).name != filename: + raise ValueError(f"{name}: hash keys must be plain filenames") + path = directory / filename + raw = path.read_bytes() + digest = sha256(raw) + if digest != spec["sha256"][filename]: + raise ValueError(f"SHA-256 mismatch: {name}/{filename}") + if filename in INPUT_FILES: + captured[name][filename] = raw + inputs[name][filename] = {"path": str(path), "sha256": digest, "bytes": len(raw)} + runs = {} + for name in RUN_NAMES: + payload = captured[name] + with np.load(io.BytesIO(payload["trajectories.npz"]), allow_pickle=False) as archive: + arrays = {key: archive[key] for key in archive.files} + runs[name] = {"config": json.loads(payload["config.json"]), + "metrics": json.loads(payload["metrics.json"]), "arrays": arrays} + return runs, {"manifest": {"path": str(manifest_path), "sha256": sha256(raw_manifest)}, + "inputs": inputs} + + +def _require_config(name, config): + expected = {"N": 64, "dt": .05, "lambda_terminal": 0., "gamma_target": 100., + "x_target": 0., "jitter": .1, "batch_size": 16, "lr": .001, + "num_fourier_freqs": 8, "embed_dim": 64, "hidden_embed": 128, + "hidden_score": 128, "num_steps": 3000 if name == "k3" else 2000} + if name == "bc": + expected.update(T=10., sigma=0., beta=1., eps=1., gain=4., phi_form="poly", + span=6., amp=.5, modes=4) + else: + k = 2 if name == "noise" else int(name[1:]) + expected.update(k=k, T={1: 5., 2: 12., 3: 20.}[k], beta=10., + sigma=.3 if name == "noise" else 0.) + for field, value in expected.items(): + if field not in config or isinstance(config[field], bool) or config[field] != value: + raise ValueError(f"{name}: original scientific setting {field} must be recorded as {value!r}") + if config.get("score_bound") is not None or config.get("uncontrolled_only", False): + raise ValueError(f"{name}: configuration differs from the archived original protocol") + + +def _array(data, key, shape, name): + if key not in data: + raise ValueError(f"{name}: missing saved array {key}") + value = np.asarray(data[key]) + if value.shape != shape or value.dtype.kind not in "fiu" or not np.isfinite(value).all(): + raise ValueError(f"{name}: invalid saved array {key}; expected finite numeric {shape}") + return value.astype(np.float64) + + +def _check(name, field, actual, recorded, checks): + actual = np.asarray(actual) + recorded = np.asarray(recorded) + if actual.shape != recorded.shape or not np.isfinite(recorded).all(): + raise ValueError(f"{name}: invalid recorded metric {field}") + error = float(np.max(np.abs(actual - recorded))) + passed = bool(np.allclose(actual, recorded, rtol=RTOL, atol=ATOL)) + checks.append({"field": field, "passed": passed, "max_absolute_error": error}) + if not passed: + raise ValueError(f"{name}: saved arrays disagree with {field} (max error {error:g})") + + +def winding(x): + gaps = np.roll(x, -1, axis=-1) - x + return (((gaps + np.pi) % (2 * np.pi)) - np.pi).sum(axis=-1) / (2 * np.pi) + + +def diagnostics(name, run): + """Recompute objective and observables directly from the archived arrays.""" + c, m, d = run["config"], run["metrics"], run["arrays"] + _require_config(name, c) + nt, n, dt = round(c["T"] / c["dt"]), c["N"], c["dt"] + checks, report = [], {} + t = _array(d, "t", (nt + 1,), name) + if not np.allclose(t, np.arange(nt + 1) * dt, rtol=0, atol=1e-10): + raise ValueError(f"{name}: time grid does not match recorded T and dt") + tw = _array(d, "t_W", (nt,), name) + if not np.allclose(tw, t[:-1], rtol=0, atol=1e-10): + raise ValueError(f"{name}: kernel time grid does not match saved states") + xi = _array(d, "xi", (n,), name) + if not np.allclose(xi, (np.arange(n) + .5) / n, rtol=0, atol=1e-7): + raise ValueError(f"{name}: expected original midpoint label grid") + ws = _array(d, "W_trained", (nt, n, n), name) + if np.min(ws) < -1e-7 or not np.allclose(ws.sum(-1), 1, rtol=0, atol=1e-5): + raise ValueError(f"{name}: archived trained kernels are not row stochastic") + vxi = np.sqrt(np.mean((n * np.diff(ws, axis=1)) ** 2, axis=-1)).sum(-1) + _check(name, "vxi_trained", vxi, _array(d, "vxi_trained", (nt,), name), checks) + _check(name, "vxi.trained_max", vxi.max(), m["vxi"]["trained_max"], checks) + _check(name, "vxi.trained_final", vxi[-1], m["vxi"]["trained_final"], checks) + for arm in ARMS: + x = _array(d, "x_" + arm, (nt + 1, n), name) + expected_initial = (c["span"] * (xi - .5) + c["amp"] * np.sin(2 * np.pi * c["modes"] * xi) + if name == "bc" else 2 * np.pi * c["k"] * (xi - .5)) + if not np.allclose(x[0], expected_initial, rtol=1e-6, atol=1e-6): + raise ValueError(f"{name}: archived initial state does not match the appendix profile") + means = x.mean(-1) + var = ((x - means[:, None]) ** 2).mean(-1) + _check(name, "var_" + arm, var, _array(d, "var_" + arm, (nt + 1,), name), checks) + _check(name, "var_final." + arm, var[-1], m["var_final"][arm], checks) + state = float(dt * var[:-1].sum()) + l2 = float(dt * c["beta"] * np.square(ws).sum()) if arm == "trained" else float(c["beta"] * c["T"]) + target = float(c["gamma_target"] * (means[-1] - c["x_target"]) ** 2) + values = {"total": state + l2 + c["lambda_terminal"] * var[-1] + target, + "state_cost": state, "l2_reg_cost": l2, "terminal_var": float(var[-1]), + "terminal_target": target, "terminal_mean": float(means[-1])} + for field, value in values.items(): + _check(name, f"costs.{arm}.{field}", value, m["costs"][arm][field], checks) + result = {**values, "max_vxi": float(vxi.max()) if arm == "trained" else 0.} + if name != "bc": + r, w = np.abs(np.exp(1j * x).mean(-1)), winding(x) + _check(name, "r_" + arm, r, _array(d, "r_" + arm, (nt + 1,), name), checks) + _check(name, "winding_" + arm, w, _array(d, "winding_" + arm, (nt + 1,), name), checks) + _check(name, "r_final." + arm, r[-1], m["r_final"][arm], checks) + if "winding_final" in m: + _check(name, "winding_final." + arm, w[-1], m["winding_final"][arm], checks) + result.update(r_final=float(r[-1]), winding_final=float(w[-1])) + else: + clusters = 1 + int(np.count_nonzero(np.diff(np.sort(x[-1])) > c["eps"])) + saved_clusters = _array(d, "K_" + arm, (nt + 1,), name) + _check(name, "K_" + arm + "[-1]", clusters, saved_clusters[-1], checks) + if "K_final" in m: + _check(name, "K_final." + arm, clusters, m["K_final"][arm], checks) + result["clusters_final"] = clusters + report[arm] = result + if name == "noise": + for key in ("r", "winding"): + for suffix in ("mean", "q10", "q90"): + a = _array(d, f"{key}_noisy_{suffix}", (nt + 1,), name) + if key == "r" and (a.min() < -1e-6 or a.max() > 1 + 1e-6): + raise ValueError("noise: order parameter must lie in [0, 1]") + if np.any(d[f"{key}_noisy_q10"] > d[f"{key}_noisy_q90"]): + raise ValueError(f"noise: inverted {key} quantile band") + for metric, array in (("r_final_mean", "r_noisy_mean"), ("r_final_q10", "r_noisy_q10"), + ("r_final_q90", "r_noisy_q90"), ("winding_final_mean", "winding_noisy_mean")): + _check(name, "noisy_eval." + metric, float(d[array][-1]), m["noisy_eval"][metric], checks) + if m["noisy_eval"]["sigma"] != .3 or m["noisy_eval"]["n_replicates"] != 32: + raise ValueError("noise: expected recorded sigma=.3 and 32 replicates") + expected_j = {"bc": (38.08, 17.93), "k1": (66.45, 57.52), "k2": (277.88, 176.09), + "k3": (792.03, 368.62)} + rounding = {} + if name in expected_j: + for arm, expected in zip(ARMS, expected_j[name]): + rounding["J_" + arm] = {"expected": expected, "rounded_recomputed": round(report[arm]["total"], 2), + "matches": bool(round(report[arm]["total"], 2) == expected)} + rounding_matches = all(check["matches"] for check in rounding.values()) + return {"checks": checks, "recomputed": report, "saved_array_checks_passed": True, + "passed": rounding_matches, "appendix_rounding": rounding, + "status": "verified" if rounding_matches else "appendix_rounding_mismatch"} + + +def _noise_figure(noisy, clean, path): + # Same saved aggregate series and layout as e1_noise_figure; no file reloads. + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt + d, ref = noisy["arrays"], clean["arrays"] + c = noisy["config"] + fig, axes = plt.subplots(1, 2, figsize=(11, 3.8)) + try: + for ax, (key, ylabel, ylim) in zip(axes, [ + ("r", r"order parameter $r(t)$", (-.03, 1.03)), + ("winding", r"winding number $\mathcal{W}(t)$", (-.25, c["k"] + .25))]): + ax.fill_between(d["t"], d[f"{key}_noisy_q10"], d[f"{key}_noisy_q90"], + color="tab:blue", alpha=.25, label="noisy ensemble, 10–90%") + ax.plot(d["t"], d[f"{key}_noisy_mean"], color="tab:blue", lw=2, + label=rf"noisy ensemble mean ($\sigma = {c['sigma']:g}$)") + ax.plot(ref["t"], ref[f"{key}_trained"], color=".25", lw=1.8, ls="--", + label=r"deterministic ($\sigma = 0$)") + if key == "winding": + for q in range(c["k"] + 1): + ax.axhline(q, color=".85", lw=.8, zorder=0) + ax.set(xlabel=r"$t$", ylabel=ylabel, ylim=ylim) + ax.grid(alpha=.2) + axes[0].legend(loc="lower right", fontsize=9) + fig.suptitle(rf"Trained control under noise ($k = {c['k']}$, $T = {c['T']:g}$)", y=1.02) + fig.tight_layout() + fig.savefig(path, bbox_inches="tight") + finally: + plt.close(fig) + + +def export_appendix(manifest_path, output): + output = Path(output).expanduser().absolute() + if output.exists() or output.is_symlink(): + raise FileExistsError(f"Output directory already exists: {output}") + runs, provenance = load_manifest(manifest_path) + verification = {name: diagnostics(name, run) for name, run in runs.items()} + # Compare recorded data directly: historical configs did not all record x0_level. + for arm in ARMS: + if not np.array_equal(runs["noise"]["arrays"]["x_" + arm][0], + runs["k2"]["arrays"]["x_" + arm][0]): + raise ValueError("Noise and clean reference have different archived initial data") + noise_summary = { + **runs["noise"]["metrics"]["noisy_eval"], + "source": "archived ensemble mean and 10th/90th percentiles", + "individual_noisy_paths_saved": False, + "all_paths_zero_terminal_winding_verified": False, + "limitation": "Individual path arrays are absent; aggregate winding statistics cannot verify every path.", + } + from graphon_control.experiments.bounded_confidence import E2Config + from graphon_control.experiments import bounded_confidence_plots as bc_plots + from graphon_control.experiments import kuramoto_plots as kur_plots + import torch + output.parent.mkdir(parents=True, exist_ok=True) + stage = Path(tempfile.mkdtemp(prefix=".appendix-stage-", dir=output.parent)) + try: + bc, k3 = runs["bc"]["arrays"], runs["k3"]["arrays"] + cfg = E2Config(**runs["bc"]["config"]) + bc_plots.fig_opinion_traj(bc["t"], bc["xi"], {a: bc["x_" + a] for a in ARMS}, + list(ARMS), cfg, str(stage / "bc_opinion_traj.pdf")) + bc_plots.fig_kernel_strip(torch.from_numpy(bc["W_trained"]), cfg.dt, + str(stage / "bc_kernel_strip.pdf"), times=[0, .1*cfg.T, .2*cfg.T, .3*cfg.T, .4*cfg.T, cfg.T]) + kur_plots.fig_rw_panel(k3["t"], {a: k3["r_" + a] for a in ARMS}, + {a: k3["winding_" + a] for a in ARMS}, 3, str(stage / "kur_rw_panel.pdf")) + kur_plots.fig_kernel_strip(torch.from_numpy(k3["W_trained"]), runs["k3"]["config"]["dt"], + str(stage / "kur_kernel_strip.pdf")) + _noise_figure(runs["noise"], runs["k2"], stage / "kur_noise_panel.pdf") + rows = [] + for name in ("bc", "k1", "k2", "k3"): + for arm in ARMS: + d = verification[name]["recomputed"][arm] + rows.append({"run": name, "arm": arm, "J": d["total"], "terminal_variance": d["terminal_var"], + "terminal_mean": d["terminal_mean"], "max_Vxi": d["max_vxi"], + "r_final": d.get("r_final", ""), "winding_final": d.get("winding_final", ""), + "clusters_final": d.get("clusters_final", "")}) + with (stage / "tables.csv").open("w", newline="", encoding="utf-8") as stream: + writer = csv.DictWriter(stream, fieldnames=list(rows[0])) + writer.writeheader() + writer.writerows(rows) + _json(stage / "noise_summary.json", noise_summary) + _json(stage / "verification.json", {"operation": "saved_array_verification", "new_experiments": False, + "float32_comparison_tolerance": {"rtol": RTOL, "atol": ATOL}, "runs": verification, + "noise_individual_paths": noise_summary["limitation"]}) + provenance.update(operation="saved_array_appendix_export", new_experiments=False, + checkpoint_loads=False, outputs={p.name: {"sha256": sha256(p.read_bytes()), "bytes": p.stat().st_size} + for p in sorted(stage.iterdir())}) + _json(stage / "provenance.json", provenance) + # mkdir is exclusive, including when another writer races after validation. + output.mkdir() + published = [] + try: + for path in stage.iterdir(): + target = output / path.name + os.link(path, target) + published.append(target) + except BaseException: + for path in published: + path.unlink() + try: + output.rmdir() + except OSError: + pass # preserve unrelated concurrent contents + raise + finally: + shutil.rmtree(stage) + return output + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--manifest", required=True, type=Path) + parser.add_argument("--output", required=True, type=Path, help="new directory; existing outputs are never replaced") + args = parser.parse_args(argv) + try: + output = export_appendix(args.manifest, args.output) + except (OSError, ValueError, KeyError, TypeError) as exc: + parser.exit(1, f"{exc}\n") + print(f"Exported saved appendix data to {output}") + + +if __name__ == "__main__": + main() diff --git a/tests/test_appendix_panel_cli.py b/tests/test_appendix_panel_cli.py new file mode 100644 index 0000000..245f478 --- /dev/null +++ b/tests/test_appendix_panel_cli.py @@ -0,0 +1,174 @@ +"""Saved-array CLI checks; all numerical values below are handwritten fixtures.""" + +import hashlib +import json + +import numpy as np +import pytest +import torch + +import export_appendix_panel as exporter +from graphon_control import artifacts, dynamics, training + + +def _write_run(directory, *, missing_config=None, missing_array=None, update=None): + directory.mkdir() + config = {"k": 1, "N": 8, "T": 1.0, "dt": 0.5} + data = {"t": np.array([0.0, 0.5, 1.0]), "xi": (np.arange(8) + 0.5) / 8, + "r_meanfield": np.zeros(3), "r_trained": np.array([0.0, 0.6, 1.0]), + "winding_meanfield": np.ones(3), "winding_trained": np.array([1.0, 1.0, 0.0]), + "r_ring": np.zeros(3), "winding_ring": np.ones(3)} + if missing_config: + del config[missing_config] + if missing_array: + del data[missing_array] + if update: + update(config, data) + (directory / "config.json").write_text(json.dumps(config)) + np.savez(directory / "trajectories.npz", **data) + return directory + + +def _hashes(directory): + return {path.name: hashlib.sha256(path.read_bytes()).hexdigest() + for path in directory.iterdir() if path.is_file()} + + +@pytest.fixture(autouse=True) +def forbid_experiment_calls(monkeypatch): + def forbidden(*args, **kwargs): + raise AssertionError("Saved-array export must not train, evaluate dynamics, or load a checkpoint") + + monkeypatch.setattr(training, "train_controller", forbidden) + monkeypatch.setattr(dynamics, "rollout_with_Ws", forbidden) + monkeypatch.setattr(dynamics, "rollout_dynamics_w1", forbidden) + monkeypatch.setattr(dynamics, "precompute_Ws", forbidden) + monkeypatch.setattr(artifacts, "load_controller", forbidden) + monkeypatch.setattr(torch, "load", forbidden) + + +def test_cli_exports_selected_saved_curves_and_new_operation_provenance(tmp_path, monkeypatch): + run = _write_run(tmp_path / "original-run") + before = _hashes(run) + output = tmp_path / "exports" / "fixture-panel.pdf" + selected = [] + original_plot = exporter.fig_rw_panel + + def plot(t, r_series, winding_series, k, path): + selected.append((set(r_series), set(winding_series), k)) + return original_plot(t, r_series, winding_series, k, path) + + monkeypatch.setattr(exporter, "fig_rw_panel", plot) + exporter.main(["--run", str(run), "--output", str(output)]) + sidecar = output.with_suffix(".pdf.provenance.json") + record = json.loads(sidecar.read_text()) + assert selected == [({"meanfield", "trained"}, {"meanfield", "trained"}, 1)] + assert output.read_bytes().startswith(b"%PDF-") + assert record["operation"] == "saved_array_export" + assert record["inputs"]["config"]["sha256"] == before["config.json"] + assert record["inputs"]["trajectories"]["sha256"] == before["trajectories.npz"] + assert record["output"]["sha256"] == hashlib.sha256(output.read_bytes()).hexdigest() + assert record["output"]["path"] == str(output) + assert record["selection"] == {"arms": ["meanfield", "trained"], + "k": 1, "N": 8, "T": 1.0, "dt": 0.5} + assert _hashes(run) == before + assert set(_hashes(output.parent)) == {output.name, sidecar.name} + + +@pytest.mark.parametrize("key", ["k", "N", "T", "dt"]) +def test_missing_config_fields_never_default_or_write_outputs(tmp_path, key): + run = _write_run(tmp_path / "run", missing_config=key) + before = _hashes(run) + output = tmp_path / "exports" / "panel.pdf" + with pytest.raises(ValueError, match="Missing configuration fields"): + exporter.export_panel(run, output) + assert not output.parent.exists() + assert _hashes(run) == before + + +@pytest.mark.parametrize("key", ["t", "xi", "r_meanfield", "r_trained", + "winding_meanfield", "winding_trained"]) +def test_missing_saved_arrays_never_reconstruct_or_write_outputs(tmp_path, key): + run = _write_run(tmp_path / "run", missing_array=key) + before = _hashes(run) + output = tmp_path / "exports" / "panel.pdf" + with pytest.raises(ValueError, match="Missing saved arrays"): + exporter.export_panel(run, output) + assert not output.parent.exists() + assert _hashes(run) == before + + +@pytest.mark.parametrize("case", ["invalid_k", "invalid_N", "invalid_dt", "nonintegral_T", + "wrong_N", "wrong_t", "wrong_shape", "nonfinite_curve", + "wrong_initial_winding", "invalid_order_parameter", "object_array"]) +def test_config_and_saved_array_mismatches_fail_before_writes(tmp_path, case): + def update(config, data): + if case == "invalid_k": + config["k"] = True + elif case == "invalid_N": + config["N"] = 2 + elif case == "invalid_dt": + config["dt"] = 0.0 + elif case == "nonintegral_T": + config["T"] = 1.1 + elif case == "wrong_N": + config["N"] = 9 + elif case == "wrong_t": + data["t"][1] = 0.4 + elif case == "wrong_shape": + data["r_trained"] = np.ones(2) + elif case == "nonfinite_curve": + data["r_trained"][1] = np.nan + elif case == "wrong_initial_winding": + data["winding_trained"][0] = 0.0 + elif case == "invalid_order_parameter": + data["r_trained"][1] = 1.1 + elif case == "object_array": + data["r_trained"] = np.array([0.0, 0.6, 1.0], dtype=object) + + run = _write_run(tmp_path / "run", update=update) + before = _hashes(run) + output = tmp_path / "exports" / "panel.pdf" + with pytest.raises(ValueError): + exporter.export_panel(run, output) + assert not output.parent.exists() + assert _hashes(run) == before + + +@pytest.mark.parametrize("existing", ["pdf", "sidecar", "pdf_symlink", "sidecar_symlink"]) +def test_existing_outputs_and_dangling_symlinks_are_preserved(tmp_path, existing): + run = _write_run(tmp_path / "run") + output = tmp_path / "panel.pdf" + sidecar = output.with_suffix(".pdf.provenance.json") + occupied = sidecar if existing.startswith("sidecar") else output + if existing.endswith("symlink"): + occupied.symlink_to(tmp_path / "missing-target") + else: + occupied.write_bytes(b"preserve existing output") + with pytest.raises(FileExistsError): + exporter.export_panel(run, output) + if existing.endswith("symlink"): + assert occupied.is_symlink() + assert not (tmp_path / "missing-target").exists() + else: + assert occupied.read_bytes() == b"preserve existing output" + other = output if occupied == sidecar else sidecar + assert not other.exists() + + +@pytest.mark.parametrize("source", ["config.json", "trajectories.npz"]) +def test_source_output_collisions_are_rejected(tmp_path, source): + run = _write_run(tmp_path / "run") + before = _hashes(run) + with pytest.raises(ValueError, match="collides with a source artifact"): + exporter.export_panel(run, run / source) + assert _hashes(run) == before + + +def test_cli_reports_missing_arrays_without_creating_files(tmp_path): + run = _write_run(tmp_path / "run", missing_array="r_trained") + output = tmp_path / "exports" / "panel.pdf" + with pytest.raises(SystemExit) as error: + exporter.main(["--run", str(run), "--output", str(output)]) + assert error.value.code == 1 + assert not output.parent.exists() diff --git a/tests/test_appendix_rw_panel.py b/tests/test_appendix_rw_panel.py new file mode 100644 index 0000000..1355a90 --- /dev/null +++ b/tests/test_appendix_rw_panel.py @@ -0,0 +1,105 @@ +"""Appendix panel checks using handwritten arrays, without an experiment.""" + +import matplotlib.pyplot as plt +from matplotlib.figure import Figure +import numpy as np +import pytest + +from graphon_control import artifacts, dynamics, training +from graphon_control.experiments.kuramoto_plots import ( + ARM_COLORS, ARM_LABELS, fig_rw_panel, +) + + +def _curves(include_ring=False): + t = np.array([0.0, 1.0, 2.0, 3.0]) + r = {"meanfield": np.zeros(4), "trained": np.array([0.0, 0.3, 0.8, 1.0])} + winding = {"meanfield": np.full(4, 3.0), "trained": np.array([3.0, 2.0, 1.0, 0.0])} + if include_ring: + r["ring"] = np.array([0.0, 0.1, 0.1, 0.1]) + winding["ring"] = np.full(4, 3.0) + return t, r, winding + + +@pytest.mark.parametrize("include_ring", [False, True]) +def test_rw_panel_preserves_both_diagnostics_and_all_arms(tmp_path, monkeypatch, include_ring): + def forbidden(*args, **kwargs): + raise AssertionError("An archived-array panel must not run an experiment or load a checkpoint") + + monkeypatch.setattr(training, "train_controller", forbidden) + monkeypatch.setattr(dynamics, "rollout_with_Ws", forbidden) + monkeypatch.setattr(artifacts, "load_controller", forbidden) + t, r, winding = _curves(include_ring) + before = [value.copy() for value in [t, *r.values(), *winding.values()]] + captured = [] + original_savefig = Figure.savefig + + def savefig(figure, *args, **kwargs): + captured.append(figure) + return original_savefig(figure, *args, **kwargs) + + monkeypatch.setattr(Figure, "savefig", savefig) + output = tmp_path / "kur_rw_panel.pdf" + fig_rw_panel(t, r, winding, 3, output) + + assert output.read_bytes().startswith(b"%PDF-") + assert len(captured) == 1 + figure = captured[0] + assert len(figure.axes) == 2 + for ax, expected in zip(figure.axes, (r, winding)): + curves = {line.get_label(): line for line in ax.lines if line.get_label() in ARM_LABELS.values()} + assert set(curves) == {ARM_LABELS[arm] for arm in expected} + if ax is figure.axes[0]: + assert {text.get_text() for text in ax.get_legend().get_texts()} == set(curves) + else: + assert ax.get_legend() is None + for arm, values in expected.items(): + line = curves[ARM_LABELS[arm]] + np.testing.assert_array_equal(line.get_xdata(), t) + np.testing.assert_array_equal(line.get_ydata(), values) + assert line.get_color() == ARM_COLORS[arm] + assert figure.axes[0].get_ylabel() == r"order parameter $r(t)$" + assert figure.axes[1].get_ylabel() == r"winding number $\mathcal{W}(t)$" + assert not plt.fignum_exists(figure.number) + for actual, expected in zip([t, *r.values(), *winding.values()], before): + np.testing.assert_array_equal(actual, expected) + + +@pytest.mark.parametrize("case", [ + "invalid_k", "short_grid", "nonmonotone_grid", "nonfinite_grid", + "not_mapping", "missing_required_arm", "mismatched_arms", "unknown_arm", + "wrong_shape", "nonfinite_curve", "invalid_order_parameter", +]) +def test_rw_panel_rejects_invalid_inputs_before_writing(tmp_path, case): + t, r, winding = _curves() + k = 3 + if case == "invalid_k": + k = True + elif case == "short_grid": + t = np.array([0.0]) + elif case == "nonmonotone_grid": + t[2] = t[1] + elif case == "nonfinite_grid": + t[1] = np.nan + elif case == "not_mapping": + r = [] + elif case == "missing_required_arm": + del r["meanfield"] + del winding["meanfield"] + elif case == "mismatched_arms": + del winding["meanfield"] + elif case == "unknown_arm": + r["other"] = np.zeros(4) + winding["other"] = np.zeros(4) + elif case == "wrong_shape": + r["trained"] = np.zeros((4, 1)) + elif case == "nonfinite_curve": + winding["trained"][1] = np.inf + elif case == "invalid_order_parameter": + r["trained"][1] = 1.1 + output = tmp_path / "invalid.pdf" + figures_before = plt.get_fignums() + with pytest.raises(ValueError): + fig_rw_panel(t, r, winding, k, output) + assert not output.exists() + assert plt.get_fignums() == figures_before diff --git a/tests/test_reproduce_appendix.py b/tests/test_reproduce_appendix.py new file mode 100644 index 0000000..99c9af7 --- /dev/null +++ b/tests/test_reproduce_appendix.py @@ -0,0 +1,278 @@ +"""Saved-array fixtures only: no dynamics, optimization, or checkpoint loading.""" + +import csv +import hashlib +import json +from pathlib import Path + +import numpy as np +import pytest +import torch + +import reproduce_appendix as exporter +from graphon_control import artifacts, dynamics, training + + +def _hash(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def _dump(path, data): + path.write_text(json.dumps(data)) + + +@pytest.fixture(scope="module") +def archived_fixture(tmp_path_factory): + root = tmp_path_factory.mktemp("appendix-handwritten-array-fixture") + manifest = {"schema_version": 1, "runs": {}} + for name in exporter.RUN_NAMES: + directory = root / name + directory.mkdir() + cfg = {"N": 64, "dt": .05, "lambda_terminal": 0., "gamma_target": 100., + "x_target": 0., "jitter": .1, "batch_size": 16, "lr": .001, + "num_fourier_freqs": 8, "embed_dim": 64, "hidden_embed": 128, + "hidden_score": 128, "num_steps": 3000 if name == "k3" else 2000} + if name == "bc": + cfg.update(T=10., sigma=0., beta=1., eps=1., gain=4., phi_form="poly", + span=6., amp=.5, modes=4) + else: + k = 2 if name == "noise" else int(name[1:]) + cfg.update(k=k, T={1: 5., 2: 12., 3: 20.}[k], beta=10., sigma=.3 if name == "noise" else 0.) + n, nt = cfg["N"], round(cfg["T"] / cfg["dt"]) + xi = (np.arange(n) + .5) / n + initial = (6*(xi-.5) + .5*np.sin(8*np.pi*xi) if name == "bc" + else 2*np.pi*cfg["k"]*(xi-.5)) + # Algebraic test arrays, deliberately not the result of a solver. + data = {"t": np.arange(nt+1)*cfg["dt"], "t_W": np.arange(nt)*cfg["dt"], "xi": xi, + "W_trained": np.full((nt,n,n), 1/n), "vxi_trained": np.zeros(nt)} + metrics = {"vxi": {"trained_max": 0., "trained_final": 0.}, "costs": {}, "var_final": {}} + if name != "bc": + metrics["r_final"], metrics["winding_final"] = {}, {} + for arm in exporter.ARMS: + factor = np.ones(nt+1) if arm == "meanfield" else np.linspace(1., .1, nt+1) + x = factor[:,None] * initial + var, mean = x.var(axis=1), x.mean(axis=1) + data["x_"+arm], data["var_"+arm] = x, var + cost = {"state_cost": cfg["dt"]*var[:-1].sum(), "l2_reg_cost": cfg["beta"]*cfg["T"], + "terminal_var": var[-1], "terminal_target": cfg["gamma_target"]*mean[-1]**2, + "terminal_mean": mean[-1]} + cost["total"] = cost["state_cost"] + cost["l2_reg_cost"] + cost["terminal_target"] + metrics["costs"][arm], metrics["var_final"][arm] = cost, var[-1] + if name != "bc": + r, w = np.abs(np.exp(1j*x).mean(-1)), exporter.winding(x) + data["r_"+arm], data["winding_"+arm] = r, w + metrics["r_final"][arm], metrics["winding_final"][arm] = r[-1], w[-1] + else: + data["K_"+arm] = np.ones(nt+1, dtype=int) + if name == "noise": + for key in ("r", "winding"): + for suffix in ("mean", "q10", "q90"): + data[f"{key}_noisy_{suffix}"] = data[f"{key}_trained"].copy() + metrics["noisy_eval"] = {"sigma": .3, "n_replicates": 32, + "r_final_mean": data["r_noisy_mean"][-1], "r_final_q10": data["r_noisy_q10"][-1], + "r_final_q90": data["r_noisy_q90"][-1], "winding_final_mean": data["winding_noisy_mean"][-1]} + _dump(directory/"config.json", cfg) + _dump(directory/"metrics.json", metrics) + np.savez_compressed(directory/"trajectories.npz", **data) + # Optional pinned files are hashed only, never deserialized. + (directory/"model.pt").write_bytes(b"not a torch checkpoint") + manifest["runs"][name] = {"path": str(directory), "sha256": {p.name: _hash(p) for p in directory.iterdir()}} + manifest_path = root/"manifest.json" + _dump(manifest_path, manifest) + return manifest_path + + +@pytest.fixture(autouse=True) +def forbid_experiment_calls(monkeypatch): + def forbidden(*args, **kwargs): + raise AssertionError("No training, dynamics, or checkpoint loads are authorized") + monkeypatch.setattr(training, "train_controller", forbidden) + for name in ("rollout_with_Ws", "rollout_dynamics_w1", "precompute_Ws"): + monkeypatch.setattr(dynamics, name, forbidden) + monkeypatch.setattr(artifacts, "load_controller", forbidden) + monkeypatch.setattr(torch, "load", forbidden) + + +def test_saved_array_export_preserves_inputs_and_lists_all_outputs(archived_fixture, tmp_path): + manifest = json.loads(archived_fixture.read_text()) + before = {p: _hash(Path(p)) for spec in manifest["runs"].values() + for p in (str(Path(spec["path"])/filename) for filename in spec["sha256"])} + output = tmp_path/"saved-array-output" + exporter.main(["--manifest", str(archived_fixture), "--output", str(output)]) + expected = {"bc_opinion_traj.pdf", "bc_kernel_strip.pdf", "kur_rw_panel.pdf", "kur_kernel_strip.pdf", + "kur_noise_panel.pdf", "tables.csv", "noise_summary.json", "verification.json", "provenance.json"} + assert {p.name for p in output.iterdir()} == expected + for pdf in output.glob("*.pdf"): + assert pdf.read_bytes().startswith(b"%PDF-") + provenance = json.loads((output/"provenance.json").read_text()) + assert provenance["new_experiments"] is False + assert provenance["checkpoint_loads"] is False + assert provenance["manifest"]["sha256"] == _hash(archived_fixture) + for filename, record in provenance["outputs"].items(): + assert record["sha256"] == _hash(output/filename) + assert {p: _hash(Path(p)) for p in before} == before + verification = json.loads((output/"verification.json").read_text()) + assert all(run["saved_array_checks_passed"] for run in verification["runs"].values()) + assert verification["runs"]["bc"]["passed"] is False + assert verification["runs"]["bc"]["status"] == "appendix_rounding_mismatch" + # Fixture data must not be passed off as the published values. + assert not verification["runs"]["bc"]["appendix_rounding"]["J_trained"]["matches"] + summary = json.loads((output/"noise_summary.json").read_text()) + assert summary["all_paths_zero_terminal_winding_verified"] is False + assert summary["individual_noisy_paths_saved"] is False + with (output/"tables.csv").open() as stream: + rows = list(csv.DictReader(stream)) + assert len(rows) == 8 + assert {(r["run"], r["arm"]) for r in rows} == {(r,a) for r in ("bc","k1","k2","k3") for a in exporter.ARMS} + assert not list(tmp_path.glob(".appendix-stage-*")) + + +def test_bundled_original_results_match_appendix(): + """Verify the published statistics against the actual archived inputs.""" + root = Path(__file__).resolve().parents[1] + if not (root / "data/appendix").exists(): + pytest.skip("Original appendix inputs are supplied separately") + manifest_path = root / "appendix_manifest.json" + runs, _ = exporter.load_manifest(manifest_path) + results = {name: exporter.diagnostics(name, run) for name, run in runs.items()} + assert all(result["passed"] for result in results.values()) + bc = results["bc"]["recomputed"] + assert bc["meanfield"]["clusters_final"] == 4 + assert bc["trained"]["clusters_final"] == 1 + assert abs(bc["meanfield"]["terminal_mean"]) < .0005 + assert f'{bc["trained"]["terminal_mean"]:.3f}' == "-0.001" + assert f'{bc["meanfield"]["terminal_var"]:.3f}' == "2.800" + assert bc["trained"]["terminal_var"] < 1e-12 + assert f'{bc["trained"]["max_vxi"]:.1f}' == "46.3" + for name, variance, peak in (("k1", "1.0e-02", "3.7"), + ("k2", "1.5e-05", "12.0"), + ("k3", "3.6e-08", "24.4")): + result = results[name]["recomputed"] + assert f'{result["trained"]["terminal_var"]:.1e}' == variance + assert f'{result["trained"]["max_vxi"]:.1f}' == peak + assert result["meanfield"]["max_vxi"] == 0 + k3 = results["k3"]["recomputed"] + assert f'{k3["meanfield"]["terminal_var"]:.3f}' == "29.602" + assert f'{k3["meanfield"]["r_final"]:.3f}' == "0.000" + assert f'{k3["trained"]["r_final"]:.3f}' == "1.000" + noisy = runs["noise"]["metrics"]["noisy_eval"] + assert noisy["n_replicates"] == 32 + assert f'{noisy["r_final_mean"]:.3f}' == "0.974" + manifest = json.loads(manifest_path.read_text()) + for name, source in manifest["figures"].items(): + assert _hash(root / source) == manifest["figures_sha256"][name] + + +def _mutated_manifest(source, tmp_path, name, filename, mutate): + manifest = json.loads(source.read_text()) + original = Path(manifest["runs"][name]["path"])/filename + directory = tmp_path/"mutated" + directory.mkdir() + spec = manifest["runs"][name] + # All remaining files stay read-only in the shared fixture directory. + for key in spec["sha256"]: + target = directory/key + if key != filename: + target.symlink_to(Path(spec["path"])/key) + raw = original.read_bytes() + if filename.endswith(".npz"): + with np.load(original) as z: + arrays = {key: z[key] for key in z.files} + mutate(arrays) + np.savez_compressed(directory/filename, **arrays) + else: + value = json.loads(raw) + mutate(value) + _dump(directory/filename, value) + spec["path"] = str(directory) + spec["sha256"][filename] = _hash(directory/filename) + path = tmp_path/"manifest.json" + _dump(path, manifest) + return path + + +def test_tampered_hash_fails_before_any_output(archived_fixture, tmp_path): + manifest = json.loads(archived_fixture.read_text()) + manifest["runs"]["noise"]["sha256"]["metrics.json"] = "0"*64 + path = tmp_path/"manifest.json" + _dump(path, manifest) + output = tmp_path/"absent-parent"/"out" + with pytest.raises(ValueError, match="SHA-256 mismatch"): + exporter.export_appendix(path, output) + assert not output.parent.exists() + + +@pytest.mark.parametrize("key", ["x_trained", "W_trained", "vxi_trained", "t", "r_trained", "winding_noisy_q90"]) +def test_missing_saved_arrays_never_reconstruct(archived_fixture, tmp_path, key): + path = _mutated_manifest(archived_fixture, tmp_path, "noise", "trajectories.npz", lambda d: d.pop(key)) + output = tmp_path/"exports"/"out" + with pytest.raises(ValueError, match="missing saved array"): + exporter.export_appendix(path, output) + assert not output.parent.exists() + + +@pytest.mark.parametrize("field,value", [("N", 128), ("dt", .1), ("num_steps", 10), ("jitter", .2)]) +def test_other_experiments_refused(archived_fixture, tmp_path, field, value): + path = _mutated_manifest(archived_fixture, tmp_path, "bc", "config.json", lambda d: d.update({field:value})) + output = tmp_path/"out" + with pytest.raises(ValueError, match="original scientific setting"): + exporter.export_appendix(path, output) + assert not output.exists() + + +def test_missing_scientific_config_never_defaults(archived_fixture, tmp_path): + path = _mutated_manifest(archived_fixture, tmp_path, "bc", "config.json", lambda d: d.pop("beta")) + with pytest.raises(ValueError, match="beta must be recorded"): + exporter.export_appendix(path, tmp_path/"out") + assert not (tmp_path/"out").exists() + + +def test_metrics_mismatch_refused(archived_fixture, tmp_path): + path = _mutated_manifest(archived_fixture, tmp_path, "k1", "metrics.json", lambda d: d["costs"]["trained"].update(total=0)) + with pytest.raises(ValueError, match="saved arrays disagree"): + exporter.export_appendix(path, tmp_path/"out") + assert not (tmp_path/"out").exists() + + +def test_existing_output_is_never_replaced(archived_fixture, tmp_path): + output = tmp_path/"out" + output.mkdir() + (output/"sentinel").write_text("keep") + with pytest.raises(FileExistsError): + exporter.export_appendix(archived_fixture, output) + assert (output/"sentinel").read_text() == "keep" + + +def test_output_created_during_render_is_never_overwritten(archived_fixture, tmp_path, monkeypatch): + output = tmp_path/"out" + original_mkdir = Path.mkdir + def racing_mkdir(path, *args, **kwargs): + if path == output: + original_mkdir(path) + (path/"other-writer").write_text("preserve") + raise FileExistsError("raced output") + return original_mkdir(path, *args, **kwargs) + monkeypatch.setattr(Path, "mkdir", racing_mkdir) + with pytest.raises(FileExistsError, match="raced output"): + exporter.export_appendix(archived_fixture, output) + assert {p.name for p in output.iterdir()} == {"other-writer"} + assert not list(tmp_path.glob(".appendix-stage-*")) + + +def test_publication_failure_preserves_unrelated_concurrent_content(archived_fixture, tmp_path, monkeypatch): + output = tmp_path/"out" + original_link = exporter.os.link + calls = 0 + def racing_link(source, target): + nonlocal calls + calls += 1 + if calls == 2: + (output/"other-writer").write_text("preserve") + raise OSError("simulated publication failure") + return original_link(source, target) + monkeypatch.setattr(exporter.os, "link", racing_link) + with pytest.raises(OSError, match="simulated publication failure"): + exporter.export_appendix(archived_fixture, output) + assert {p.name for p in output.iterdir()} == {"other-writer"} + assert (output/"other-writer").read_text() == "preserve" + assert not list(tmp_path.glob(".appendix-stage-*")) From f83122fe06967511f8ecff44e13a88302c83c1e8 Mon Sep 17 00:00:00 2001 From: Hydrodynamical Date: Wed, 21 Oct 2026 15:57:59 -0400 Subject: [PATCH 2/2] Bundle original appendix reproduction inputs --- .gitignore | 4 +- README.md | 2 +- .../reference_figures/bc_kernel_strip.pdf | Bin 0 -> 135649 bytes .../reference_figures/bc_opinion_traj.pdf | Bin 0 -> 33645 bytes .../reference_figures/kur_kernel_strip.pdf | Bin 0 -> 96628 bytes .../reference_figures/kur_noise_panel.pdf | Bin 0 -> 31012 bytes .../reference_figures/kur_rw_panel.pdf | Bin 0 -> 21893 bytes data/appendix/runs/e1_k1_T5/config.json | 24 ++++++ data/appendix/runs/e1_k1_T5/metrics.json | 48 ++++++++++++ data/appendix/runs/e1_k1_T5/trajectories.npz | Bin 0 -> 1327837 bytes data/appendix/runs/e1_k2_T12/config.json | 24 ++++++ data/appendix/runs/e1_k2_T12/metrics.json | 48 ++++++++++++ data/appendix/runs/e1_k2_T12/trajectories.npz | Bin 0 -> 3369505 bytes .../appendix/runs/e1_k2_T12_sig03/config.json | 25 +++++++ .../runs/e1_k2_T12_sig03/metrics.json | 61 +++++++++++++++ .../runs/e1_k2_T12_sig03/trajectories.npz | Bin 0 -> 3477467 bytes data/appendix/runs/e1_k3_T20/config.json | 25 +++++++ data/appendix/runs/e1_k3_T20/metrics.json | 53 +++++++++++++ data/appendix/runs/e1_k3_T20/trajectories.npz | Bin 0 -> 5782234 bytes data/appendix/runs/e2_eps1_T10/config.json | 30 ++++++++ data/appendix/runs/e2_eps1_T10/metrics.json | 70 ++++++++++++++++++ .../runs/e2_eps1_T10/trajectories.npz | Bin 0 -> 2912613 bytes docs/APPENDIX_REPRODUCTION.md | 21 ++---- tests/test_reproduce_appendix.py | 2 - 24 files changed, 419 insertions(+), 18 deletions(-) create mode 100644 data/appendix/reference_figures/bc_kernel_strip.pdf create mode 100644 data/appendix/reference_figures/bc_opinion_traj.pdf create mode 100644 data/appendix/reference_figures/kur_kernel_strip.pdf create mode 100644 data/appendix/reference_figures/kur_noise_panel.pdf create mode 100644 data/appendix/reference_figures/kur_rw_panel.pdf create mode 100644 data/appendix/runs/e1_k1_T5/config.json create mode 100644 data/appendix/runs/e1_k1_T5/metrics.json create mode 100644 data/appendix/runs/e1_k1_T5/trajectories.npz create mode 100644 data/appendix/runs/e1_k2_T12/config.json create mode 100644 data/appendix/runs/e1_k2_T12/metrics.json create mode 100644 data/appendix/runs/e1_k2_T12/trajectories.npz create mode 100644 data/appendix/runs/e1_k2_T12_sig03/config.json create mode 100644 data/appendix/runs/e1_k2_T12_sig03/metrics.json create mode 100644 data/appendix/runs/e1_k2_T12_sig03/trajectories.npz create mode 100644 data/appendix/runs/e1_k3_T20/config.json create mode 100644 data/appendix/runs/e1_k3_T20/metrics.json create mode 100644 data/appendix/runs/e1_k3_T20/trajectories.npz create mode 100644 data/appendix/runs/e2_eps1_T10/config.json create mode 100644 data/appendix/runs/e2_eps1_T10/metrics.json create mode 100644 data/appendix/runs/e2_eps1_T10/trajectories.npz diff --git a/.gitignore b/.gitignore index 2e5e9b4..77a75a6 100644 --- a/.gitignore +++ b/.gitignore @@ -237,8 +237,8 @@ STYLE.md figures/ references/ -# Original appendix input archives are supplied separately. -/data/appendix/ +# Curated appendix reproduction inputs; other paper PDFs stay ignored. +!/data/appendix/reference_figures/*.pdf # Saved-array exports are generated, never source inputs. /out/ diff --git a/README.md b/README.md index 0c5af95..ee006e5 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ This runs a 20-iteration workflow check on a small grid and network. Results app ## Reproduce the appendix -Regenerate the existing appendix figures and table statistics from the original results (supplied separately), without training or simulation: +Regenerate the existing appendix figures and table statistics from the bundled original results, without training or simulation: ```bash python reproduce_appendix.py --manifest appendix_manifest.json --output out/appendix-regenerated diff --git a/data/appendix/reference_figures/bc_kernel_strip.pdf b/data/appendix/reference_figures/bc_kernel_strip.pdf new file mode 100644 index 0000000000000000000000000000000000000000..21f6911b5f01378f06757980a7cbb7e96a9e9d22 GIT binary patch literal 135649 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 y>vmQ3G8bcU%V@#|^0 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