Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
73b28bf
add basic neb set and jobs
esoteric-ephemera Sep 3, 2024
4ed1cd1
correct parsing of neb
esoteric-ephemera Sep 5, 2024
8ac3f36
remove vasprun xml validator from NEB jobs - non-trivial to correct
esoteric-ephemera Sep 5, 2024
420fa0d
fix automatic validator assignment
esoteric-ephemera Sep 5, 2024
5527b26
fix syntax of VaspNebFilesValidator
esoteric-ephemera Sep 5, 2024
27b44f4
gzip image dirs
esoteric-ephemera Sep 5, 2024
e19956b
first draft neb jobs for vasp + analysis
esoteric-ephemera Sep 11, 2024
4501739
Merge remote-tracking branch 'origin/main' into approx_neb
hmlli Sep 17, 2024
592e82f
[WIP] added ApproxNEB flow and jobs
hmlli Sep 26, 2024
29359f4
Merge remote-tracking branch 'origin/main' into approx_neb
hmlli Sep 26, 2024
84aea7d
[WIP] variable name change
hmlli Sep 26, 2024
8847f49
redraft neb, better schemas dependent on emmet pr
esoteric-ephemera Oct 4, 2024
63c266f
precommit
esoteric-ephemera Oct 4, 2024
6b690a3
consistent capitalization / remove symlink
esoteric-ephemera Oct 4, 2024
50cf12e
Merge branch 'materialsproject:main' into neb
esoteric-ephemera Oct 4, 2024
83962fd
linting
esoteric-ephemera Oct 4, 2024
3e75265
Add approxNEB workflows from @hmlli
esoteric-ephemera Oct 7, 2024
85a02dd
precommit
esoteric-ephemera Oct 14, 2024
42c158f
Merge branch 'main' into neb
esoteric-ephemera Oct 14, 2024
cde4ffb
add temporary emmet-core install for new doc schemas
esoteric-ephemera Oct 14, 2024
31f5139
fix emmet-core git temp install
esoteric-ephemera Oct 14, 2024
53703c8
fix emmet-core git temp install
esoteric-ephemera Oct 14, 2024
9b88863
refactor approx neb
esoteric-ephemera Oct 15, 2024
aca40c6
partial precommit
esoteric-ephemera Oct 15, 2024
10a3f9b
small fix
hmlli Oct 15, 2024
0437c41
output.energy --> output.output.energy
esoteric-ephemera Oct 16, 2024
44364bd
add option to get charge density just from chgcar, consistent with or…
esoteric-ephemera Oct 16, 2024
505ceb5
move around charge density parsing to avoid needing to store in blob
esoteric-ephemera Oct 16, 2024
ca05afe
fix some typos in aneb
esoteric-ephemera Oct 17, 2024
7bee40b
temp name change ep_output --> ep_structures
esoteric-ephemera Oct 17, 2024
e7b9390
string dict keys rather than ints ; move pydantic typing out of type_…
esoteric-ephemera Oct 17, 2024
2ca71d5
try to get optional job logic working / better naming
esoteric-ephemera Oct 21, 2024
9b496d5
partial lint
esoteric-ephemera Oct 21, 2024
a99d482
fix typing import
esoteric-ephemera Oct 21, 2024
9aad2ec
fix typing import
esoteric-ephemera Oct 21, 2024
ff864cc
change image relax maker to use custodian double relax
esoteric-ephemera Oct 23, 2024
105f7aa
partial lint
esoteric-ephemera Oct 23, 2024
7a9fb93
ensure no vasp_gam cmd sent to custodian doublerelaxation job
esoteric-ephemera Oct 23, 2024
2adb9cf
patch neb from endpoints job
esoteric-ephemera Oct 24, 2024
433918f
fix document collation
esoteric-ephemera Oct 25, 2024
3056ef9
tweak approx neb, ensure neb interpolation is enum
esoteric-ephemera Oct 25, 2024
491795e
strip hostname in vasp neb postproces
esoteric-ephemera Oct 30, 2024
c55b2cd
tweak output parsing
esoteric-ephemera Oct 30, 2024
f69edbb
add VASP NEB tests
esoteric-ephemera Oct 30, 2024
c2978c1
partial lint
esoteric-ephemera Oct 30, 2024
f370324
add working approx neb test
esoteric-ephemera Oct 31, 2024
29f5df7
partial lint
esoteric-ephemera Oct 31, 2024
fe16c70
move some vasp schemas to emmet; modify approx neb to allow for minim…
esoteric-ephemera Nov 6, 2024
3bd358f
ruff
esoteric-ephemera Nov 6, 2024
04c6dad
flesh out ase neb jobs, add option to ApproxNeb to start from migrati…
esoteric-ephemera Nov 7, 2024
c743c60
ApproxNEB --> ApproxNeb for consistent naming
esoteric-ephemera Nov 7, 2024
68a87b0
ruff ruff ruff
esoteric-ephemera Nov 7, 2024
adec024
merge main / resolve conflicts
esoteric-ephemera Nov 8, 2024
6980a49
default min_hop_distance for approxneb to be twice ionic radius
esoteric-ephemera Nov 11, 2024
d40b733
Merge remote-tracking branch 'upstream/main' into neb
esoteric-ephemera Nov 11, 2024
cb23484
unset host structure magmoms in approx neb endpoint calcs
esoteric-ephemera Nov 12, 2024
225ed68
Merge remote-tracking branch 'upstream/main' into neb
esoteric-ephemera Nov 21, 2024
0df629d
unset magmoms in image calcs - debugging to see where old and new app…
esoteric-ephemera Nov 21, 2024
9fb978b
linting
esoteric-ephemera Nov 21, 2024
88a03f9
add initial structures to approxneb output
esoteric-ephemera Nov 22, 2024
a7898c2
pcmt
esoteric-ephemera Nov 22, 2024
9390942
Merge remote-tracking branch 'upstream/main' into neb
esoteric-ephemera Nov 22, 2024
329fa00
refactor, add more output to collate results, add notes about failure…
esoteric-ephemera Nov 27, 2024
3b0e5a2
precommit
esoteric-ephemera Nov 27, 2024
8168097
Merge remote-tracking branch 'upstream/main' into neb
esoteric-ephemera Nov 27, 2024
fe38bde
Merge remote-tracking branch 'upstream/main' into neb
esoteric-ephemera Dec 11, 2024
59c892b
first pass genericizing approx neb flows
esoteric-ephemera Dec 12, 2024
3f1653e
precommit
esoteric-ephemera Dec 12, 2024
eed728b
add default option not to remap hops atomate style
esoteric-ephemera Dec 12, 2024
a63add9
fix approx neb from migration doc
esoteric-ephemera Dec 13, 2024
bed1a24
Merge remote-tracking branch 'upstream/main' into neb
esoteric-ephemera Dec 23, 2024
bda9c3c
cleanup/fix ApproxNeb single hop maker
esoteric-ephemera Jan 14, 2025
66190d7
add missing names to single hop aneb flows
esoteric-ephemera Jan 28, 2025
fe2fa44
Merge remote-tracking branch 'upstream/main' into neb
esoteric-ephemera Jan 28, 2025
1cc7888
Merge remote-tracking branch 'upstream/main' into neb
esoteric-ephemera Feb 11, 2025
a0faac7
add forcefield approx neb
esoteric-ephemera Feb 12, 2025
1a8990f
precommit
esoteric-ephemera Feb 12, 2025
46bf7d8
Merge remote-tracking branch 'upstream/main' into neb
esoteric-ephemera Feb 18, 2025
89e4ba1
refactor aneb to be common
esoteric-ephemera Feb 18, 2025
28592aa
correct from_migration_graph method on aneb flow
esoteric-ephemera Feb 20, 2025
7943086
move neb base schemas to emmet-core
esoteric-ephemera Feb 20, 2025
8d8d3d8
precommit
esoteric-ephemera Feb 20, 2025
ec936fc
add option to approx neb common maker to specify different endpoint r…
esoteric-ephemera Feb 21, 2025
42fc0f5
Merge remote-tracking branch 'upstream/main' into neb
esoteric-ephemera Feb 25, 2025
34f46e7
remove metadata in favor of top-level failure_reasons field
esoteric-ephemera Feb 25, 2025
97014ed
fix trajectory observer and file naming
esoteric-ephemera Mar 3, 2025
5d4c011
update test data to reflect emmet schema changes
esoteric-ephemera Mar 12, 2025
b156e70
resolve merge conflicts
esoteric-ephemera Apr 1, 2025
6a66546
merge conflicts
esoteric-ephemera May 27, 2025
f89f149
resolve merge conflicts
esoteric-ephemera Jun 26, 2025
4743403
bump emmet-core to use new neb schemas
esoteric-ephemera Jun 27, 2025
29870ce
Merge remote-tracking branch 'upstream/main' into neb
esoteric-ephemera Jun 30, 2025
49f8a7b
bump emmet-core in strict
esoteric-ephemera Jun 30, 2025
ff94128
incl pmg analysis diffusion dependency and bump down pmg
esoteric-ephemera Jun 30, 2025
5b984a3
update automerge / dependencies
esoteric-ephemera Jun 30, 2025
458e064
update automerge / dependencies
esoteric-ephemera Jun 30, 2025
e3e337f
have neb tests use pre-generated initial endpoints
esoteric-ephemera Jul 2, 2025
0742aa6
the mac file system lack of case sensitivity does me in yet again
esoteric-ephemera Jul 2, 2025
116d6af
add forcefield approx/neb tests
esoteric-ephemera Jul 2, 2025
5a11460
add ase neb from endpoints + num images
esoteric-ephemera Jul 3, 2025
2a32d5c
review changes 1/
esoteric-ephemera Jul 8, 2025
2a0cd3a
modify vasp flows / tests post emmet refactor
esoteric-ephemera Jul 8, 2025
daefef5
Merge remote-tracking branch 'upstream/main' into neb
esoteric-ephemera Jul 9, 2025
b539c37
full draft ase neb + tests + forcefield implementation
esoteric-ephemera Jul 10, 2025
5d9bf51
remove unnecessary ff file
esoteric-ephemera Jul 10, 2025
4f743ca
merge conflict / bump emmet for newer neb schemas
Aug 6, 2025
a210f8b
missing emmet bump
Aug 6, 2025
df51160
lingering ase npt issues
Aug 6, 2025
bfd01ae
ensure mace calculator uses dispersion when explicit model path speci…
Aug 6, 2025
3e806b7
d3 for mace test
Aug 6, 2025
b34c43c
fix torch dft-d3 kwargs + test
esoteric-ephemera Aug 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 3 additions & 3 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ jobs:
python -m pip install --upgrade pip
mkdir -p ~/.abinit/pseudos
cp -r tests/test_data/abinit/pseudos/ONCVPSP-PBE-SR-PDv0.4 ~/.abinit/pseudos
uv pip install .[strict,strict-forcefields,tests,abinit]
uv pip install torch-runstats
uv pip install .[strict,strict-forcefields,tests,abinit,approxneb]
uv pip install torch-runstats torch_dftd
uv pip install --no-deps nequip==0.5.6

