Skip to content
Merged
Show file tree
Hide file tree
Changes from 99 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
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ 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 .[strict,strict-forcefields,tests,abinit,approxneb]
uv pip install torch-runstats
uv pip install --no-deps nequip==0.5.6

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
7 changes: 4 additions & 3 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.9rc",
"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.4.14",
"dscribe==2.1.1",
"emmet-core==0.84.8",
"emmet-core==0.84.9rc",
"ijson==3.4.0",
"jobflow==0.2.0",
"lobsterpy==0.4.9",
Expand All @@ -121,7 +122,7 @@ strict-openff = [
"monty==2025.3.3",
"openmm-mdanalysis-reporter==0.1.0",
"openmm==8.1.1",
"pymatgen==2025.6.14", # TODO: open ff is extremely sensitive to pymatgen version
"pymatgen==2024.11.13", # TODO: open ff is extremely sensitive to pymatgen version
]
strict-forcefields = [
"calorine==3.1",
Expand Down
2 changes: 1 addition & 1 deletion src/atomate2/ase/md.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,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
100 changes: 100 additions & 0 deletions src/atomate2/ase/neb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""Create NEB jobs with ASE."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING

from emmet.core.neb import NebResult
from jobflow import job

from atomate2.ase.jobs import _ASE_DATA_OBJECTS, AseMaker
from atomate2.ase.utils import AseNebInterface

if TYPE_CHECKING:
from pathlib import Path
from typing import Literal

from ase.calculators.calculator import Calculator
from pymatgen.core import Molecule, Structure


@dataclass
class AseNebMaker(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[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.
"""
return self.run_ase(images, prev_dir=prev_dir)


class LennardJonesNebMaker(AseNebMaker):
"""Lennard-Jones NEB maker, primarily for testing/debugging."""

name: str = "Lennard-Jones 6-12 NEB"

@property
def calculator(self) -> Calculator:
"""Lennard-Jones calculator."""
from ase.calculators.lj import LennardJones

return LennardJones(**self.calculator_kwargs)
172 changes: 171 additions & 1 deletion src/atomate2/ase/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import os
import sys
import time
from copy import deepcopy
from pathlib import Path
from typing import TYPE_CHECKING

import numpy as np
Expand All @@ -17,8 +19,10 @@
from ase.filters import FrechetCellFilter
from ase.io import Trajectory as AseTrajectory
from ase.io import write as ase_write
from ase.mep.neb import NEB
from ase.optimize import BFGS, FIRE, LBFGS, BFGSLineSearch, LBFGSLineSearch, MDMin
from ase.optimize.sciopt import SciPyFminBFGS, SciPyFminCG
from emmet.core.neb import NebMethod, NebResult
from monty.serialization import dumpfn
from pymatgen.core.structure import Molecule, Structure
from pymatgen.core.trajectory import Trajectory as PmgTrajectory
Expand Down Expand Up @@ -46,6 +50,15 @@
"BFGSLineSearch": BFGSLineSearch,
}

FORCE_BASED_OPTIMIZERS = {
"FIRE": FIRE,
"BFGS": BFGS,
"MDMin": MDMin,
}

# Parameters chosen for consistency with atomate2.vasp.sets.core.NebSetGenerator
DEFAULT_NEB_KWARGS = {"k": 5.0, "climb": True, "method": "improvedtangent"}


class TrajectoryObserver:
"""Trajectory observer.
Expand Down Expand Up @@ -149,6 +162,8 @@ def save(
self.to_pymatgen_trajectory(filename=filename, file_format=fmt) # type: ignore[arg-type]
elif fmt == "ase":
self.to_ase_trajectory(filename=filename)
else:
raise ValueError(f"Unknown trajectory format {fmt}.")

def to_ase_trajectory(
self, filename: str | None = "atoms.traj"
Expand Down Expand Up @@ -329,6 +344,7 @@ def relax(
fmax: float = 0.1,
steps: int = 500,
traj_file: str = None,
traj_file_fmt: Literal["pmg", "ase", "xdatcar"] = "ase",
final_atoms_object_file: str | os.PathLike[str] = "final_atoms_object.xyz",
interval: int = 1,
verbose: bool = False,
Expand Down Expand Up @@ -388,7 +404,7 @@ def relax(
t_f = time.perf_counter()

if traj_file is not None:
obs.save(traj_file)
obs.save(traj_file, fmt=traj_file_fmt)
if isinstance(atoms, cell_filter):
atoms = atoms.atoms

Expand Down Expand Up @@ -420,3 +436,157 @@ def relax(
dir_name=os.getcwd(),
elapsed_time=t_f - t_i,
)


class AseNebInterface:
"""Perform NEB using the Atomic Simulation Environment."""

def __init__(
self,
calculator: Calculator,
optimizer: Optimizer | str = "FIRE",
fix_symmetry: bool = False,
symprec: float = 1e-2,
) -> None:
"""Initialize the interface.

Parameters
----------
calculator (ase Calculator): an ase calculator
optimizer (str or ase Optimizer): the optimization algorithm.
fix_symmetry (bool): if True, symmetry will be fixed during relaxation.
symprec (float): Tolerance for symmetry finding in case of fix_symmetry.
"""
self.calculator = calculator

if isinstance(optimizer, str):
optimizer_obj = FORCE_BASED_OPTIMIZERS.get(optimizer)
elif optimizer is None:
raise ValueError("Optimizer cannot be None")
else:
optimizer_obj = optimizer

self.opt_class: Optimizer = optimizer_obj
self.ase_adaptor = AseAtomsAdaptor()
self.fix_symmetry = fix_symmetry
self.symprec = symprec

def run_neb(
self,
images: list[Atoms | Structure | Molecule],
fmax: float = 0.1,
steps: int = 500,
traj_file: str | Path | list[str | Path] = None,
traj_file_fmt: Literal["pmg", "ase", "xdatcar"] = "ase",
interval: int = 1,
verbose: bool = False,
neb_doc_kwargs: dict | None = None,
neb_kwargs: dict = DEFAULT_NEB_KWARGS,
optimizer_kwargs: dict | None = None,
) -> NebResult:
"""
Perform NEB on a list of molecules or structures.

Parameters
----------
images : list of ASE Atoms, pymatgen Structure, or pymatgen Molecule
The ordered list of atoms to perform NEB on.
fmax : float
Total force tolerance for relaxation convergence.
steps : int
Max number of steps for relaxation.
traj_file : str, Path, or a list of str / Path
The trajectory file for saving. If a single str or Path,
this specifies the file name prefix. For example,
`traj_file = "traj_mp-149.json.gz"`
will yield individual trajectory file names:
traj_mp-149-image-1.json.gz
traj_mp-149-image-2.json.gz
...
Alternately, if this is a list of str / Path, this specifies the
file name for each image.
interval : int
The step interval for saving the trajectories.
verbose : bool
If True, screen output will be shown.
neb_kwargs : dict, defaults to DEFAULT_NEB_KWARGS
kwargs to pass to ASE's NEB.
optimizer_kwargs : dict or None (default)
kwargs to pass to the optimizer.

Returns
-------
dict including optimized structure and the trajectory
"""
is_mol = isinstance(images[0], Molecule) or (
isinstance(images[0], Atoms) and all(not pbc for pbc in images[0].pbc)
)
num_images = len(images)
initial_images = [img.copy() for img in images]

for idx, image in enumerate(images):
if isinstance(image, Structure | Molecule):
images[idx] = self.ase_adaptor.get_atoms(image)

if self.fix_symmetry:
images[idx].set_constraint(FixSymmetry(image, symprec=self.symprec))
images[idx].calc = deepcopy(self.calculator)

neb_calc = NEB(images, **neb_kwargs)

with contextlib.redirect_stdout(sys.stdout if verbose else io.StringIO()):
observers = [TrajectoryObserver(image) for image in images]
optimizer = self.opt_class(neb_calc, **(optimizer_kwargs or {}))
for idx in range(num_images):
optimizer.attach(observers[idx], interval=interval)
t_i = time.perf_counter()
optimizer.run(fmax=fmax, steps=steps)
t_f = time.perf_counter()
[observers[idx]() for idx in range(num_images)]

if traj_file is not None:
if isinstance(traj_file, str | Path):
traj_file = Path(traj_file)
if traj_file_suffix := "".join(traj_file.suffixes):
traj_file_prefix = str(traj_file).split(traj_file_suffix)[0]
else:
traj_file_prefix = str(traj_file)
traj_files = [
f"{traj_file_prefix}-image-{idx + 1}{traj_file_suffix}"
for idx in range(num_images)
]
elif isinstance(traj_file, list | tuple):
traj_files = [str(f) for f in traj_file]

for idx, f in enumerate(traj_files):
observers[idx].save(f, fmt=traj_file_fmt)

images = [
self.ase_adaptor.get_structure(image, cls=Molecule if is_mol else Structure)
for image in images
]
num_sites = len(images[0])

tags = [os.getcwd()]
is_force_conv = all(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not try to do the math myself to see what the problem could be with this point, but in one of my tests I had an issue. The optimizer considered the forces converged and stopped the optimization after few loops, but this check was False and thus the task was marked as failed.
More in general, is there a need for this check specifically? optimize.run already returns a bool to specify if the optimization converged or not, why not using that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably has to do with the difference in NEB forces (interatomic + spring) vs plain interatomic forces. I'll take this out - I like the idea of using optimize.run to determine the task state, I'll update the other jobs as well

np.linalg.norm(observers[image_idx].forces[-1][site_idx]) < abs(fmax)
for site_idx in range(num_sites)
for image_idx in range(num_images)
)
tags += ["force converged" if is_force_conv else "forces not converged"]
tags += [f"elapsed time {t_f - t_i} seconds"]

return NebResult(
images=images,
initial_images=initial_images,
energies=[
observers[image_idx].energies[-1] for image_idx in range(num_images)
],
method=NebMethod.CLIMBING_IMAGE
if neb_kwargs.get("climb", False)
else NebMethod.STANDARD,
# dir_name=os.getcwd(), # NB: this should be migrated in emmet-core
state="successful" if is_force_conv else "failed",
tags=tags,
**neb_doc_kwargs,
)
Loading
Loading