matter-os / scripts /active_learning_benchmark.py
tmepass000's picture
MATTER OS v0.1.0
87b48de verified
Raw History Blame Contribute Delete
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()