Spaces:
Running
Running
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 | |
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") | |
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, | |
)() | |