MagNET / analysis /code /magnet_benchmark_run.py
ekwan16's picture
Add files using upload-large-folder tool
ef53368 verified
Raw
History Blame Contribute Delete
5.55 kB
"""Produce the magnet_benchmark results table by running MagNET and MagNET-x over the four test sets
and write it to performance_results.csv.
Needs the checkpoints and datasets, so it does not run in CI; they ship in the repo (Git LFS via the
Hugging Face checkout) and resolve automatically. One row per (model, test set, nucleus) with median
AE, MAE, RMSE, and atom count. The three vibrated/stationary sets are sampled; isolated-chloroform is
run in full. The shipped CSV used --sample 500 --seed 0; re-running reproduces it (sampled rows within
tolerance, isolated rows exactly).
Usage:
python magnet_benchmark_run.py --out performance_results.csv --n-passes 1 --sample 500 --seed 0
"""
import argparse
import csv
import os
import sys
import time
from paths import ensure_on_path, repo_root, dataset_file
HERE = os.path.dirname(os.path.abspath(__file__))
GH = repo_root(__file__)
if HERE not in sys.path:
sys.path.insert(0, HERE)
ensure_on_path("analysis", "code", "shared", file=__file__) # stats.py etc. live here
ensure_on_path("magnet", file=__file__)
for _d in ("sigma-shake", "gdb_qcd", "dft8k", "sigma-fresh"):
ensure_on_path("data", _d, file=__file__)
import magnet_benchmark as M # noqa: E402
from run_magnet import compute_MagNET_foundation_shieldings, _predict_with # noqa: E402
def magnet_x_chloroform_shieldings(ans, geos, n_passes, device):
"""Absolute shieldings from the MagNET-x chloroform model (run as a plain predictor, no solvent),
used to score MagNET-x on the same test sets as the foundation model."""
return _predict_with("MagNET-x-chloroform_H", "MagNET-x-chloroform_C", ans, geos,
n_passes=n_passes, device=device)
MODEL_RUNNERS = {
"MagNET": compute_MagNET_foundation_shieldings,
"MagNET-x": magnet_x_chloroform_shieldings,
}
def assemble_all(paths, sample, seed):
"""Assemble the four test sets, returning {test_set: (ans, geos, dfts)}."""
from decode_sigma_shake import SigmaShake
from decode_gdb_qcd import GDBQCD
from dft8k_reader import DFT8k
from decode_sigma_fresh import SigmaFresh
raw = {}
with SigmaShake(paths["sigma_shake"]) as ss:
raw["stationary_internal"] = M.cases_stationary_internal(ss, n_sample=sample, seed=seed)
with GDBQCD(paths["gdb_qcd"]) as gq:
raw["vibrated_internal"] = M.cases_vibrated_internal(gq, n_sample=sample // 3, seed=seed)
with DFT8k(paths["dft8k"]) as d8:
raw["vibrated_external"] = M.cases_vibrated_external(d8, n_sample=sample, seed=seed)
with SigmaFresh(paths["sigma_fresh"]) as sf:
# the full published test set (held-out solutes, frames 1..10 with an isolated calculation);
# not sampled, so this row reproduces Tables S3/S4 exactly
raw["isolated_chloroform"] = M.cases_isolated_chloroform(sf, scrub=False)
# exclude molecules with elements outside MagNET's vocabulary (e.g. phosphorus in dft8k); the
# model would refuse them anyway, and scoring them would not be a fair test
cases = {}
for ts, (ans, geos, dfts) in raw.items():
a, g, d, dropped = M.filter_supported(ans, geos, dfts)
if dropped:
print(f" {ts}: dropped {dropped} structure(s) with unsupported elements", flush=True)
cases[ts] = (a, g, d)
return cases
def main():
"""Assemble the four test sets, run both models over them, and write performance_results.csv."""
ap = argparse.ArgumentParser()
ap.add_argument("--out", default=os.path.join(HERE, "..", "..", "data", "magnet_benchmark", "performance_results.csv"))
ap.add_argument("--n-passes", type=int, default=1)
ap.add_argument("--sample", type=int, default=500)
ap.add_argument("--seed", type=int, default=0)
ap.add_argument("--device", default="cpu")
ap.add_argument("--sigma-shake", default=dataset_file("sigma-shake", root=GH))
ap.add_argument("--gdb-qcd", default=dataset_file("gdb_qcd", root=GH))
ap.add_argument("--dft8k", default=dataset_file("dft8k", root=GH))
ap.add_argument("--sigma-fresh", default=dataset_file("sigma-fresh", root=GH))
args = ap.parse_args()
paths = {"sigma_shake": args.sigma_shake, "gdb_qcd": args.gdb_qcd,
"dft8k": args.dft8k, "sigma_fresh": args.sigma_fresh}
print(f"assembling test cases (sample={args.sample}, seed={args.seed})...", flush=True)
cases = assemble_all(paths, args.sample, args.seed)
for ts, (ans, _, _) in cases.items():
print(f" {ts}: {len(ans)} structures", flush=True)
rows = []
for model, runner in MODEL_RUNNERS.items():
for ts in M.TEST_SETS:
ans, geos, dfts = cases[ts]
t = time.time()
pred = runner(ans, geos, args.n_passes, args.device)
tab = M.error_table(pred, dfts, ans)
dt = time.time() - t
for nucleus in ("1H", "13C"):
s = tab[nucleus]
rows.append(dict(model=model, test_set=ts, nucleus=nucleus,
median_ae=s["median_ae"], mae=s["mae"], rmse=s["rmse"], n=s["n"]))
print(f"{model:9s} {ts:20s} ({dt:.0f}s) "
f"1H mae {tab['1H']['mae']:.4f} 13C mae {tab['13C']['mae']:.4f}", flush=True)
with open(args.out, "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=["model", "test_set", "nucleus", "median_ae", "mae", "rmse", "n"])
w.writeheader()
for r in rows:
w.writerow(r)
print(f"wrote {args.out} ({len(rows)} rows)")
if __name__ == "__main__":
main()