Download scripts/active_learning_benchmark.py from tmepass000/matter-os: direct link, hf CLI and curl.
- Browser
- Download file 4.46 kB
-
https://huggingface.co/tmepass000/matter-os/resolve/main/scripts/active_learning_benchmark.py
- Command line
-
hf download hf://tmepass000/matter-os/scripts/active_learning_benchmark.py
-
curl -L -o active_learning_benchmark.py https://huggingface.co/tmepass000/matter-os/resolve/main/scripts/active_learning_benchmark.py
4.46 kB
| """Phase-6 exit-gate measurement: active-learning selection vs a fixed (random-order) baseline. | |
| Task: from a pool of N candidate Cu5Au2 clusters, find the lowest EMT relaxed energy with as few | |
| "expensive" evaluations (tight EMT relaxations) as possible. | |
| baseline : evaluate pool members in a random order | |
| AL (EI) : 3 random seeds, then GP expected improvement on structure fingerprints (discovery/active_learning.py) | |
| Two feature settings: | |
| A) fingerprint of the UNRELAXED structure (hard: features weakly related to the relaxed energy) | |
| B) fingerprint after a very cheap pre-relaxation (8 FIRE steps) - a multi-fidelity setting | |
| Reports the median / mean number of evaluations to hit the pool minimum over many seeds. Results are | |
| written to docs/benchmarks/active_learning.json and reported as measured, whichever way they come out. | |
| python scripts/active_learning_benchmark.py [--pool 60] [--trials 30] | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| import time | |
| from pathlib import Path | |
| import numpy as np | |
| ROOT = Path(__file__).resolve().parent.parent | |
| sys.path.insert(0, str(ROOT)) | |
| from ase.calculators.emt import EMT # noqa: E402 | |
| from ase.optimize import BFGS, FIRE # noqa: E402 | |
| from core.representation import MatterStructure # noqa: E402 | |
| from discovery.active_learning import select # noqa: E402 | |
| from discovery.generators import random_cluster # noqa: E402 | |
| def build_pool(n: int, seed: int = 0): | |
| rng = np.random.default_rng(seed) | |
| syms = ["Cu"] * 5 + ["Au"] * 2 | |
| pool = [] | |
| for i in range(n): | |
| ms = random_cluster(list(rng.permutation(syms)), seed=int(rng.integers(2**31))) | |
| a = ms.to_ase(); a.calc = EMT() | |
| FIRE(a, logfile=None).run(fmax=0.5, steps=8) | |
| cheap = MatterStructure.from_ase(a) | |
| b = ms.to_ase(); b.calc = EMT() | |
| BFGS(b, logfile=None).run(fmax=0.01, steps=1500) | |
| pool.append({"fp_unrelaxed": ms.fingerprint(), "fp_cheap": cheap.fingerprint(), "energy": float(b.get_potential_energy())}) | |
| return pool | |
| def evals_to_min(order_fn, pool, rng): | |
| best = min(p["energy"] for p in pool) | |
| return order_fn(pool, rng, best) | |
| def random_order(pool, rng, best): | |
| for k, i in enumerate(rng.permutation(len(pool)), 1): | |
| if abs(pool[i]["energy"] - best) < 1e-9: | |
| return k | |
| return len(pool) | |
| def make_al(feature): | |
| def al(pool, rng, best): | |
| seen = list(rng.choice(len(pool), 3, replace=False)) | |
| for i in seen: | |
| if abs(pool[i]["energy"] - best) < 1e-9: | |
| return seen.index(i) + 1 | |
| while len(seen) < len(pool): | |
| rest = [i for i in range(len(pool)) if i not in seen] | |
| res = select([pool[i][feature] for i in rest], [pool[i][feature] for i in seen], [pool[i]["energy"] for i in seen], | |
| k=1, acquisition="expected_improvement", diversity_penalty=0.0) | |
| nxt = rest[res["chosen"][0]] | |
| seen.append(nxt) | |
| if abs(pool[nxt]["energy"] - best) < 1e-9: | |
| return len(seen) | |
| return len(pool) | |
| return al | |
| def main(): | |
| ap = argparse.ArgumentParser(); ap.add_argument("--pool", type=int, default=60); ap.add_argument("--trials", type=int, default=30) | |
| a = ap.parse_args() | |
| t0 = time.time() | |
| pool = build_pool(a.pool) | |
| res = {} | |
| for name, fn in [("random_baseline", random_order), ("al_ei_unrelaxed_features", make_al("fp_unrelaxed")), | |
| ("al_ei_cheap_prerelax_features", make_al("fp_cheap"))]: | |
| ks = [evals_to_min(fn, pool, np.random.default_rng(1000 + t)) for t in range(a.trials)] | |
| res[name] = {"median": float(np.median(ks)), "mean": float(np.mean(ks)), "std": float(np.std(ks)), "trials": ks} | |
| out = {"task": "evaluations needed to find the pool minimum (lower is better)", "pool_size": a.pool, "trials": a.trials, | |
| "solver": "ASE EMT, BFGS fmax=0.01", "results": {k: {kk: v[kk] for kk in ("median", "mean", "std")} for k, v in res.items()}, | |
| "raw": {k: v["trials"] for k, v in res.items()}, "elapsed_s": round(time.time() - t0, 1), | |
| "expected_random_mean": (a.pool + 1) / 2} | |
| d = ROOT / "docs" / "benchmarks"; d.mkdir(parents=True, exist_ok=True) | |
| (d / "active_learning.json").write_text(json.dumps(out, indent=1)) | |
| print(json.dumps({k: out[k] for k in ("pool_size", "trials", "results", "expected_random_mean", "elapsed_s")}, indent=1)) | |
| if __name__ == "__main__": | |
| main() | |