- name: Install pymatgen from master if triggered by pymatgen repo dispatch
Expand Down Expand Up @@ -321,7 +321,7 @@ jobs:
run: sphinx-build docs docs_build

automerge:
needs: [lint, test-non-ase, test-notebooks-and-ase, test-force-field-notebook, docs]
needs: [docs, lint, test-force-field-notebook, test-non-ase, test-notebooks-and-ase, test-openff]
runs-on: ubuntu-latest

permissions:
Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dependencies = [
"PyYAML",
"click",
"custodian>=2024.4.18",
"emmet-core>=0.84.8",
"emmet-core>=0.84.9",
"jobflow>=0.1.11",
"monty>=2024.12.10",
"numpy",
Expand Down Expand Up @@ -62,6 +62,7 @@ forcefields = [
"sevenn>=0.9.3",
"torchdata<=0.7.1", # TODO: remove when issue fixed
]
approxneb = ["pymatgen-analysis-diffusion>=2024.7.15"]
ase = ["ase>=3.25.0"]
ase-ext = ["tblite>=0.3.0; platform_system=='Linux'"]
openmm = [
Expand Down Expand Up @@ -99,7 +100,7 @@ strict = [
"click==8.2.1",
"custodian==2025.5.12",
"dscribe==2.1.1",
"emmet-core==0.84.8",
"emmet-core==0.84.9",
"ijson==3.4.0",
"jobflow==0.2.0",
"lobsterpy==0.4.9",
Expand Down
21 changes: 21 additions & 0 deletions src/atomate2/ase/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,27 @@ def run_ase(
return relaxer.relax(mol_or_struct, steps=self.steps, **self.relax_kwargs)


@dataclass
class EmtRelaxMaker(AseRelaxMaker):
"""
Relax a structure with an EMT potential.

This serves mostly as an example of how to create atomate2
jobs with existing ASE calculators, and test purposes.

See `atomate2.ase.AseRelaxMaker` for further documentation.
"""

name: str = "EMT relaxation"

@property
def calculator(self) -> Calculator:
"""EMT calculator."""
from ase.calculators.emt import EMT

return EMT(**self.calculator_kwargs)


@dataclass
class LennardJonesRelaxMaker(AseRelaxMaker):
"""
Expand Down
25 changes: 17 additions & 8 deletions src/atomate2/ase/md.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ class AseMDMaker(AseMaker, metaclass=ABCMeta):
ionic_step_data: tuple[str, ...] | None = None
store_trajectory: StoreTrajectoryOption = StoreTrajectoryOption.PARTIAL
traj_file: str | Path | None = None
traj_file_fmt: Literal["pmg", "ase"] = "ase"
traj_file_fmt: Literal["pmg", "ase", "xdatcar"] = "ase"
traj_interval: int = 1
mb_velocity_seed: int | None = None
zero_linear_momentum: bool = False
Expand Down Expand Up @@ -247,12 +247,16 @@ def _get_ensemble_defaults(self) -> None:

# These use different kwargs for pressure
if (
isinstance(self.dynamics, DynamicsPresets)
and DynamicsPresets(self.dynamics) == DynamicsPresets.npt_berendsen
) or (
isinstance(self.dynamics, type)
and issubclass(self.dynamics, MolecularDynamics)
and self.dynamics.__name__ == "NPTBerendsen"
(
isinstance(self.dynamics, DynamicsPresets)
and DynamicsPresets(self.dynamics) == DynamicsPresets.npt_berendsen
)
or (
isinstance(self.dynamics, type)
and issubclass(self.dynamics, MolecularDynamics)
and self.dynamics.__name__ == "NPTBerendsen"
)
or (isinstance(self.dynamics, str) and self.dynamics == "berendsen")
):
stress_kwarg = "pressure_au"
else:
Expand Down Expand Up @@ -388,7 +392,12 @@ def _callback(dyn: MolecularDynamics = md_runner) -> None:
dyn.set_temperature(temperature_K=self.t_schedule[dyn.nsteps])
if self.ensemble == MDEnsemble.nvt:
return
dyn.set_stress(self.p_schedule[dyn.nsteps] * 1e3 * units.bar)

if "pressure_au" in self.ase_md_kwargs:
# set_pressure is broken for NPTBerendsen
dyn.pressure = self.p_schedule[dyn.nsteps] * 1e3 * units.bar
else:
dyn.set_stress(self.p_schedule[dyn.nsteps] * 1e3 * units.bar)

md_runner.attach(_callback, interval=1)
with contextlib.redirect_stdout(sys.stdout if self.verbose else io.StringIO()):
Expand Down
265 changes: 265 additions & 0 deletions src/atomate2/ase/neb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
"""Create NEB jobs with ASE."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING

from ase.mep.neb import idpp_interpolate, interpolate
from emmet.core.neb import NebResult
from jobflow import Flow, Maker, Response, job
from pymatgen.core import Molecule, Structure
from pymatgen.io.ase import AseAtomsAdaptor

from atomate2.ase.jobs import _ASE_DATA_OBJECTS, AseMaker
from atomate2.ase.utils import AseNebInterface
from atomate2.common.jobs.neb import NebInterpolation, _get_images_from_endpoints

if TYPE_CHECKING:
from pathlib import Path
from typing import Literal

from ase.atoms import Atoms
from ase.calculators.calculator import Calculator


@dataclass
class AseNebFromImagesMaker(AseMaker):
"""Define scheme for performing ASE NEB calculations."""

name: str = "ASE NEB maker"
neb_kwargs: dict = field(default_factory=dict)
fix_symmetry: bool = False
symprec: float | None = 1e-2
steps: int = 500
relax_kwargs: dict = field(default_factory=dict)
optimizer_kwargs: dict = field(default_factory=dict)
traj_file: str | None = None
traj_file_fmt: Literal["pmg", "ase", "xdatcar"] = "ase"
traj_interval: int = 1
neb_doc_kwargs: dict = field(default_factory=dict)

def run_ase(
self,
images: list[Atoms | Structure | Molecule],
prev_dir: str | Path | None = None,
) -> NebResult:
"""
Run an ASE NEB job from a list of images.

Parameters
----------
images: list of pymatgen .Molecule or .Structure
pymatgen molecule or structure images
prev_dir : str or Path or None
A previous calculation directory to copy output files from. Unused, just
added to match the method signature of other makers.
"""
return AseNebInterface(
calculator=self.calculator,
fix_symmetry=self.fix_symmetry,
symprec=self.symprec,
).run_neb(
images,
steps=self.steps,
traj_file=self.traj_file,
traj_file_fmt=self.traj_file_fmt,
interval=self.traj_interval,
neb_doc_kwargs=self.neb_doc_kwargs,
neb_kwargs=self.neb_kwargs,
optimizer_kwargs=self.optimizer_kwargs,
**self.relax_kwargs,
)

@job(data=_ASE_DATA_OBJECTS, schema=NebResult)
def make(
self,
images: list[Structure | Molecule],
prev_dir: str | Path | None = None,
) -> NebResult:
"""
Run an ASE NEB job from a list of images.

Parameters
----------
images: list of pymatgen .Molecule or .Structure
pymatgen molecule or structure images
prev_dir : str or Path or None
A previous calculation directory to copy output files from. Unused, just
added to match the method signature of other makers.
"""
# Note that images are copied to prevent them from being overwritten
# by ASE during the NEB run
return self.run_ase([image.copy() for image in images], prev_dir=prev_dir)


@dataclass
class AseNebFromEndpointsMaker(AseNebFromImagesMaker):
"""Maker to create ASE NEB jobs from two endpoints.

Optionally relax the two endpoints and return a full NEB hop analysis.
If a maker to relax the endpoints is not specified, this job
interpolates the provided endpoints and performs NEB on the
interpolated images.

Parameters
----------
endpoint_relax_maker : Maker or None (default)
Optional maker to initially relax the endpoints.
"""

endpoint_relax_maker: Maker | None = None

@job
def interpolate_endpoints(
self,
endpoints: tuple[Structure | Molecule, Structure | Molecule],
num_images: int,
interpolation_method: NebInterpolation | str = NebInterpolation.LINEAR,
**interpolation_kwargs,
) -> list[Atoms]:
"""
Interpolate between two endpoints using ASE's methods, as a job.

Note that `num_images` specifies the number of intermediate images
between two endpoints. Thus, specifying `num_images = 5` will return
the endpoints and 5 intermediate images.

Parameters
----------
endpoints : tuple[Structure,Structure] or list[Structure]
A set of two endpoints to interpolate NEB images from.
num_images : int
The number of images to include in the interpolation.
interpolation_method : .NebInterpolation
The method to use to interpolate between images.
**interpolation_kwargs
kwargs to pass to the interpolation function.
"""
# return interpolate_endpoints_ase(
# endpoints, num_images, interpolation_method, **interpolation_kwargs
# )
interpolated = _get_images_from_endpoints(
endpoints,
num_images,
interpolation_method=interpolation_method,
**interpolation_kwargs,
)
adaptor = AseAtomsAdaptor()
return [adaptor.get_atoms(image) for image in interpolated]

@job(data=_ASE_DATA_OBJECTS)
def make(
self,
endpoints: tuple[Structure | Molecule, Structure | Molecule]
| list[Structure | Molecule],
num_images: int,
prev_dir: str | Path = None,
interpolation_method: NebInterpolation | str = NebInterpolation.LINEAR,
**interpolation_kwargs,
) -> Flow:
"""
Make an NEB job from a set of endpoints.

Parameters
----------
endpoints : tuple[Structure,Structure] or list[Structure]
A set of two endpoints to interpolate NEB images from.
num_images : int
The number of images to include in the interpolation.
prev_dir : str or Path or None (default)
A previous directory to copy outputs from.
interpolation_method : .NebInterpolation
The method to use to interpolate between images.
**interpolation_kwargs
kwargs to pass to the interpolation function.
"""
if len(endpoints) != 2:
raise ValueError("Please specify exactly two endpoint structures.")

endpoint_jobs = []
if self.endpoint_relax_maker is not None:
endpoint_jobs += [
self.endpoint_relax_maker.make(endpoint, prev_dir=prev_dir)
for endpoint in endpoints
]
for idx in range(2):
endpoint_jobs[idx].append_name(f" endpoint {idx + 1}")
endpoints = [relax_job.output.structure for relax_job in endpoint_jobs]

get_images = self.interpolate_endpoints(
endpoints,
num_images,
interpolation_method=interpolation_method,
**interpolation_kwargs,
)

neb_from_images = super().make(get_images.output)

flow = Flow(
[*endpoint_jobs, get_images, neb_from_images],
output=neb_from_images.output,
)

return Response(replace=flow, output=neb_from_images.output)


def interpolate_endpoints_ase(
endpoints: tuple[Structure | Molecule | Atoms, Structure | Molecule | Atoms],
num_images: int,
interpolation_method: NebInterpolation | str = NebInterpolation.LINEAR,
**interpolation_kwargs,
) -> list[Atoms]:
"""
Interpolate between two endpoints using ASE's methods.

Note that `num_images` specifies the number of intermediate images
between two endpoints. Thus, specifying `num_images = 5` will return
the endpoints and 5 intermediate images.

Parameters
----------
endpoints : tuple[Structure,Structure] or list[Structure]
A set of two endpoints to interpolate NEB images from.
num_images : int
The number of images to include in the interpolation.
interpolation_method : .NebInterpolation
The method to use to interpolate between images.
**interpolation_kwargs
kwargs to pass to the interpolation function.

Returns
-------
list of Atoms : the atoms interpolated between endpoints.
"""
endpoint_atoms = [
AseAtomsAdaptor().get_atoms(ions)
if isinstance(ions, Structure | Molecule)
else ions.copy()
for ions in endpoints
]
images = [
endpoint_atoms[0],
*[endpoint_atoms[0].copy() for _ in range(num_images)],
endpoint_atoms[1],
]

interp_method = NebInterpolation(interpolation_method)
if interp_method == NebInterpolation.LINEAR:
interpolate(images, **interpolation_kwargs)
elif interp_method == NebInterpolation.IDPP:
idpp_interpolate(images, **interpolation_kwargs)
return images


class EmtNebFromImagesMaker(AseNebFromImagesMaker):
"""EMT NEB from images maker."""

name: str = "EMT NEB from images maker"

@property
def calculator(self) -> Calculator:
"""EMT calculator."""
from ase.calculators.emt import EMT

return EMT(**self.calculator_kwargs)
Loading
Loading