Yuan (Cyrus) Chiang
Add eSEN and update dependency versions (#58)
b7d94da unverified
import itertools
from pathlib import Path
import numpy as np
from ase import Atom, Atoms
from ase.calculators.calculator import BaseCalculator
from ase.data import chemical_symbols, covalent_radii, vdw_alvarez
from ase.io import read, write
from prefect import flow, task
from tqdm.auto import tqdm
from mlip_arena.models import REGISTRY, MLIPEnum
from mlip_arena.tasks.utils import get_calculator
@task
def homonuclear_diatomics(symbol: str, calculator: BaseCalculator, out_dir: Path):
"""
Calculate potential energy curves for homonuclear diatomic molecules.
This function computes the potential energy of a diatomic molecule (two atoms of
the same element) across a range of interatomic distances. The distance range is
automatically determined from the covalent and van der Waals radii of the element.
Args:
symbol: Chemical symbol of the atom (e.g., 'H', 'O', 'Fe')
calculator: ASE calculator object used to compute the potential energies. Could be VASP, MLIP, etc.
Returns:
None: Results are saved as trajectory files in a directory structure:
/{model_family}/{element_pair}/{model_name}.extxyz
Note:
- Minimum distance is set to 0.9× the covalent radius
- Maximum distance is set to 3.1× the van der Waals radius (or 6 Å if unknown)
- Distance step size is fixed at 0.01 Å
- If an existing trajectory file is found, the calculation will resume from where it left off
- The atoms are placed in a periodic box large enough to avoid self-interaction
"""
atom = Atom(symbol)
rmin = 0.9 * covalent_radii[atom.number]
rvdw = (
vdw_alvarez.vdw_radii[atom.number]
if atom.number < len(vdw_alvarez.vdw_radii)
else np.nan
)
rmax = 3.1 * rvdw if not np.isnan(rvdw) else 6
rstep = 0.01
npts = int((rmax - rmin) / rstep)
rs = np.linspace(rmin, rmax, npts)
es = np.zeros_like(rs)
da = symbol + symbol
out_dir.mkdir(parents=True, exist_ok=True)
skip = 0
a = 5 * rmax
r = rs[0]
positions = [
[a / 2 - r / 2, a / 2, a / 2],
[a / 2 + r / 2, a / 2, a / 2],
]
traj_fpath = out_dir / f"{da!s}.extxyz"
if traj_fpath.exists():
traj = read(traj_fpath, index=":")
skip = len(traj)
atoms = traj[-1]
else:
# Create the unit cell with two atoms
atoms = Atoms(
da,
positions=positions,
# magmoms=magmoms,
cell=[a, a + 0.001, a + 0.002],
pbc=False,
)
atoms.calc = calculator
for i, r in enumerate(tqdm(rs)):
if i < skip:
continue
positions = [
[a / 2 - r / 2, a / 2, a / 2],
[a / 2 + r / 2, a / 2, a / 2],
]
# atoms.set_initial_magnetic_moments(magmoms)
atoms.set_positions(positions)
es[i] = atoms.get_potential_energy()
write(traj_fpath, atoms, append="a")
@flow
def submit_homonuclear_diatomics():
futures = []
for symbol, model in itertools.product(
chemical_symbols[1:],
MLIPEnum,
):
if "homonuclear-diatomics" not in REGISTRY[model.name].get("gpu-tasks", []):
continue
out_dir = Path(__file__).parent / model.name
calculator = get_calculator(model)
# if not (out_dir / "homonuclear-diatomics.json").exists():
future = homonuclear_diatomics.submit(
symbol,
calculator,
out_dir=out_dir,
)
futures.append(future)
return [f.result(raise_on_failure=False) for f in futures]
if __name__ == "__main__":
submit_homonuclear_diatomics.with_options(
# task_runner=DaskTaskRunner(address=client.scheduler.address),
log_prints=True,
)()