Download code/reproduce/table2.py from coarse-grained-forecasting-benchmark/forecasting-models: direct link, hf CLI and curl.
- Browser
- Download file 13.3 kB
-
https://huggingface.co/coarse-grained-forecasting-benchmark/forecasting-models/resolve/main/code/reproduce/table2.py
- Command line
-
hf download hf://coarse-grained-forecasting-benchmark/forecasting-models/code/reproduce/table2.py
-
curl -L -o table2.py https://huggingface.co/coarse-grained-forecasting-benchmark/forecasting-models/resolve/main/code/reproduce/table2.py
13.3 kB
| """Reproduce one entry of Table 2, or one depth point of Table 6 / Fig. 3: csFID-10k of a model on the ten test conditions. | |
| python -m reproduce.table2 list | |
| python -m reproduce.table2 check --system SYS --method M [--S S] [--checkpoint FILE] [--samples DIR] [--hash-large] | |
| python -m reproduce.table2 run --system SYS --method M [--S S] --checkpoint FILE --out DIR [--device cuda] | |
| python -m reproduce.table2 generate --system SYS --method M [--S S] --checkpoint FILE --out DIR [--gids g1,g2] [--n-samples N] | |
| python -m reproduce.table2 score --system SYS --method M [--S S] --out DIR [--samples DIR] | |
| python -m reproduce.table2 compare --system SYS --method M [--S S] --out DIR [--tolerance REL] | |
| SYS is euler | sqg | waves | nlkg | passive_scalar; M is direct_si | edm | cascaded_si (without --S the selected depth S*, | |
| with --S the Cascaded SI model of that depth). The package and the truth bank are taken from <SYS>_DATA and | |
| <SYS>_TRUTH_BANK or given with --data and --truth-bank. Everything else comes from records/<SYS>/: the run configuration | |
| of the model, its sampler resolution n*, the image adapter and the recorded values. | |
| generate 10,000 samples per test condition (bifurcation.sample with the settings of configs/evaluation.yaml: | |
| n* sampler steps, noise stream 777 * 100003 + gid, chunks of 2,000) -> OUT/samples/<variant>/group_<gid>.npz | |
| score csFID per condition (bifurcation.fid.evaluate.evaluate_csfid_run) of OUT/samples, or of the archived samples | |
| given with --samples -> OUT/csfid/{csfid_per_ic.csv, csfid_summary.json, MANIFEST_CSFID.json} | |
| compare per-condition values, mean and SEM against the recorded values -> OUT/COMPARE.json | |
| run generate + score + compare | |
| check the package, truth bank, checkpoint and archived samples against the recorded sha256 | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import csv | |
| import hashlib | |
| import json | |
| import sys | |
| from datetime import datetime, timezone | |
| from pathlib import Path | |
| from bifurcation.config import RECORDS, REPO, SYSTEMS, data_paths, dataset_card, evaluation_settings, records, run_config, unit | |
| def sha256(path: Path) -> str: | |
| h = hashlib.sha256() | |
| with open(path, "rb") as f: | |
| for chunk in iter(lambda: f.read(1 << 24), b""): | |
| h.update(chunk) | |
| return h.hexdigest() | |
| def asset_hashes(system: str) -> dict: | |
| """{section: {relative path: sha256}} from records/<system>/assets.sha256.""" | |
| out, sec = {}, None | |
| for line in (RECORDS / system / "assets.sha256").read_text().splitlines(): | |
| if line.startswith("# "): | |
| sec = line[2:].split(":")[0].strip() | |
| continue | |
| h, q = line.split(" ", 1) | |
| out.setdefault(sec, {})[q] = h | |
| return out | |
| def cmd_list(a): | |
| print(f"{'system':15s} {'model':16s} {'paper':22s} {'n*':>4s} {'NFE':>6s} csFID-10k") | |
| for s in SYSTEMS: | |
| rec = records(s) | |
| for u in rec["units"]: | |
| names = ", ".join({"direct_si": "Direct SI", "edm": "EDM", "cascaded_si": f"Cascaded SI (S*={rec['s_star']})"}[n] | |
| for n in u["paper_names"]) or f"depth S={u['S']}" | |
| e = u["expected_csfid"] | |
| print(f"{s:15s} {u['key']:16s} {names:22s} {u['sampler']['n_steps']:4d} {u['sampler']['nfe']:6d} {e['mean']:.2f} +- {e['sem']:.2f}") | |
| def cmd_check(a) -> int: | |
| rec, u = unit(a.system, a.method, a.S) | |
| data, bank = data_paths(a.system, a.data, a.truth_bank) | |
| card = dataset_card(a.system) | |
| problems, checked = [], 0 | |
| big = 200 * 1024 * 1024 | |
| for rel, want in card["expected_sha256"].items(): | |
| p = data / rel | |
| if not p.is_file(): | |
| problems.append(f"--data: missing {rel}"); continue | |
| if p.stat().st_size > big and not a.hash_large: | |
| continue | |
| checked += 1 | |
| if sha256(p) != want: | |
| problems.append(f"--data: {rel} sha256 differs from configs/{a.system}/dataset.yaml") | |
| hashes = asset_hashes(a.system) | |
| for gid in rec["test_gids"]: | |
| rel = f"group_{gid:04d}.npz" | |
| p = bank / rel | |
| if not p.is_file(): | |
| problems.append(f"--truth-bank: missing {rel}"); continue | |
| if a.hash_large: | |
| checked += 1 | |
| if sha256(p) != hashes["truth_bank"][rel]: | |
| problems.append(f"--truth-bank: {rel} sha256 differs") | |
| if a.checkpoint: | |
| got = sha256(Path(a.checkpoint)); checked += 1 | |
| if u["checkpoint"] is None: | |
| problems.append(f"--checkpoint: no checkpoint of {u['key']} is distributed") | |
| elif got != u["checkpoint"]["sha256"]: | |
| problems.append(f"--checkpoint: sha256 {got[:16]} is not the recorded {u['checkpoint']['file']}") | |
| if a.samples: | |
| v = u["sampler"]["variant"] | |
| sec = f"samples/{u['paper_names'][0]}" if u["paper_names"] else None # archived samples exist for the Table 2 models | |
| for gid in rec["test_gids"]: | |
| rel = f"{v}/group_{gid:04d}.npz" | |
| p = Path(a.samples) / rel | |
| if not p.is_file(): | |
| problems.append(f"--samples: missing {rel}"); continue | |
| want = (hashes.get(sec) or {}).get(rel) if sec else None | |
| if want and a.hash_large: | |
| checked += 1 | |
| if sha256(p) != want: | |
| problems.append(f"--samples: {rel} sha256 differs from the archived samples") | |
| print(json.dumps({"system": a.system, "model": u["key"], "files_hashed": checked, "problems": problems}, indent=1)) | |
| return 2 if problems else 0 | |
| def _fresh(out: Path, sub: str) -> Path: | |
| d = out / sub | |
| if d.exists() and any(d.iterdir()): | |
| raise SystemExit(f"refusing: {d} exists and is not empty (use a fresh --out)") | |
| d.mkdir(parents=True, exist_ok=True) | |
| return d | |
| def cmd_generate(a): | |
| from bifurcation import sample | |
| import yaml | |
| rec, u = unit(a.system, a.method, a.S) | |
| data, bank = data_paths(a.system, a.data, a.truth_bank) | |
| ev = evaluation_settings() | |
| out = Path(a.out).resolve() | |
| out.mkdir(parents=True, exist_ok=True) | |
| cfg_path = out / "config.yaml" | |
| cfg_path.write_text(yaml.safe_dump(run_config(a.system, u, data), sort_keys=False)) | |
| n_samples = int(a.n_samples) if getattr(a, "n_samples", None) else int(ev["fid"]["n_generated"]) | |
| argv = ["--config", str(cfg_path), "--data", str(data), "--checkpoint", str(Path(a.checkpoint).resolve()), | |
| "--out", str(out / "samples"), "--split", "test", "--n-samples", str(n_samples), | |
| "--n-steps", str(u["sampler"]["n_steps"]), "--seed-base", str(ev["sampling"]["crn_base_seed"]), | |
| "--chunk", str(ev["sampling"]["sample_chunk"]), "--device", a.device] | |
| if rec["x0_from_truth_bank"]: | |
| argv += ["--truth-bank", str(bank)] | |
| if a.gids: | |
| argv += ["--gids", a.gids] | |
| print("$ python -m bifurcation.sample " + " ".join(argv), flush=True) | |
| sample.main(argv) | |
| log = out / "GENERATED.json" | |
| entries = json.loads(log.read_text()) if log.is_file() else [] | |
| entries.append({"system": a.system, "model": u["key"], "checkpoint": str(Path(a.checkpoint).resolve()), "gids": a.gids or "all", | |
| "argv": argv, "at": datetime.now(timezone.utc).isoformat(timespec="seconds")}) | |
| log.write_text(json.dumps(entries, indent=1) + "\n") | |
| def cmd_score(a): | |
| from bifurcation.fid.adapters import FIDAdapter | |
| from bifurcation.fid.evaluate import evaluate_csfid_run, generated_states | |
| rec, u = unit(a.system, a.method, a.S) | |
| data, bank = data_paths(a.system, a.data, a.truth_bank) | |
| card, fid_cfg = dataset_card(a.system), evaluation_settings()["fid"] | |
| out = Path(a.out).resolve() | |
| samples = Path(a.samples).resolve() if a.samples else out / "samples" | |
| variant = u["sampler"]["variant"] | |
| missing = [g for g in rec["test_gids"] if not (samples / variant / f"group_{g:04d}.npz").is_file()] | |
| if missing: | |
| raise SystemExit(f"{samples / variant}: no samples for test conditions {missing}") | |
| n_gen = int(fid_cfg["n_generated"]) | |
| gen_raw = generated_states(samples, variant, data, n_gen) | |
| gen_raw = {g: x for g, x in gen_raw.items() if g in set(rec["test_gids"])} | |
| for g, x in gen_raw.items(): | |
| if len(x) != n_gen: | |
| raise SystemExit(f"condition {g}: {len(x)} generated states, the paper's read uses {n_gen}") | |
| adapter = FIDAdapter.load(RECORDS / a.system / "fid_adapter.json", **(card["fid_adapter"].get("params") or {})) | |
| csdir = _fresh(out, "csfid") | |
| summary = evaluate_csfid_run(gen_raw, bank, int(rec["fine_horizon_idx"]), int(fid_cfg["n_truth"]), adapter, csdir, | |
| out / "fid_cache", fid_cfg, a.device, | |
| provenance={"system": a.system, "model": u["key"], "samples": str(samples), "source": "reproduce.table2 score"}) | |
| print(f"csFID-10k {a.system} {u['key']}: {summary['csFID']['mean']:.6f} +- {summary['csFID']['sem']:.6f} (n = {summary['csFID']['n']})") | |
| def cmd_compare(a) -> int: | |
| rec, u = unit(a.system, a.method, a.S) | |
| out = Path(a.out).resolve() | |
| got = {int(r["gid"]): float(r["sfid"]) for r in csv.DictReader(open(out / "csfid" / "csfid_per_ic.csv"))} | |
| summ = json.loads((out / "csfid" / "csfid_summary.json").read_text())["csFID"] | |
| exp = u["expected_csfid"] | |
| rows, worst = [], 0.0 | |
| for g in rec["test_gids"]: | |
| e, v = exp["per_condition"][str(g)], got.get(g, float("nan")) | |
| rel = abs(v - e) / abs(e) | |
| worst = max(worst, rel) | |
| rows.append({"gid": g, "recorded": e, "this_run": v, "rel_diff": rel}) | |
| agg = {k: {"recorded": exp[k], "this_run": summ[k], "rel_diff": abs(summ[k] - exp[k]) / abs(exp[k])} for k in ("mean", "sem")} | |
| for r in rows: | |
| print(f" gid {r['gid']:4d} recorded {r['recorded']:.6f} this run {r['this_run']:.6f} rel diff {r['rel_diff']:.2e}") | |
| print(f" mean {summ['mean']:.6f} (recorded {exp['mean']:.6f}, rel diff {agg['mean']['rel_diff']:.2e}); " | |
| f"SEM {summ['sem']:.6f} (recorded {exp['sem']:.6f}, rel diff {agg['sem']['rel_diff']:.2e})") | |
| verdict = {"system": a.system, "model": u["key"], "max_rel_diff_per_condition": worst, "aggregate": agg, "per_condition": rows, | |
| "bitwise_equal": all(r["rel_diff"] == 0.0 for r in rows) and all(v["rel_diff"] == 0.0 for v in agg.values())} | |
| if a.tolerance is not None: | |
| verdict["tolerance"] = a.tolerance | |
| verdict["within_tolerance"] = worst <= a.tolerance and all(v["rel_diff"] <= a.tolerance for v in agg.values()) | |
| (out / "COMPARE.json").write_text(json.dumps(verdict, indent=1) + "\n") | |
| print(f" max relative difference per condition: {worst:.3e}" + (f"; within {a.tolerance:g}: {verdict['within_tolerance']}" if a.tolerance is not None else "")) | |
| return 1 if (a.tolerance is not None and not verdict["within_tolerance"]) else 0 | |
| def cmd_run(a) -> int: | |
| cmd_generate(a) | |
| a.samples = None | |
| cmd_score(a) | |
| return cmd_compare(a) | |
| def main(argv=None) -> int: | |
| ap = argparse.ArgumentParser(prog="python -m reproduce.table2", description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) | |
| sub = ap.add_subparsers(dest="cmd", required=True) | |
| sub.add_parser("list") | |
| def unit_args(p, need_data=True): | |
| p.add_argument("--system", required=True, choices=SYSTEMS) | |
| p.add_argument("--method", required=True, choices=("direct_si", "edm", "cascaded_si")) | |
| p.add_argument("--S", type=int, default=None, help="depth of a Cascaded SI model (Table 6); default: the selected S*") | |
| if need_data: | |
| p.add_argument("--data", default=None, help="dataset package directory (default: $<SYSTEM>_DATA)") | |
| p.add_argument("--truth-bank", default=None, help="truth-bank directory (default: $<SYSTEM>_TRUTH_BANK)") | |
| c = sub.add_parser("check"); unit_args(c) | |
| c.add_argument("--checkpoint"); c.add_argument("--samples") | |
| c.add_argument("--hash-large", action="store_true", help="also hash the large files (trajectories, truth banks, samples)") | |
| for name in ("generate", "run"): | |
| g = sub.add_parser(name); unit_args(g) | |
| g.add_argument("--checkpoint", required=True); g.add_argument("--out", required=True) | |
| g.add_argument("--gids", default=None, help="comma-separated subset of the test conditions (split generation across jobs)") | |
| g.add_argument("--device", default="cuda") | |
| if name == "generate": | |
| g.add_argument("--n-samples", type=int, default=None, help="fewer samples per condition (a smoke test; `score` refuses fewer than 10,000)") | |
| if name == "run": | |
| g.add_argument("--tolerance", type=float, default=None) | |
| s = sub.add_parser("score"); unit_args(s) | |
| s.add_argument("--out", required=True); s.add_argument("--samples", default=None, help="archived samples (default OUT/samples)") | |
| s.add_argument("--device", default="cuda") | |
| m = sub.add_parser("compare"); unit_args(m, need_data=False) | |
| m.add_argument("--out", required=True); m.add_argument("--tolerance", type=float, default=None) | |
| a = ap.parse_args(argv) | |
| fn = {"list": cmd_list, "check": cmd_check, "generate": cmd_generate, "score": cmd_score, "compare": cmd_compare, "run": cmd_run}[a.cmd] | |
| return int(fn(a) or 0) | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |