diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 7caba74acb..9b85fc6167 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -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 @@ -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: diff --git a/pyproject.toml b/pyproject.toml index cb257618ba..54aa309564 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", @@ -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 = [ @@ -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", diff --git a/src/atomate2/ase/jobs.py b/src/atomate2/ase/jobs.py index 1986e5936b..70d4b369ba 100644 --- a/src/atomate2/ase/jobs.py +++ b/src/atomate2/ase/jobs.py @@ -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): """ diff --git a/src/atomate2/ase/md.py b/src/atomate2/ase/md.py index 535e247d1f..e6adbf056d 100644 --- a/src/atomate2/ase/md.py +++ b/src/atomate2/ase/md.py @@ -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 @@ -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: @@ -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()): diff --git a/src/atomate2/ase/neb.py b/src/atomate2/ase/neb.py new file mode 100644 index 0000000000..3cfd8304f6 --- /dev/null +++ b/src/atomate2/ase/neb.py @@ -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) diff --git a/src/atomate2/ase/schemas.py b/src/atomate2/ase/schemas.py index 35d5319f4e..db4a7ac792 100644 --- a/src/atomate2/ase/schemas.py +++ b/src/atomate2/ase/schemas.py @@ -17,6 +17,7 @@ from ase.units import GPa from emmet.core.math import Matrix3D, Vector3D from emmet.core.structure import MoleculeMetadata, StructureMetadata +from emmet.core.tasks import TaskState from emmet.core.utils import ValueEnum from emmet.core.vasp.calculation import StoreTrajectoryOption from pydantic import BaseModel, Field @@ -30,6 +31,7 @@ "dir_name", "included_objects", "objects", + "state", "is_force_converged", "energy_downhill", "tags", @@ -51,6 +53,10 @@ class AseResult(BaseModel): None, description="The relaxation or molecular dynamics trajectory." ) + converged: bool | None = Field( + None, description="Whether the ASE optimizer converged." + ) + is_force_converged: bool | None = Field( None, description=( @@ -215,6 +221,10 @@ class AseStructureTaskDoc(StructureMetadata): None, description="ASE objects associated with this task" ) + state: TaskState | None = Field( + None, description="Whether the calculation completed successfully." + ) + is_force_converged: bool | None = Field( None, description=( @@ -281,6 +291,10 @@ class AseMoleculeTaskDoc(MoleculeMetadata): None, description="ASE objects associated with this task" ) + state: TaskState | None = Field( + None, description="Whether the calculation completed successfully." + ) + is_force_converged: bool | None = Field( None, description=( @@ -324,6 +338,10 @@ class AseTaskDoc(AseBaseModel): None, description="ASE objects associated with this task" ) + state: TaskState | None = Field( + None, description="Whether the calculation completed successfully." + ) + is_force_converged: bool | None = Field( None, description=( @@ -516,6 +534,10 @@ def from_ase_compatible_result( n_steps=n_steps, ) + state = None + if result.converged is not None: + state = TaskState.SUCCESS if result.converged else TaskState.FAILED + return cls( mol_or_struct=output_mol_or_struct, input=input_doc, @@ -523,6 +545,7 @@ def from_ase_compatible_result( ase_calculator_name=ase_calculator_name, included_objects=list(objects.keys()), objects=objects, + state=state, is_force_converged=result.is_force_converged, energy_downhill=result.energy_downhill, dir_name=result.dir_name, diff --git a/src/atomate2/ase/utils.py b/src/atomate2/ase/utils.py index cb60f80c2d..db46113797 100644 --- a/src/atomate2/ase/utils.py +++ b/src/atomate2/ase/utils.py @@ -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 @@ -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 @@ -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. @@ -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" @@ -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, @@ -383,12 +399,14 @@ def relax( atoms = cell_filter(atoms, **(filter_kwargs or {})) optimizer = self.opt_class(atoms, **kwargs) optimizer.attach(obs, interval=interval) - optimizer.run(fmax=fmax, steps=steps) + converged = optimizer.run(fmax=fmax, steps=steps) + else: + converged = True obs() 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 @@ -414,9 +432,174 @@ def relax( return AseResult( final_mol_or_struct=struct, trajectory=traj, + converged=converged, is_force_converged=is_force_conv, - energy_downhill=traj.frame_properties[-1]["energy"] - < traj.frame_properties[0]["energy"], + energy_downhill=( + traj.frame_properties[-1]["energy"] < traj.frame_properties[0]["energy"] + ), 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 = [] + for img in images: + if isinstance(img, Atoms): + image = self.ase_adaptor.get_structure( + img, cls=Molecule if is_mol else Structure + ) + else: + image = img.copy() + initial_images.append(image) + + 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() + converged = 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( + 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, + image_indices=list(range(len(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(), + state="successful" if converged else "failed", + tags=tags, + **neb_doc_kwargs, + ) diff --git a/src/atomate2/common/flows/approx_neb.py b/src/atomate2/common/flows/approx_neb.py new file mode 100644 index 0000000000..b9d325da1a --- /dev/null +++ b/src/atomate2/common/flows/approx_neb.py @@ -0,0 +1,347 @@ +"""Define ApproxNEB flows for all calculators.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from emmet.core.mobility.migrationgraph import MigrationGraphDoc +from jobflow import Flow, Maker, OnMissing + +from atomate2.common.jobs.approx_neb import ( + collate_images_single_hop, + collate_results, + get_endpoints_and_relax, + get_images_and_relax, +) + +if TYPE_CHECKING: + from pathlib import Path + from typing import Any, Literal + + from jobflow import Job + from pymatgen.core import Structure + from pymatgen.io.common import VolumetricData + from pymatgen.util.typing import CompositionLike + + +@dataclass +class CommonApproxNebMaker(Maker): + """Run an ApproxNEB workflow. + + Parameters + ---------- + name : str = "ApproxNEB" + Name of the workflow + host_relax_maker : Maker + Optional, a maker to relax the input host structure. + image_relax_maker : Maker + Required, a maker to relax the ApproxNEB endpoints and images. + endpoint_relax_maker : Maker or None (default) + Optional maker to relax the endpoints that could differ from the + relax maker used on the intermediate images. + If None, this is set to `image_relax_maker`. + selective_dynamics_scheme : "fix_two_atoms" (default) or None + If "fix_two_atoms", uses the default selective dynamics scheme of ApproxNEB, + wherein the migrating ion and the ion farthest from it are the only + ions whose positions can relax. + min_hop_distance : float or bool (default = True) + If a float, skips any hops where the working ion moves a distance less + than min_hop_distance. + If True, min_hop_distance is set to twice the average ionic radius. + If False, no checks are made. + """ + + name: str = "ApproxNEB" + host_relax_maker: Maker | None = None + image_relax_maker: Maker = None + endpoint_relax_maker: Maker | None = None + selective_dynamics_scheme: Literal["fix_two_atoms"] | None = "fix_two_atoms" + min_hop_distance: float | bool = True + + def make( + self, + host_structure: Structure, + working_ion: str, + inserted_coords_dict: dict | list, + inserted_coords_combo: list, + n_images: int = 5, + min_images_per_hop: int | None = 3, + prev_dir: str | Path | None = None, + ) -> Flow: + """ + Make an ApproxNEB flow. + + Parameters + ---------- + host_structure: Structure + the (supercell) structure of the empty host with no working ion + working_ion: str + the mobile species in ApproxNEB + inserted_coords_dict: dict or list + a dictionary containing fractional site coords (endpoints) + for working ions in the simulation cell. + inserted_coords_combo: list + a list of combo strings "a+b" to designate run calculations between + endpoints a and b + inserted_coords_dict should contain all indices specified + in inserted_coords_combo + n_images: int = 5 + number of intermediate images for the ApproxNEB calculation + min_images_per_hop : int or None, default = 3 + If an int, the minimum number of image calculations per hop that + must succeed to mark a hop as successfully calculated. + selective_dynamics: str + the scheme for adding selective dynamics to image relaxations + + Returns + ------- + Flow + A flow performing AppoxNEB calculations + """ + # compatibility with legacy input (list) + if isinstance(inserted_coords_dict, list): + inserted_coords_dict = dict(enumerate(inserted_coords_dict)) + + # Check to see that all hop indices are included in the dict of endpoints + unique_ep_idxs = set() + for combo in inserted_coords_combo: + unique_ep_idxs.update([int(idx) for idx in combo.split("+")]) + if len( + missing_idxs := unique_ep_idxs.difference( + {int(idx) for idx in inserted_coords_dict} + ) + ): + raise ValueError( + "Missing working ion insertion indices in `inserted_coords_dict`: " + f"{', '.join([str(idx) for idx in missing_idxs])}" + ) + + jobs: list[Job] = [] + + # assign job to relax host structure + if self.host_relax_maker: + host_relax_job = self.host_relax_maker.make( + host_structure, prev_dir=prev_dir + ) + host_relax_job.append_name("host structure ", prepend=True) + jobs += [host_relax_job] + host_structure = host_relax_job.output.structure + prev_dir = host_relax_job.output.dir_name + + # assign jobs to relax endpoint structures + ep_relax_jobs = get_endpoints_and_relax( + host_structure=host_structure, + working_ion=working_ion, + endpoint_coords=inserted_coords_dict, + inserted_coords_combo=inserted_coords_combo, + relax_maker=self.endpoint_relax_maker or self.image_relax_maker, + ) + + # run pathfinder (and selective dynamics) to get image structure input + image_relax_jobs = get_images_and_relax( + working_ion=working_ion, + ep_output=ep_relax_jobs.output, + inserted_combo_list=inserted_coords_combo, + n_images=n_images, + charge_density_path=prev_dir, + get_charge_density=self.get_charge_density, + relax_maker=self.image_relax_maker, + selective_dynamics_scheme=self.selective_dynamics_scheme, + min_hop_distance=self.min_hop_distance, + ) + + collect_output = collate_results( + host_structure, + working_ion, + ep_relax_jobs.output, + image_relax_jobs.output, + min_images_per_hop=min_images_per_hop, + ) + + # to permit the flow to succeed even when prior jobs fail + collect_output.config.on_missing_references = OnMissing.NONE + + return Flow( + [*jobs, ep_relax_jobs, image_relax_jobs, collect_output], + output=collect_output.output, + ) + + def make_from_migration_graph_doc( + self, + migration_graph_doc: MigrationGraphDoc, + n_images: int = 5, + prev_dir: str | Path | None = None, + atomate_compat_labels: bool = False, + ) -> Flow: + """ + Make an ApproxNEB flow from an emmet MigrationGraphDoc. + + Parameters + ---------- + migration_graph_doc: MigrationGraph + Migration graph containing information about the host structure, + inserted coordinates, etc. + n_images: int + number of images for the ApproxNEB calculation + prev_dir: str or .Path or None (default) + A previous calculation directory to copy outputs from. + atomate_compat_labels : bool = False + Whether to use atomate style labeling of the endpoints (True) + or the original labels from the MigrationGraphDoc (False, default) + + Returns + ------- + Flow + A flow performing AppoxNEB calculations + """ + inserted_coords, inserted_coords_combo, mapping = ( + MigrationGraphDoc.get_distinct_hop_sites( + migration_graph_doc.inserted_ion_coords, + migration_graph_doc.insert_coords_combo, + ) + ) + if not atomate_compat_labels: + inserted_coords_combo = [mapping[k] for k in inserted_coords_combo] + site_idx_map = {} + for k, v in mapping.items(): + start_idx_new, end_idx_new = k.split("+") + start_idx_old, end_idx_old = v.split("+") + site_idx_map.update( + {start_idx_new: start_idx_old, end_idx_new: end_idx_old} + ) + inserted_coords = { + site_idx_map[str(idx)]: coords + for idx, coords in enumerate(inserted_coords) + } + + composition = migration_graph_doc.working_ion_entry.composition.remove_charges() + working_ion = next(ele.value for ele in composition) + + return self.make( + host_structure=migration_graph_doc.matrix_supercell_structure, + working_ion=working_ion, + inserted_coords_dict=inserted_coords, + inserted_coords_combo=inserted_coords_combo, + n_images=n_images, + prev_dir=prev_dir, + ) + + def get_charge_density(self, *args, **kwargs) -> VolumetricData: + """Get charge density, to be implemented in subclasses. + + Returns + ------- + pymatgen VolumetricData + """ + raise NotImplementedError + + +@dataclass +class ApproxNebFromEndpointsMaker(Maker): + """ + Create an ApproxNEB flow from specified endpoints. + + image_relax_maker : Maker + Maker to relax both endpoints and images + selective_dynamics_scheme : "fix_two_atoms" (default) or None + If "fix_two_atoms", uses the default selective dynamics scheme of ApproxNEB, + wherein the migrating ion and the ion farthest from it are the only + ions whose positions can relax. + min_images_per_hop : int or None + If an int, the minimum number of image calculations per hop that + must succeed to mark a hop as successfully calculated. + min_hop_distance : float or bool (default = True) + If a float, skips any hops where the working ion moves a distance less + than min_hop_distance. + If True, min_hop_distance is set to twice the average ionic radius. + If False, no checks are made. + """ + + image_relax_maker: Maker + name: str = "ApproxNEB single hop from endpoints maker" + selective_dynamics_scheme: Literal["fix_two_atoms"] | None = "fix_two_atoms" + min_images_per_hop: int | None = 3 + min_hop_distance: float | bool = True + + def make( + self, + working_ion: CompositionLike, + end_point_structures: list[Structure] | tuple[Structure, Structure], + charge_density_path: str | Path, + n_images: int = 5, + prev_dir: str | Path | None = None, + ) -> Flow: + """ + Run an ApproxNEB flow for a single hop. + + working_ion : CompositionLike + The element which migrates. + end_point_structures : list of pymatgen .Structure + The two endpoint structures + charge_density_path: str or .Path + Path to the directory containing the charge density file(s). + n_images: int = 5 + number of images for the ApproxNEB calculation + prev_dir : str, .Path, or None + If not None, the path to a previous calculation to + copy outputs from. + + Returns + ------- + Flow + A flow performing an AppoxNEB calculation for a single hop. + """ + if len(end_point_structures) != 2: + raise ValueError( + "Specify only two endpoint structures, " + f"{len(end_point_structures)} structures were supplied." + ) + + ep_jobs: list[Job] = [] + ep_output: dict[str, dict[str, Any]] = {} + for idx, ep in enumerate(end_point_structures): + job = self.image_relax_maker.make(ep, prev_dir=prev_dir) + job.name = f"ApproxNEB relax endpoint {idx}" + ep_output[str(idx)] = { + "initial_structure": ep, + "structure": job.output.structure, + "energy": job.output.output.energy, + } + ep_jobs.append(job) + + image_calcs = get_images_and_relax( + working_ion=working_ion, + ep_output=ep_output, + inserted_combo_list=["0+1"], + n_images=n_images, + charge_density_path=charge_density_path, + get_charge_density=self.get_charge_density, + relax_maker=self.image_relax_maker, + selective_dynamics_scheme=self.selective_dynamics_scheme, + min_hop_distance=self.min_hop_distance, + ) + + collate_job = collate_images_single_hop( + working_ion=working_ion, + endpoint_calc_output=[ep_output[str(idx)] for idx in range(2)], + image_calc_output=image_calcs.output["0+1"], + min_images_per_hop=self.min_images_per_hop, + ) + collate_job.config.on_missing_references = OnMissing.NONE + return Flow([*ep_jobs, image_calcs, collate_job], output=collate_job.output) + + def get_charge_density(self, prev_dir: str | Path) -> VolumetricData: + """Obtain charge density from a specified path. + + Parameters + ---------- + prev_dir : str or Path + Path to the calculation containing the previous charge density. + + Returns + ------- + VolumetricData + The charge density + """ + raise NotImplementedError diff --git a/src/atomate2/common/jobs/approx_neb.py b/src/atomate2/common/jobs/approx_neb.py new file mode 100644 index 0000000000..92bf34ad09 --- /dev/null +++ b/src/atomate2/common/jobs/approx_neb.py @@ -0,0 +1,579 @@ +"""Define common utility jobs needed for ApproxNEB flows.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import numpy as np +from emmet.core.neb import HopFailureReason, NebMethod, NebPathwayResult, NebResult +from emmet.core.vasp.task_valid import TaskState +from jobflow import Flow, Response, job +from pymatgen.analysis.diffusion.neb.pathfinder import ChgcarPotential, NEBPathfinder +from pymatgen.core import Element + +if TYPE_CHECKING: + from collections.abc import Callable, Sequence + from pathlib import Path + from typing import Any, Literal + + from jobflow import Maker + from pymatgen.core import Structure + from pymatgen.io.common import VolumetricData + from pymatgen.util.typing import CompositionLike + + +@job +def get_endpoints_and_relax( + host_structure: Structure, + working_ion: CompositionLike, + endpoint_coords: dict, + inserted_coords_combo: list[str], + relax_maker: Maker, +) -> Response: + """ + Get and relax endpoint structures. + + Parameters + ---------- + host_structure : pymatgen Structure + The structure in which to insert a working ion + working_ion : pymatgen CompositionLike (string, Element, or Species) + The name of the element to insert + endpoint_coords : dict + Dict of endpoint index to the coordinates where the + working ion will be inserted + inserted_coords_combo : list + List of hops indicated by < endpoint index 1 > + < endpoint index 2 > + relax_maker : Maker + Maker to relax the endpoints + + Returns + ------- + Response: + The flow relaxes all required endpoints, but the output of this flow + is a dict containing the energies and relaxed structures of the + endpoints, with the endpoint indices as keys. + """ + ep_distinct = set() + for one_combo in inserted_coords_combo: + try: + combo = one_combo.split("+") + ini, fin = [int(combo[0]), int(combo[1])] + except ValueError as error: + raise ValueError( + f"{one_combo} inserted_coords_combo input is incorrect" + ) from error + ep_distinct.update([ini, fin]) + + if ( + len( + missing_idxs := ep_distinct.difference( + {int(idx) for idx in endpoint_coords} + ) + ) + > 0 + ): + raise ValueError( + "Missing working ion insertion indices: " + f"{', '.join([str(idx) for idx in missing_idxs])}" + ) + + # In principle, it makes sense to use the magmoms from the host + # structure to initialize the host + inserted working ion calcs + # In practice, this throws unfixable "Bravais" errors in VASP + # (the actual reciprocal lattice does not have the expected + # symmetry of the ideal reciprocal lattice.) + if host_structure.site_properties.get("magmom") is not None: + host_structure.remove_site_property("magmom") + + ep_relax_output = {} + ep_relax_jobs = [] + for ep_index, ep_coords in endpoint_coords.items(): + if int(ep_index) in ep_distinct: + ep_inserted_struct = host_structure.copy() + ep_inserted_struct.insert(0, working_ion, ep_coords) + + relax_job = relax_maker.make(ep_inserted_struct) + relax_job.append_name(f" endpoint {ep_index}") + ep_relax_jobs.append(relax_job) + ep_relax_output[ep_index] = { + "energy": relax_job.output.output.energy, + "structure": relax_job.output.structure, + "initial_structure": relax_job.output.input.structure, + } + + flow = Flow(ep_relax_jobs, output=ep_relax_output) + + return Response(replace=flow) + + +@job +def collate_results( + host_structure: Structure, + working_ion: CompositionLike, + endpoint_calc_output: dict, + image_calc_output: dict[str, list], + min_images_per_hop: int | None = None, +) -> NebPathwayResult: + """Collect output from an ApproxNEB workflow. + + Parameters + ---------- + host_structure : pymatgen Structure + working_ion : CompositionLike + Used in combination with host_structure to estimate + the linear distance traversed in a given ApproxNEB hop + endpoint_calc_output : dict + Output of get_endpoints_and_relax + image_calc_output : dict[str,list] + Output of get_images_and_relax + min_images_per_hop : int or None (default) + If an integer, the minimum number of successful image calculations + to mark a calculation as successful + + Returns + ------- + NebPathwayResult - a collection of NEB calculations and analysis + """ + hop_dict = {} + hop_dist = {} + + endpoint_idxs = [] + for combo_name in image_calc_output: + endpoint_idxs.extend(combo_name.split("+")) + endpoint_idxs = sorted(set(endpoint_idxs)) + + for combo_name, entry in image_calc_output.items(): + failure_reasons = [] + task_state = TaskState.SUCCESS + images = entry + if all(isinstance(v, str) for v in entry): + # hop calculation failed + failure_reasons.extend([HopFailureReason(v) for v in entry]) + task_state = TaskState.FAILED + if HopFailureReason.ENDPOINT in failure_reasons: + # Cannot populate any NEB fields, skip entirely + hop_dict[combo_name] = NebResult( + state=task_state, failure_reasons=failure_reasons + ) + continue + images = [] + + endpoint_calcs = [endpoint_calc_output[idx] for idx in combo_name.split("+")] + hop = [endpoint_calcs[0], *images, endpoint_calcs[1]] + + if min_images_per_hop is not None and task_state == TaskState.SUCCESS: + num_success_calcs = len( + [calc for calc in hop if calc["structure"] is not None] + ) + if num_success_calcs < min_images_per_hop: + task_state = TaskState.FAILED + failure_reasons.append(HopFailureReason.MIN_IMAGE) + + hop_dict[combo_name] = NebResult( + images=[calc["structure"] for calc in hop if calc["structure"] is not None], + image_indices=[ + idx for idx, calc in enumerate(hop) if calc["structure"] is not None + ], + initial_images=[ + calc["initial_structure"] + for calc in hop + if calc["initial_structure"] is not None + ], + energies=[calc["energy"] for calc in hop if calc["energy"] is not None], + method=NebMethod.APPROX, + state=task_state, + failure_reasons=failure_reasons if len(failure_reasons) > 0 else None, + endpoint_indices=combo_name.split("+"), + ) + + hop_dist[combo_name] = get_hop_distance_from_endpoints( + [ep_calc["structure"] for ep_calc in endpoint_calcs], working_ion + ) + + return NebPathwayResult( + hops=hop_dict, + host_structure=host_structure, + hop_distances=hop_dist, + initial_endpoints={ + idx: entry["initial_structure"] + for idx, entry in endpoint_calc_output.items() + }, + relaxed_endpoints={ + idx: entry["structure"] for idx, entry in endpoint_calc_output.items() + }, + ) + + +@job +def get_images_and_relax( + working_ion: str, + ep_output: dict[str, dict], + inserted_combo_list: list[str], + n_images: int | list[int], + charge_density_path: str | Path, + get_charge_density: Callable[[str | Path], VolumetricData], + relax_maker: Maker, + selective_dynamics_scheme: Literal["fix_two_atoms"] | None = "fix_two_atoms", + min_hop_distance: float | bool = True, +) -> Response: + """ + Get and relax image input structures. + + Parameters + ---------- + ep_output : dict + Output of get_endpoints_and_relax + inserted_combo_list : list + List of hops to perform ApproxNEB on + n_images : int + The number of images to use along each hop. + If an int, the number to use for every hop. + If a list of ints, the number of images to use in that hop, + charge_density_path: str | Path + The path to the calculation of the host_structure + relax_maker : Maker + Maker to relax images + selective_dynamics_scheme : "fix_two_atoms" or None + Whether to use a pre-defined selective dynamics scheme or relax + all ionic positions in the images. + use_aeccar : bool = False + If True, the sum of the host structure AECCAR0 (pseudo-core charge density) + and AECCAR2 (valence charge density) are used in image pathfinding. + If False (default), the CHGCAR (valence charge density) is used. + min_hop_distance : float or bool (default = True) + If a float, skips any hops where the working ion moves a distance less + than min_hop_distance. + If True, min_hop_distance is set to twice the average ionic radius. + If False, no checks are made. + + Returns + ------- + Response : a series of image relaxations with output containing the + relaxed structures and energies of each image in each hop in a dict: + { + hop_index : [ + {"energy": energy of image 1, "structure": relaxed image structure 1}, + ... + ] + } + """ + # remove failed output and strip magmoms to avoid "Bravais" errors + ep_structures = {} + for k, calc in ep_output.items(): + if calc["structure"] is None: + continue + ep_struct = calc["structure"].copy() + if ep_struct.site_properties.get("magmom") is not None: + ep_struct.remove_site_property("magmom") + ep_structures[k] = ep_struct + + host_chgcar = get_charge_density(charge_density_path) + + image_relax_jobs = [] + image_relax_output: dict[str, list] = {} + + if isinstance(n_images, int): + n_images = [n_images for _ in inserted_combo_list] + + if isinstance(min_hop_distance, bool) and min_hop_distance: + _wion = Element(str(working_ion)) + if (ionic_radius := getattr(_wion, "average_ionic_radius", None)) is not None: + min_hop_distance = 2 * ionic_radius + elif (atomic_radius := getattr(_wion, "atomic_radius", None)) is not None: + # all elements have an atomic radius in pymatgen + min_hop_distance = atomic_radius + + for hop_idx, combo in enumerate(inserted_combo_list): + ini_ind, fin_ind = combo.split("+") + + # See if we can proceed with this hop calculation: + skip_reasons = [] + if not all(ep_structures.get(idx) for idx in [ini_ind, fin_ind]): + # At least one endpoint calculation failed + skip_reasons.append(HopFailureReason.ENDPOINT) + if ( + isinstance(min_hop_distance, float) + and get_hop_distance_from_endpoints( + [ep_structures[ini_ind], ep_structures[fin_ind]], working_ion + ) + < min_hop_distance + ): + # The working ion hop distance is below the specified threshold + skip_reasons.append(HopFailureReason.MIN_DIST) + + if len(skip_reasons) > 0: + image_relax_output[combo] = [reason.value for reason in skip_reasons] + continue + + # potential place for uuid logic if depth first is desirable + pathfinder_output = get_pathfinder_results( + ep_structures[ini_ind], + ep_structures[fin_ind], + working_ion, + n_images[hop_idx], + host_chgcar, + ) + images_list = pathfinder_output["images"] + + # add selective dynamics to structure + if selective_dynamics_scheme == "fix_two_atoms": + images_list = [ + add_selective_dynamics_two_fixed_sites( + image, + pathfinder_output["mobile_site_index"], + working_ion, + ) + for image in pathfinder_output["images"] + ] + + elif selective_dynamics_scheme is not None: + raise ValueError(f"Unknown {selective_dynamics_scheme=}.") + + image_relax_output[combo] = [] + for image_idx, image in enumerate(images_list): + relax_job = relax_maker.make(image) + relax_job.append_name(f" hop {combo} image {image_idx + 1}") + image_relax_jobs.append(relax_job) + image_relax_output[combo].append( + { + "energy": relax_job.output.output.energy, + "structure": relax_job.output.structure, + "initial_structure": relax_job.output.input.structure, + } + ) + + relax_flow = Flow(image_relax_jobs, output=image_relax_output) + + return Response(replace=relax_flow) + + +def get_pathfinder_results( + pf_struct_ini: Structure, + pf_struct_fin: Structure, + working_ion: CompositionLike, + n_images: int, + host_charge_density: VolumetricData, +) -> dict: + """ + Get interpolated images from the pathfinder algorithm. + + Parameters + ---------- + pf_struct_ini : pymatgen Structure + First NEB endpoint structure + pf_struct_fin : pymatgen Structure + Second/final endpoint structure + working_ion : CompositionLike + The element which migrates + n_images : int + The number of images to be created along the path + host_charge_density : VolumetricData, like pymatgen.io.vasp.outputs Chgcar + The charge density of the host structure + + Returns + ------- + dict containing the images along the path and the mobile_site index + """ + ini_wi_ind = get_working_ion_index(pf_struct_ini, working_ion) + fin_wi_ind = get_working_ion_index(pf_struct_fin, working_ion) + + if ini_wi_ind != fin_wi_ind: + raise ValueError( + "Inserted site indexes of end point structures must match for NEBPathfinder" + ) + + # get potential gradient v from host chgcar + v_chgcar = ChgcarPotential(host_charge_density) + host_v = v_chgcar.get_v() + + # perform pathfinding and get images + neb_pf = NEBPathfinder( + pf_struct_ini, + pf_struct_fin, + relax_sites=[ini_wi_ind], + v=host_v, + n_images=n_images + 1, + ) + # note NEBPathfinder currently returns n_images+1 images (rather than n_images) + # and the first and last images generated are very similar to the end points + # provided so they are discarded + + return { + "images": neb_pf.images[1:-1], + "mobile_site_index": ini_wi_ind, + } + + +def add_selective_dynamics_two_fixed_sites( + structure: Structure, + fixed_index: int, + fixed_species_name: CompositionLike, +) -> Structure: + """Add selective dynamics to input structure. + + Parameters + ---------- + structure : pymatgen Structure + Structure to add selective dynamics tags to + fixed_index : int + Index of the site to add selective dynamics to + fixed_species_name : CompositionLike + The expected element at the indexed site - used + to check validity of fixed_index + + Returns + ------- + Copy of the input structure with selective dynamics tags + """ + if structure[fixed_index].specie.name != fixed_species_name: + raise ValueError( + f"The chosen fixed atom at index {fixed_index} is not a " + f"{fixed_species_name} atom" + ) + + # removes site properties to avoid error + for p in structure.site_properties: + structure.remove_site_property(p) + + # add selectives dynamics with fix_two_atoms scheme + # fix the atom at fixed_index and the furthest atom in the structure + ref_site = structure.sites[fixed_index] + distances = [site.distance(ref_site) for site in structure.sites] + farthest_index = distances.index(max(distances)) + sd_array = [ + [False, False, False] + if idx in {fixed_index, farthest_index} + else [True, True, True] + for idx in range(structure.num_sites) + ] + structure.add_site_property("selective_dynamics", sd_array) + + return structure + + +def get_working_ion_index( + structure: Structure, working_ion: CompositionLike +) -> int | None: + """Get the index of the working ion in a structure. + + Parameters + ---------- + structure : pymatgen Structure + working_ion : CompositionLike + Name of the element to identify + + Returns + ------- + int - the first site in the structure containing the working ion + None - no sites in the structure contain the working ion + """ + for ind, site in enumerate(structure): + if site.species_string == working_ion: + # assume that only the lowest indexed working ion is mobile + return ind + return None + + +def get_hop_distance_from_endpoints( + endpoint_structures: Sequence[Structure], working_ion: CompositionLike +) -> float: + """ + Find the hop distance of a working ion from two endpoint structures. + + Parameters + ---------- + endpoint_structures : Sequence of pymatgen .Structure + The two endpoint structures defining a hop. + working_ion : pymatgen .CompositionLike + The species name of the working ion. + + Returns + ------- + float - the hop distance + """ + working_ion_sites = [ + [ + site + for site in endpoint_structures[ep_idx] + if site.species_string == working_ion + ] + for ep_idx in range(2) + ] + + return max( + np.linalg.norm(site_a.coords - site_b.coords) + for site_a in working_ion_sites[0] + for site_b in working_ion_sites[1] + ) + + +@job +def collate_images_single_hop( + working_ion: CompositionLike, + endpoint_calc_output: list[dict[str, Any]], + image_calc_output: list[dict[str, Any]], + min_images_per_hop: int | None = None, +) -> NebResult: + """ + Collect output from an ApproxNEB flow. + + Parameters + ---------- + working_ion: CompositionLike + The mobile ion. + endpoint_calc_output: list of dict + Output from the endpoint relaxations. + image_calc_output: list of dict + Output from the image relaxations. + min_images_per_hop: int | None = None, + If an int, the minimum number of image calculations per hop that + must succeed to mark a hop as successfully calculated. + + Returns + ------- + NebResult + """ + failure_reasons: list[HopFailureReason] = [] + task_state = TaskState.SUCCESS + + calcs: list[dict[str, Any]] = [] + if image_calc_output is None: + task_state = TaskState.FAILED + failure_reasons.append(HopFailureReason.IMAGE_FAILURE) + else: + calcs += image_calc_output + + if endpoint_calc_output is not None: + calcs = [endpoint_calc_output[0], *calcs, endpoint_calc_output[1]] + + if min_images_per_hop is not None: + num_success_calcs = len( + [calc for calc in calcs if calc["structure"] is not None] + ) + if num_success_calcs < min_images_per_hop: + task_state = TaskState.FAILED + failure_reasons.append(HopFailureReason.MIN_IMAGE) + + hop_dist = None + if endpoint_calc_output is not None and working_ion is not None: + hop_dist = get_hop_distance_from_endpoints( + [ep_calc["structure"] for ep_calc in endpoint_calc_output], working_ion + ) + + return NebResult( + images=[calc["structure"] for calc in calcs if calc["structure"] is not None], + image_indices=[ + idx for idx, calc in enumerate(calcs) if calc["structure"] is not None + ], + initial_images=[ + calc["initial_structure"] + for calc in calcs + if calc["initial_structure"] is not None + ], + energies=[calc["energy"] for calc in calcs if calc["energy"] is not None], + method=NebMethod.APPROX, + state=task_state, + failure_reasons=failure_reasons if len(failure_reasons) > 0 else None, + hop_distance=hop_dist, + ) diff --git a/src/atomate2/common/jobs/neb.py b/src/atomate2/common/jobs/neb.py new file mode 100644 index 0000000000..f335181160 --- /dev/null +++ b/src/atomate2/common/jobs/neb.py @@ -0,0 +1,110 @@ +"""Define tools for analyzing NEB runs.""" + +from __future__ import annotations + +import logging +from enum import Enum +from typing import TYPE_CHECKING + +from jobflow import job + +if TYPE_CHECKING: + from pymatgen.core import Structure + +logger = logging.getLogger(__name__) + + +class NebInterpolation(Enum): + """Methods for interpolating NEB images.""" + + LINEAR = "linear" + IDPP = "IDPP" + + +@job +def get_images_from_endpoints( + endpoints: tuple[Structure, Structure] | list[Structure], + num_images: int, + interpolation_method: NebInterpolation = NebInterpolation.LINEAR, + **interpolation_kwargs, +) -> list[Structure]: + """ + Interpolate between two endpoints 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 _get_images_from_endpoints( + endpoints, + num_images, + interpolation_method=NebInterpolation(interpolation_method), + **interpolation_kwargs, + ) + + +def _get_images_from_endpoints( + endpoints: tuple[Structure, Structure] | list[Structure], + num_images: int, + interpolation_method: NebInterpolation | str = NebInterpolation.LINEAR, + **interpolation_kwargs, +) -> list[Structure]: + """ + Interpolate between two endpoints. + + 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. + """ + interpolation_method = NebInterpolation(interpolation_method) + if interpolation_method == NebInterpolation.LINEAR: + return endpoints[0].interpolate( + endpoints[1], nimages=num_images + 1, **interpolation_kwargs + ) + if interpolation_method == NebInterpolation.IDPP: + try: + from pymatgen.analysis.diffusion.neb.pathfinder import IDPPSolver + except ImportError as exc: + raise ImportError( + "You must pip install `pymatgen-analysis-diffusion` " + "to generate images with IDPP." + ) from exc + + constructor_keys = {"sort_tol", "interpolate_lattices"} + constructor_kwargs = { + k: v for k, v in interpolation_kwargs.items() if k in constructor_keys + } + + return IDPPSolver.from_endpoints( + endpoints, nimages=num_images, **constructor_kwargs + ).run( + **{ + k: v + for k, v in interpolation_kwargs.items() + if k not in constructor_keys + } + ) + + raise ValueError(f"Unknown {interpolation_method=}") diff --git a/src/atomate2/forcefields/__init__.py b/src/atomate2/forcefields/__init__.py index c15783fa24..4c1882ce48 100644 --- a/src/atomate2/forcefields/__init__.py +++ b/src/atomate2/forcefields/__init__.py @@ -1,71 +1,4 @@ """Tools and functions common to all forcefields.""" -from __future__ import annotations - -import warnings -from enum import Enum -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import Any - - -class MLFF(Enum): # TODO inherit from StrEnum when 3.11+ - """Names of ML force fields.""" - - MACE = "MACE" # This is MACE-MP-0 (medium), deprecated - MACE_MP_0 = "MACE-MP-0" - MACE_MPA_0 = "MACE-MPA-0" - MACE_MP_0B3 = "MACE-MP-0b3" - GAP = "GAP" - M3GNet = "M3GNet" - CHGNet = "CHGNet" - Forcefield = "Forcefield" # default placeholder option - NEP = "NEP" - Nequip = "Nequip" - SevenNet = "SevenNet" - MATPES_R2SCAN = "MatPES-r2SCAN" - MATPES_PBE = "MatPES-PBE" - - @classmethod - def _missing_(cls, value: Any) -> Any: - """Allow input of str(MLFF) as valid enum.""" - if isinstance(value, str): - value = value.split("MLFF.")[-1] - for member in cls: - if member.name == value: - return member - return None - - -def _get_formatted_ff_name(force_field_name: str | MLFF) -> str: - """ - Get the standardized force field name. - - Parameters - ---------- - force_field_name : str or .MLFF - The name of the force field - - Returns - ------- - str : the name of the forcefield from MLFF - """ - if isinstance(force_field_name, str): - # ensure `force_field_name` uses enum format - if force_field_name in MLFF.__members__: - force_field_name = MLFF[force_field_name] - elif force_field_name in [v.value for v in MLFF]: - force_field_name = MLFF(force_field_name) - force_field_name = str(force_field_name) - if force_field_name in {"MLFF.MACE", "MACE"}: - warnings.warn( - "Because the default MP-trained MACE model is constantly evolving, " - "we no longer recommend using `MACE` or `MLFF.MACE` to specify " - "a MACE model. For reproducibility purposes, specifying `MACE` " - "will still default to MACE-MP-0 (medium), which is identical to " - "specifying `MLFF.MACE_MP_0`.", - category=UserWarning, - stacklevel=2, - ) - return force_field_name +# ensure that this is still importable for legacy jobs +from atomate2.forcefields.utils import MLFF, _get_formatted_ff_name diff --git a/src/atomate2/forcefields/flows/approx_neb.py b/src/atomate2/forcefields/flows/approx_neb.py new file mode 100644 index 0000000000..464125740f --- /dev/null +++ b/src/atomate2/forcefields/flows/approx_neb.py @@ -0,0 +1,98 @@ +"""Run an ApproxNEB flow using MLIPs.""" + +from collections.abc import Sequence +from dataclasses import dataclass +from pathlib import Path + +from pymatgen.io.common import VolumetricData +from pymatgen.io.vasp.outputs import Chgcar +from typing_extensions import Self + +from atomate2.common.flows.approx_neb import ApproxNebFromEndpointsMaker +from atomate2.forcefields import MLFF, _get_formatted_ff_name +from atomate2.forcefields.jobs import ForceFieldRelaxMaker + + +@dataclass +class ForceFieldApproxNebFromEndpointsMaker(ApproxNebFromEndpointsMaker): + """ + Perform ApproxNEB on a single hop using ML forcefields. + + image_relax_maker : Maker + Maker to relax both endpoints and images + selective_dynamics_scheme : "fix_two_atoms" (default) or None + If "fix_two_atoms", uses the default selective dynamics scheme of ApproxNEB, + wherein the migrating ion and the ion farthest from it are the only + ions whose positions can relax. + min_images_per_hop : int or None + If an int, the minimum number of image calculations per hop that + must succeed to mark a hop as successfully calculated. + min_hop_distance : float or bool (default = True) + If a float, skips any hops where the working ion moves a distance less + than min_hop_distance. + If True, min_hop_distance is set to twice the average ionic radius. + If False, no checks are made. + """ + + image_relax_maker: ForceFieldRelaxMaker + name: str = "MLFF ApproxNEB single hop from endpoints maker" + + def get_charge_density( + self, prev_dir_or_chgcar: str | Path | Chgcar | Sequence[str | Path | Chgcar] + ) -> VolumetricData: + """Obtain charge density from a specified path, CHGCAR, or list of them. + + Parameters + ---------- + prev_dir_or_chgcar : str or Path or pymatgen .Chgcar, or a list of these + Path(s) to the CHGCAR/AECCAR* file(s) or the object(s) themselves. + + Returns + ------- + VolumetricData + The charge density + """ + if isinstance(prev_dir_or_chgcar, str | Path | Chgcar): + prev_dir_or_chgcar = [prev_dir_or_chgcar] + + for idx, obj in enumerate(prev_dir_or_chgcar): + chg = Chgcar.from_file(obj) if isinstance(obj, str | Path) else obj + if idx == 0: + charge_density = chg + else: + charge_density += chg + return charge_density + + @classmethod + def from_force_field_name( + cls, + force_field_name: str | MLFF, + **kwargs, + ) -> Self: + """ + Create an ApproxNEB flow from a forcefield name. + + Parameters + ---------- + force_field_name : str or .MLFF + The name of the force field. + **kwargs + Additional kwargs to pass to ApproxNEB + + + Returns + ------- + MLFFApproxNebFromEndpointsMaker + """ + force_field_name = _get_formatted_ff_name(force_field_name) + kwargs.update( + image_relax_maker=ForceFieldRelaxMaker( + force_field_name=force_field_name, relax_cell=False + ), + ) + return cls( + name=( + f"{force_field_name.split('MLFF.')[-1]} ApproxNEB from endpoints Maker" + ), + **kwargs, + ) diff --git a/src/atomate2/forcefields/jobs.py b/src/atomate2/forcefields/jobs.py index 2276de0f25..2e1896de20 100644 --- a/src/atomate2/forcefields/jobs.py +++ b/src/atomate2/forcefields/jobs.py @@ -7,49 +7,26 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING -from ase.io import Trajectory as AseTrajectory -from ase.units import GPa as _GPa_to_eV_per_A3 from jobflow import job from monty.dev import deprecated -from pymatgen.core.trajectory import Trajectory as PmgTrajectory from atomate2.ase.jobs import AseRelaxMaker -from atomate2.forcefields import MLFF, _get_formatted_ff_name from atomate2.forcefields.schemas import ForceFieldTaskDocument -from atomate2.forcefields.utils import ase_calculator, revert_default_dtype +from atomate2.forcefields.utils import ( + _DEFAULT_CALCULATOR_KWARGS, + _FORCEFIELD_DATA_OBJECTS, + MLFF, + ForceFieldMixin, +) if TYPE_CHECKING: from collections.abc import Callable from pathlib import Path - from ase.calculators.calculator import Calculator from pymatgen.core.structure import Structure logger = logging.getLogger(__name__) -_FORCEFIELD_DATA_OBJECTS = [PmgTrajectory, AseTrajectory, "ionic_steps"] - -_DEFAULT_CALCULATOR_KWARGS = { - MLFF.CHGNet: {"stress_weight": _GPa_to_eV_per_A3}, - MLFF.M3GNet: {"stress_weight": _GPa_to_eV_per_A3}, - MLFF.NEP: {"model_filename": "nep.txt"}, - MLFF.GAP: {"args_str": "IP GAP", "param_filename": "gap.xml"}, - MLFF.MACE: {"model": "medium"}, - MLFF.MACE_MP_0: {"model": "medium"}, - MLFF.MACE_MPA_0: {"model": "medium-mpa-0"}, - MLFF.MACE_MP_0B3: {"model": "medium-0b3"}, - MLFF.MATPES_PBE: { - "architecture": "TensorNet", - "version": "2025.1", - "stress_unit": "eV/A3", - }, - MLFF.MATPES_R2SCAN: { - "architecture": "TensorNet", - "version": "2025.1", - "stress_unit": "eV/A3", - }, -} - def forcefield_job(method: Callable) -> job: """ @@ -88,7 +65,7 @@ def make(structure): @dataclass -class ForceFieldRelaxMaker(AseRelaxMaker): +class ForceFieldRelaxMaker(ForceFieldMixin, AseRelaxMaker): """ Base Maker to calculate forces and stresses using any force field. @@ -142,18 +119,6 @@ class ForceFieldRelaxMaker(AseRelaxMaker): calculator_kwargs: dict = field(default_factory=dict) task_document_kwargs: dict = field(default_factory=dict) - def __post_init__(self) -> None: - """Ensure that force_field_name is correctly assigned as str.""" - self.force_field_name = _get_formatted_ff_name(self.force_field_name) - - # Pad calculator_kwargs with default values, but permit user to override them - self.calculator_kwargs = { - **_DEFAULT_CALCULATOR_KWARGS.get( - MLFF(self.force_field_name.split("MLFF.")[-1]), {} - ), - **self.calculator_kwargs, - } - @forcefield_job def make( self, structure: Structure, prev_dir: str | Path | None = None @@ -169,8 +134,7 @@ def make( A previous calculation directory to copy output files from. Unused, just added to match the method signature of other makers. """ - with revert_default_dtype(): - ase_result = self.run_ase(structure, prev_dir=prev_dir) + ase_result = self._run_ase_safe(structure, prev_dir=prev_dir) if len(self.task_document_kwargs) > 0: warnings.warn( @@ -195,14 +159,6 @@ def make( **self.task_document_kwargs, ) - @property - def calculator(self) -> Calculator: - """ASE calculator, can be overwritten by user.""" - return ase_calculator( - str(self.force_field_name), # make mypy happy - **self.calculator_kwargs, - ) - @dataclass class ForceFieldStaticMaker(ForceFieldRelaxMaker): diff --git a/src/atomate2/forcefields/md.py b/src/atomate2/forcefields/md.py index c46330cd52..21a6ffded3 100644 --- a/src/atomate2/forcefields/md.py +++ b/src/atomate2/forcefields/md.py @@ -10,23 +10,22 @@ from monty.dev import deprecated from atomate2.ase.md import AseMDMaker, MDEnsemble -from atomate2.forcefields import MLFF, _get_formatted_ff_name -from atomate2.forcefields.jobs import ( +from atomate2.forcefields.schemas import ForceFieldTaskDocument +from atomate2.forcefields.utils import ( _DEFAULT_CALCULATOR_KWARGS, _FORCEFIELD_DATA_OBJECTS, + MLFF, + ForceFieldMixin, ) -from atomate2.forcefields.schemas import ForceFieldTaskDocument -from atomate2.forcefields.utils import ase_calculator, revert_default_dtype if TYPE_CHECKING: from pathlib import Path - from ase.calculators.calculator import Calculator from pymatgen.core.structure import Structure @dataclass -class ForceFieldMDMaker(AseMDMaker): +class ForceFieldMDMaker(ForceFieldMixin, AseMDMaker): """ Perform MD with a force field. @@ -107,23 +106,6 @@ class ForceFieldMDMaker(AseMDMaker): Options to pass to the TaskDoc. """ - name: str = "Forcefield MD" - force_field_name: str | MLFF = MLFF.Forcefield - task_document_kwargs: dict = None - - def __post_init__(self) -> None: - """Ensure that force_field_name is correctly assigned.""" - super().__post_init__() - self.force_field_name = _get_formatted_ff_name(self.force_field_name) - - # Pad calculator_kwargs with default values, but permit user to override them - self.calculator_kwargs = { - **_DEFAULT_CALCULATOR_KWARGS.get( - MLFF(self.force_field_name.split("MLFF.")[-1]), {} - ), - **self.calculator_kwargs, - } - @job( data=[*_FORCEFIELD_DATA_OBJECTS, "ionic_steps"], output_schema=ForceFieldTaskDocument, @@ -144,8 +126,7 @@ def make( A previous calculation directory to copy output files from. Unused, just added to match the method signature of other makers. """ - with revert_default_dtype(): - md_result = self.run_ase(structure, prev_dir=prev_dir) + md_result = self._run_ase_safe(structure, prev_dir=prev_dir) self.task_document_kwargs = self.task_document_kwargs or {} if len(self.task_document_kwargs) > 0: @@ -171,14 +152,6 @@ def make( **self.task_document_kwargs, ) - @property - def calculator(self) -> Calculator: - """ASE calculator, can be overwritten by user.""" - return ase_calculator( - str(self.force_field_name), # make mypy happy - **self.calculator_kwargs, - ) - @deprecated( replacement=ForceFieldMDMaker, diff --git a/src/atomate2/forcefields/neb.py b/src/atomate2/forcefields/neb.py new file mode 100644 index 0000000000..0f0c8338ff --- /dev/null +++ b/src/atomate2/forcefields/neb.py @@ -0,0 +1,88 @@ +"""Run NEB with ML forcefields.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from emmet.core.neb import NebResult +from jobflow import job + +from atomate2.ase.neb import AseNebFromEndpointsMaker, AseNebFromImagesMaker +from atomate2.forcefields.jobs import ForceFieldRelaxMaker +from atomate2.forcefields.utils import _FORCEFIELD_DATA_OBJECTS, MLFF, ForceFieldMixin + +if TYPE_CHECKING: + from pathlib import Path + + from pymatgen.core import Structure + from typing_extensions import Self + + +@dataclass +class ForceFieldNebFromImagesMaker(ForceFieldMixin, AseNebFromImagesMaker): + """Run NEB with an ML forcefield using ASE.""" + + name: str = "Forcefield NEB from images" + force_field_name: str | MLFF = MLFF.Forcefield + + @job(data=_FORCEFIELD_DATA_OBJECTS, schema=NebResult) + def make( + self, images: list[Structure], prev_dir: str | Path | None = None + ) -> NebResult: + """ + Perform NEB with MLFFs on a set of images. + + Parameters + ---------- + images: list of pymatgen .Structure + Structures to perform NEB on. + 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_safe(images=images, prev_dir=prev_dir) + + +@dataclass +class ForceFieldNebFromEndpointsMaker(ForceFieldMixin, AseNebFromEndpointsMaker): + """Run NEB with an ML forcefield using ASE.""" + + name: str = "Forcefield NEB from endpoints" + force_field_name: str | MLFF = MLFF.Forcefield + + @job(data=_FORCEFIELD_DATA_OBJECTS, schema=NebResult) + def make( + self, images: list[Structure], prev_dir: str | Path | None = None + ) -> NebResult: + """ + Perform NEB with MLFFs on a set of images. + + Parameters + ---------- + images: list of pymatgen .Structure + Structures to perform NEB on. + 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_safe(images=images, prev_dir=prev_dir) + + @classmethod + def from_force_field_name(cls, force_field_name: str | MLFF, **kwargs) -> Self: + """Create a force field NEB job from its name. + + Parameters + ---------- + force_field_name : str or MLFF + The name of the forcefield. Should be a valid MLFF member. + **kwargs + kwargs to pass to ForceFieldNebFromEndpointsMaker. + """ + endpoint_relax_maker = ForceFieldRelaxMaker(force_field_name=force_field_name) + return cls( + name=f"{force_field_name} NEB from endpoints maker", + endpoint_relax_maker=endpoint_relax_maker, + force_field_name=force_field_name, + **kwargs, + ) diff --git a/src/atomate2/forcefields/utils.py b/src/atomate2/forcefields/utils.py index 4c1800cadb..96e627088c 100644 --- a/src/atomate2/forcefields/utils.py +++ b/src/atomate2/forcefields/utils.py @@ -2,13 +2,18 @@ from __future__ import annotations +import warnings from contextlib import contextmanager +from dataclasses import dataclass, field +from enum import Enum from pathlib import Path from typing import TYPE_CHECKING +from ase.io import Trajectory as AseTrajectory +from ase.units import Bohr +from ase.units import GPa as _GPa_to_eV_per_A3 from monty.json import MontyDecoder - -from atomate2.forcefields import MLFF +from pymatgen.core.trajectory import Trajectory as PmgTrajectory if TYPE_CHECKING: from collections.abc import Generator @@ -16,6 +21,149 @@ from ase.calculators.calculator import Calculator + from atomate2.ase.schemas import AseResult + +_FORCEFIELD_DATA_OBJECTS = [PmgTrajectory, AseTrajectory, "ionic_steps"] + + +class MLFF(Enum): # TODO inherit from StrEnum when 3.11+ + """Names of ML force fields.""" + + MACE = "MACE" # This is MACE-MP-0 (medium), deprecated + MACE_MP_0 = "MACE-MP-0" + MACE_MPA_0 = "MACE-MPA-0" + MACE_MP_0B3 = "MACE-MP-0b3" + GAP = "GAP" + M3GNet = "M3GNet" + CHGNet = "CHGNet" + Forcefield = "Forcefield" # default placeholder option + NEP = "NEP" + Nequip = "Nequip" + SevenNet = "SevenNet" + MATPES_R2SCAN = "MatPES-r2SCAN" + MATPES_PBE = "MatPES-PBE" + + @classmethod + def _missing_(cls, value: Any) -> Any: + """Allow input of str(MLFF) as valid enum.""" + if isinstance(value, str): + value = value.split("MLFF.")[-1] + for member in cls: + if member.name == value: + return member + return None + + +_DEFAULT_CALCULATOR_KWARGS = { + MLFF.CHGNet: {"stress_weight": _GPa_to_eV_per_A3}, + MLFF.M3GNet: {"stress_weight": _GPa_to_eV_per_A3}, + MLFF.NEP: {"model_filename": "nep.txt"}, + MLFF.GAP: {"args_str": "IP GAP", "param_filename": "gap.xml"}, + MLFF.MACE: {"model": "medium"}, + MLFF.MACE_MP_0: {"model": "medium"}, + MLFF.MACE_MPA_0: {"model": "medium-mpa-0"}, + MLFF.MACE_MP_0B3: {"model": "medium-0b3"}, + MLFF.MATPES_PBE: { + "architecture": "TensorNet", + "version": "2025.1", + "stress_unit": "eV/A3", + }, + MLFF.MATPES_R2SCAN: { + "architecture": "TensorNet", + "version": "2025.1", + "stress_unit": "eV/A3", + }, +} + + +def _get_formatted_ff_name(force_field_name: str | MLFF) -> str: + """ + Get the standardized force field name. + + Parameters + ---------- + force_field_name : str or .MLFF + The name of the force field + + Returns + ------- + str : the name of the forcefield from MLFF + """ + if isinstance(force_field_name, str): + # ensure `force_field_name` uses enum format + if force_field_name in MLFF.__members__: + force_field_name = MLFF[force_field_name] + elif force_field_name in [v.value for v in MLFF]: + force_field_name = MLFF(force_field_name) + force_field_name = str(force_field_name) + if force_field_name in {"MLFF.MACE", "MACE"}: + warnings.warn( + "Because the default MP-trained MACE model is constantly evolving, " + "we no longer recommend using `MACE` or `MLFF.MACE` to specify " + "a MACE model. For reproducibility purposes, specifying `MACE` " + "will still default to MACE-MP-0 (medium), which is identical to " + "specifying `MLFF.MACE_MP_0`.", + category=UserWarning, + stacklevel=2, + ) + return force_field_name + + +@dataclass +class ForceFieldMixin: + """Mix-in class for force-fields. + + Attributes + ---------- + force_field_name : str or MLFF + Name of the forcefield which will be + correctly deserialized/standardized if the forcefield is + a known `MLFF`. + calculator_kwargs : dict = field(default_factory=dict) + Keyword arguments that will get passed to the ASE calculator. + task_document_kwargs: dict = field(default_factory=dict) + Additional keyword args passed to :obj:`.ForceFieldTaskDocument() + or another final document schema. + """ + + force_field_name: str | MLFF = MLFF.Forcefield + calculator_kwargs: dict = field(default_factory=dict) + task_document_kwargs: dict = field(default_factory=dict) + + def __post_init__(self) -> None: + """Ensure that force_field_name is correctly assigned.""" + if hasattr(super(), "__post_init__"): + super().__post_init__() # type: ignore[misc] + + self.force_field_name = _get_formatted_ff_name(self.force_field_name) + + # Pad calculator_kwargs with default values, but permit user to override them + self.calculator_kwargs = { + **_DEFAULT_CALCULATOR_KWARGS.get( + MLFF(self.force_field_name.split("MLFF.")[-1]), {} + ), + **self.calculator_kwargs, + } + + if not self.task_document_kwargs.get("force_field_name"): + self.task_document_kwargs["force_field_name"] = str(self.force_field_name) + + def _run_ase_safe(self, *args, **kwargs) -> AseResult: + if not hasattr(self, "run_ase"): + raise NotImplementedError( + "You must implement a `run_ase` method to use this method." + ) + with revert_default_dtype(): + return self.run_ase(*args, **kwargs) + + @property + def calculator(self) -> Calculator: + """ASE calculator, can be overwritten by user.""" + return ase_calculator( + str(self.force_field_name), # make mypy happy + **self.calculator_kwargs, + ) + def ase_calculator( calculator_meta: str | MLFF | dict, **kwargs: Any @@ -76,7 +224,7 @@ def ase_calculator( model = kwargs.get("model") if isinstance(model, str | Path) and Path(model).exists(): model_path = model - device = kwargs.get("device") or "cpu" + device = kwargs.pop("device", None) or "cpu" if "device" in kwargs: del kwargs["device"] calculator = MACECalculator( @@ -84,6 +232,27 @@ def ase_calculator( device=device, **kwargs, ) + + if kwargs.get("dispersion", False): + # See https://github.com/materialsproject/atomate2/issues/1262 + # Specifying an explicit model path unsets the dispersio + # Reset it here. + import torch + from ase.calculators.mixing import SumCalculator + from torch_dftd.torch_dftd3_calculator import TorchDFTD3Calculator + + default_d3_kwargs = { + "damping": "bj", + "xc": "pbe", + "cutoff": 40.0 * Bohr, + "dtype": kwargs.get("default_dtype", torch.get_default_dtype()), + } + for k, v in default_d3_kwargs.items(): + if k not in kwargs: + kwargs[k] = v + + d3_calc = TorchDFTD3Calculator(device=device, **kwargs) + calculator = SumCalculator([calculator, d3_calc]) else: calculator = mace_mp(**kwargs) diff --git a/src/atomate2/utils/testing/vasp.py b/src/atomate2/utils/testing/vasp.py index c1b5133a72..beef5ec91f 100644 --- a/src/atomate2/utils/testing/vasp.py +++ b/src/atomate2/utils/testing/vasp.py @@ -19,6 +19,8 @@ from pymatgen.util.coord import find_in_coord_list_pbc import atomate2.vasp.jobs.base +import atomate2.vasp.jobs.defect +import atomate2.vasp.jobs.neb try: import atomate2.vasp.jobs.defect @@ -119,6 +121,8 @@ def mock_nelect(*_args, **_kwargs) -> int: monkeypatch.setattr(atomate2.vasp.run, "run_vasp", mock_run_vasp) monkeypatch.setattr(atomate2.vasp.jobs.base, "run_vasp", mock_run_vasp) + monkeypatch.setattr(atomate2.vasp.jobs.defect, "run_vasp", mock_run_vasp) + monkeypatch.setattr(atomate2.vasp.jobs.neb, "run_vasp", mock_run_vasp) if pmg_defects_installed: monkeypatch.setattr(atomate2.vasp.jobs.defect, "run_vasp", mock_run_vasp) monkeypatch.setattr(VaspInputSet, "get_input_set", mock_get_input_set) @@ -171,7 +175,14 @@ def fake_run_vasp( _check_kpoints(ref_path) if "poscar" in check_inputs: - _check_poscar(ref_path) + if len(neb_sub_dirs := sorted((ref_path / "inputs").glob("[0-9][0-9]"))) > 0: + for idx, neb_sub_dir in enumerate(neb_sub_dirs): + _check_poscar( + zpath(neb_sub_dir / "POSCAR"), + user_poscar_path=zpath(Path(f"{idx:02}") / "POSCAR"), + ) + else: + _check_poscar(ref_path) if "potcar" in check_inputs: _check_potcar(ref_path) @@ -255,10 +266,14 @@ def _check_kpoints(ref_path: Path) -> None: ) -def _check_poscar(ref_path: Path) -> None: +def _check_poscar( + ref_poscar_path: Path, + user_poscar_path: Path | None = None, +) -> None: """Check that POSCAR information is consistent with the reference calculation.""" - user_poscar_path = zpath("POSCAR") - ref_poscar_path = zpath(ref_path / "inputs" / "POSCAR") + user_poscar_path = user_poscar_path or zpath("POSCAR") + if ref_poscar_path.is_dir(): + ref_poscar_path = zpath(ref_poscar_path / "inputs" / "POSCAR") user_poscar = Poscar.from_file(user_poscar_path) ref_poscar = Poscar.from_file(ref_poscar_path) @@ -278,8 +293,11 @@ def _check_poscar(ref_path: Path) -> None: or user_poscar.site_symbols != ref_poscar.site_symbols or not all(coord_match) ): + no_match = [f"{idx}" for idx, v in enumerate(coord_match) if not v] + aux_str = " on site(s) " + ", ".join(no_match) if no_match else "" raise ValueError( - f"POSCAR files are inconsistent\n\n{ref_poscar_path!s}\n{ref_poscar}" + f"POSCAR files are inconsistent{aux_str}" + f"\n\n{ref_poscar_path!s}\n{ref_poscar}" f"\n\n{user_poscar_path!s}\n{user_poscar}" ) @@ -324,10 +342,18 @@ def _clear_vasp_inputs() -> None: def _copy_vasp_outputs(ref_path: Path) -> None: """Copy VASP output files from the reference directory.""" output_path = ref_path / "outputs" + neb_dirs = sorted(output_path.glob("[0-9][0-9]")) + for output_file in output_path.iterdir(): if output_file.is_file(): shutil.copy(output_file, ".") + for idx, neb_dir in enumerate(neb_dirs): + (copied_neb_dir := Path(f"./{idx:02}")).mkdir(parents=True, exist_ok=True) + for output_file in neb_dir.iterdir(): + if output_file.is_file(): + shutil.copy(output_file, copied_neb_dir) + class TestData(BaseModel): """ diff --git a/src/atomate2/vasp/flows/approx_neb.py b/src/atomate2/vasp/flows/approx_neb.py new file mode 100644 index 0000000000..ba059b583d --- /dev/null +++ b/src/atomate2/vasp/flows/approx_neb.py @@ -0,0 +1,118 @@ +"""Define the ApproxNEB VASP flow.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import TYPE_CHECKING + +from atomate2.common.flows.approx_neb import ( + ApproxNebFromEndpointsMaker, + CommonApproxNebMaker, +) +from atomate2.vasp.jobs.approx_neb import ( + ApproxNebHostRelaxMaker, + ApproxNebImageRelaxMaker, + get_charge_density, +) + +if TYPE_CHECKING: + from pathlib import Path + + from pymatgen.io.vasp.outputs import Chgcar + + from atomate2.vasp.jobs.base import BaseVaspMaker + + +@dataclass +class ApproxNebMaker(CommonApproxNebMaker): + """Run an ApproxNEB workflow with VASP. + + Parameters + ---------- + name : str = "ApproxNEB" + Name of the workflow + host_relax_maker : Maker + Optional, a maker to relax the input host structure. + Defaults to atomate2.vasp.jobs.approx_neb.ApproxNebHostRelaxMaker + image_relax_maker : maker + Required, a maker to relax the ApproxNEB endpoints and images. + Defaults to atomate2.vasp.jobs.approx_neb.ApproxNebImageRelaxMaker + selective_dynamics_scheme : "fix_two_atoms" (default) or None + If "fix_two_atoms", uses the default selective dynamics scheme of ApproxNEB, + wherein the migrating ion and the ion farthest from it are the only + ions whose positions can relax. + use_aeccar : bool = False + If True, the sum of the host structure AECCAR0 (pseudo-core charge density) + and AECCAR2 (valence charge density) are used in image pathfinding. + If False (default), the CHGCAR (valence charge density) is used. + min_hop_distance : float or bool (default = True) + If a float, skips any hops where the working ion moves a distance less + than min_hop_distance. + If True, min_hop_distance is set to twice the average ionic radius. + If False, no checks are made. + """ + + name: str = "ApproxNEB VASP" + host_relax_maker: BaseVaspMaker | None = field( + default_factory=ApproxNebHostRelaxMaker + ) + image_relax_maker: BaseVaspMaker = field(default_factory=ApproxNebImageRelaxMaker) + use_aeccar: bool = False + + def get_charge_density(self, prev_dir: str | Path) -> Chgcar: + """Get charge density from a prior VASP calculation. + + Parameters + ---------- + prev_dir : str or Path + Path to the previous VASP calculation + + Returns + ------- + pymatgen Chgcar object + """ + return get_charge_density(prev_dir, use_aeccar=self.use_aeccar) + + +@dataclass +class ApproxNebSingleHopMaker(ApproxNebFromEndpointsMaker): + """ + Create an ApproxNEB VASP flow from specified endpoints. + + image_relax_maker : Maker + Maker to relax both endpoints and images + selective_dynamics_scheme : "fix_two_atoms" (default) or None + If "fix_two_atoms", uses the default selective dynamics scheme of ApproxNEB, + wherein the migrating ion and the ion farthest from it are the only + ions whose positions can relax. + min_images_per_hop : int or None + If an int, the minimum number of image calculations per hop that + must succeed to mark a hop as successfully calculated. + min_hop_distance : float or bool (default = True) + If a float, skips any hops where the working ion moves a distance less + than min_hop_distance. + If True, min_hop_distance is set to twice the average ionic radius. + If False, no checks are made. + use_aeccar : bool = False + If True, the sum of the host structure AECCAR0 (pseudo-core charge density) + and AECCAR2 (valence charge density) are used in image pathfinding. + If False (default), the CHGCAR (valence charge density) is used. + """ + + image_relax_maker: BaseVaspMaker = field(default_factory=ApproxNebImageRelaxMaker) + name: str = "VASP ApproxNEB single hop from endpoints maker" + use_aeccar: bool = False + + def get_charge_density(self, prev_dir: str | Path) -> Chgcar: + """Get charge density from a prior VASP calculation. + + Parameters + ---------- + prev_dir : str or Path + Path to the previous VASP calculation + + Returns + ------- + pymatgen Chgcar object + """ + return get_charge_density(prev_dir, use_aeccar=self.use_aeccar) diff --git a/src/atomate2/vasp/flows/electrode.py b/src/atomate2/vasp/flows/electrode.py index b416a02722..148e43d84f 100644 --- a/src/atomate2/vasp/flows/electrode.py +++ b/src/atomate2/vasp/flows/electrode.py @@ -55,7 +55,8 @@ class ElectrodeInsertionMaker(electrode_flows.ElectrodeInsertionMaker): The structure matcher to use to determine if additional insertion is needed. """ - def get_charge_density(self, prev_dir: Path | str) -> VolumetricData: + @staticmethod + def get_charge_density(prev_dir: Path | str) -> VolumetricData: """Get the charge density of a structure. Parameters diff --git a/src/atomate2/vasp/jobs/approx_neb.py b/src/atomate2/vasp/jobs/approx_neb.py new file mode 100644 index 0000000000..43bc189ca5 --- /dev/null +++ b/src/atomate2/vasp/jobs/approx_neb.py @@ -0,0 +1,76 @@ +"""Define ApproxNEB jobs.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from pathlib import Path +from typing import TYPE_CHECKING + +from monty.os.path import zpath +from pymatgen.io.vasp.outputs import Chgcar + +from atomate2.utils.path import strip_hostname +from atomate2.vasp.flows.core import DoubleRelaxMaker +from atomate2.vasp.jobs.core import RelaxMaker +from atomate2.vasp.run import JobType +from atomate2.vasp.sets.approx_neb import ApproxNebSetGenerator + +if TYPE_CHECKING: + from atomate2.vasp.jobs.base import BaseVaspMaker + from atomate2.vasp.sets.base import VaspInputGenerator + + +@dataclass +class ApproxNebHostRelaxMaker(DoubleRelaxMaker): + """Maker to perform a double relaxation on an ApproxNEB host structure.""" + + name: str = "ApproxNEB host relax" + relax_maker1: BaseVaspMaker | None = field( + default_factory=lambda: RelaxMaker(input_set_generator=ApproxNebSetGenerator()) + ) + relax_maker2: BaseVaspMaker = field( + default_factory=lambda: RelaxMaker(input_set_generator=ApproxNebSetGenerator()) + ) + + +@dataclass +class ApproxNebImageRelaxMaker(RelaxMaker): + """ + Maker to perform a double relaxation on an ApproxNEB endpoint/image structure. + + Very important here - we are doing a double relaxation in the atomate style, + where one job maps to two VASP calculations. + """ + + name: str = "ApproxNEB image relax" + input_set_generator: VaspInputGenerator = field( + default_factory=lambda: ApproxNebSetGenerator(set_type="image") + ) + run_vasp_kwargs: dict = field( + default_factory=lambda: { + "job_type": JobType.DOUBLE_RELAXATION, + } + ) + + +def get_charge_density(prev_dir: str | Path, use_aeccar: bool = False) -> Chgcar: + """Get charge density from a prior VASP calculation. + + Parameters + ---------- + prev_dir : str or Path + Path to the previous VASP calculation + use_aeccar : bool = False + True: use AECCAR0 and AECCAR2 (pseudo-all electron charge density) + rather than CHGCAR (valence electron density only - False) + + Returns + ------- + pymatgen Chgcar object + """ + prev_dir = Path(strip_hostname(prev_dir)) + if use_aeccar: + aeccar0 = Chgcar.from_file(zpath(str(prev_dir / "AECCAR0"))) + aeccar2 = Chgcar.from_file(zpath(str(prev_dir / "AECCAR2"))) + return aeccar0 + aeccar2 + return Chgcar.from_file(zpath(str(prev_dir / "CHGCAR"))) diff --git a/src/atomate2/vasp/jobs/base.py b/src/atomate2/vasp/jobs/base.py index 188ed20995..a7be55f525 100644 --- a/src/atomate2/vasp/jobs/base.py +++ b/src/atomate2/vasp/jobs/base.py @@ -8,6 +8,7 @@ from shutil import which from typing import TYPE_CHECKING +from emmet.core.neb import NebIntermediateImagesDoc from emmet.core.tasks import TaskDoc from jobflow import Maker, Response, job from monty.serialization import dumpfn @@ -248,10 +249,17 @@ def make( ) -def get_vasp_task_document(path: Path | str, **kwargs) -> TaskDoc: +def get_vasp_task_document( + path: Path | str, is_neb: bool = False, **kwargs +) -> TaskDoc | NebIntermediateImagesDoc: """Get VASP Task Document using atomate2 settings.""" kwargs.setdefault("store_additional_json", SETTINGS.VASP_STORE_ADDITIONAL_JSON) + kwargs.setdefault("store_volumetric_data", SETTINGS.VASP_STORE_VOLUMETRIC_DATA) + + if is_neb: + return NebIntermediateImagesDoc.from_directory(path, **kwargs) + kwargs.setdefault( "volume_change_warning_tol", SETTINGS.VASP_VOLUME_CHANGE_WARNING_TOL ) @@ -288,6 +296,4 @@ def get_vasp_task_document(path: Path | str, **kwargs) -> TaskDoc: stacklevel=1, ) - kwargs.setdefault("store_volumetric_data", SETTINGS.VASP_STORE_VOLUMETRIC_DATA) - return TaskDoc.from_directory(path, **kwargs) diff --git a/src/atomate2/vasp/jobs/neb.py b/src/atomate2/vasp/jobs/neb.py new file mode 100644 index 0000000000..7a1a162023 --- /dev/null +++ b/src/atomate2/vasp/jobs/neb.py @@ -0,0 +1,306 @@ +"""Define NEB VASP jobs.""" + +from __future__ import annotations + +import logging +from dataclasses import dataclass, field +from glob import glob +from os import mkdir +from pathlib import Path +from typing import TYPE_CHECKING + +from emmet.core.neb import NebIntermediateImagesDoc, NebTaskDoc +from jobflow import Flow, Maker, Response, job +from monty.serialization import dumpfn +from pymatgen.io.vasp import Kpoints + +from atomate2 import SETTINGS +from atomate2.common.files import gzip_output_folder +from atomate2.common.jobs.neb import NebInterpolation, get_images_from_endpoints +from atomate2.utils.path import strip_hostname +from atomate2.vasp.files import copy_vasp_outputs, write_vasp_input_set +from atomate2.vasp.jobs.base import ( + _DATA_OBJECTS, + _FILES_TO_ZIP, + BaseVaspMaker, + get_vasp_task_document, +) +from atomate2.vasp.run import JobType, run_vasp, should_stop_children +from atomate2.vasp.sets.core import NebSetGenerator + +if TYPE_CHECKING: + from collections.abc import Callable, Sequence + + from pymatgen.core import Structure + + from atomate2.vasp.sets.base import VaspInputGenerator + +logger = logging.getLogger(__name__) + + +def vasp_neb_job(method: Callable) -> job: + """ + Decorate the ``make`` method of VASP NEB job makers. + + This is a thin wrapper around :obj:`~jobflow.core.job.Job` that configures common + settings for VASP NEB jobs. For example, it ensures that large data objects + (band structures, density of states, LOCPOT, CHGCAR, etc) are all stored in the + atomate2 data store. It also configures the output schema to be a VASP + :obj:`.NebTaskDoc`. + + Parameters + ---------- + method : callable + A BaseVaspMaker.make method. This should not be specified directly and is + implied by the decorator. + + Returns + ------- + callable + A decorated version of the make function that will generate VASP NEB jobs. + """ + return job(method, data=_DATA_OBJECTS, schema=NebIntermediateImagesDoc) + + +@job +def collect_neb_output( + endpoint_dirs: list[str | Path] | None, neb_head_dir: str | Path, **neb_doc_kwargs +) -> NebTaskDoc: + """Parse NEB output from image and optionally endpoint relaxations.""" + if endpoint_dirs is not None and len(endpoint_dirs) == 2: + return NebTaskDoc.from_directories( + [strip_hostname(endpoint_path) for endpoint_path in endpoint_dirs], + strip_hostname(neb_head_dir), + **neb_doc_kwargs, + ) + return NebTaskDoc.from_directory(neb_head_dir, **neb_doc_kwargs) + + +@dataclass +class NebFromImagesMaker(BaseVaspMaker): + """ + Maker to create VASP NEB jobs from a set of images. + + Note on KPOINTS / VASP 6: + -------------------------- + There's a bug in VASP 6 compiled with HDF5 support: + https://www.vasp.at/forum/viewtopic.php?f=3&t=18721&p=23430&hilit=neb+hdf5+images#p23430 + + VASP performs a validation check of whether the KPOINTS file + used in the "head" directory (which contains 00, 01, ..., 0N + subdirectories) is the same as in each image subdirectory. + + If KSPACING is used, this check isn't performed. + However, a "kludge" to get around this issue is to simply + copy the KPOINTS file used in the head directory + to each image directory. + + Parameters + ---------- + kpoints_kludge: Kpoints or bool. Default is True. + See "Note on KPOINTS / VASP 6" above. + If True (default), the job checks for the + existence of a KPOINTS file and copies it (if it exists) + to each image subdirectory. + The user can override this with a Kpoints object of their choice. + If kpoints_kludge is set to False, the KPOINTS file will not be + copied to each image. + name : str + The job name. + input_set_generator : .VaspInputGenerator + A generator used to make the input set. + write_input_set_kwargs : dict + Keyword arguments that will get passed to :obj:`.write_vasp_input_set`. + copy_vasp_kwargs : dict + Keyword arguments that will get passed to :obj:`.copy_vasp_outputs`. + run_vasp_kwargs : dict + Keyword arguments that will get passed to :obj:`.run_vasp`. + task_document_kwargs : dict + Keyword arguments that will get passed to :obj:`.TaskDoc.from_directory`. + stop_children_kwargs : dict + Keyword arguments that will get passed to :obj:`.should_stop_children`. + write_additional_data : dict + Additional data to write to the current directory. Given as a dict of + {filename: data}. Note that if using FireWorks, dictionary keys cannot contain + the "." character which is typically used to denote file extensions. To avoid + this, use the ":" character, which will automatically be converted to ".". E.g. + ``{"my_file:txt": "contents of the file"}``. + """ + + name: str = "NEB" + input_set_generator: VaspInputGenerator = field(default_factory=NebSetGenerator) + run_vasp_kwargs: dict = field( + default_factory=lambda: { + "job_type": JobType.NEB, + "vasp_job_kwargs": { + "output_file": "vasp.out", + "stderr_file": "std_err.txt", + }, + } + ) + kpoints_kludge: Kpoints | bool = True + + @vasp_neb_job + def make( + self, + images: list[Structure], + prev_dir: str | Path | None = None, + ) -> Response: + """ + Make an NEB job from a list of images. + + Parameters + ---------- + images : list[Structure] + A list of NEB images. + prev_dir : str or Path or None (default) + A previous directory to copy outputs from. + + """ + num_frames = len(images) + num_images = num_frames - 2 + self.input_set_generator.num_images = num_images + + # copy previous inputs + from_prev = prev_dir is not None + if prev_dir is not None: + copy_vasp_outputs(prev_dir, **self.copy_vasp_kwargs) + + self.write_input_set_kwargs.setdefault("from_prev", from_prev) + + # write vasp input files + write_vasp_input_set( + images[0], self.input_set_generator, **self.write_input_set_kwargs + ) + + if ( + Path("KPOINTS").exists() + and isinstance(self.kpoints_kludge, bool) + and self.kpoints_kludge + ): + self.kpoints_kludge = Kpoints.from_file("KPOINTS") + + for iimage in range(num_frames): + image_dir = f"{iimage:02}" + mkdir(image_dir) + + images[iimage].to(f"{image_dir}/POSCAR") + + if isinstance(self.kpoints_kludge, Kpoints): + self.kpoints_kludge.write_file(f"{image_dir}/KPOINTS") + + # write any additional data + for filename, data in self.write_additional_data.items(): + dumpfn(data, filename.replace(":", ".")) + + # run vasp + run_vasp(**self.run_vasp_kwargs) + + # parse vasp outputs + task_doc = get_vasp_task_document( + Path.cwd(), is_neb=True, **self.task_document_kwargs + ) + task_doc.task_label = self.name + + # decide whether child jobs should proceed + stop_children = should_stop_children(task_doc, **self.stop_children_kwargs) + + # gzip folder + gzip_output_folder( + directory=Path.cwd(), + setting=SETTINGS.VASP_ZIP_FILES, + files_list=_FILES_TO_ZIP, + ) + + for image_dir in glob(str(Path.cwd() / "[0-9][0-9]")): + gzip_output_folder( + directory=image_dir, + setting=SETTINGS.VASP_ZIP_FILES, + files_list=_FILES_TO_ZIP, + ) + + return Response( + stop_children=stop_children, + stored_data={"custodian": task_doc.custodian}, + output=task_doc, + ) + + +@dataclass +class NebFromEndpointsMaker(Maker): + """Maker to create VASP 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 an NEB on the + interpolated images, returning an NebTaskDoc. + + Parameters + ---------- + endpoint_relax_maker : BaseVaspMaker or None (default) + Optional maker to initially relax the endpoints. + images_maker : NebFromImagesMaker + Required maker to perform NEB on interpolated images. + """ + + endpoint_relax_maker: BaseVaspMaker | None = None + images_maker: NebFromImagesMaker = field(default_factory=NebFromImagesMaker) + + def make( + self, + endpoints: tuple[Structure, Structure] | list[Structure], + num_images: int, + prev_dir: str | Path = None, + interpolation_method: NebInterpolation = 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 = [] + endpoint_dirs: Sequence[str | Path] | None = None + 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] + endpoint_dirs = [ + endpoint_job.output.dir_name for endpoint_job in endpoint_jobs + ] + + get_images = get_images_from_endpoints( + endpoints, + num_images, + interpolation_method=interpolation_method, + **interpolation_kwargs, + ) + + image_relax_job = self.images_maker.make(get_images.output) # type: ignore[attr-defined] + + collate_job = collect_neb_output( + endpoint_dirs, image_relax_job.output.dir_name, store_calculations=False + ) + + return Flow( + [*endpoint_jobs, get_images, image_relax_job, collate_job], + output=collate_job.output, + ) diff --git a/src/atomate2/vasp/run.py b/src/atomate2/vasp/run.py index 0bb9370271..c0230a152d 100644 --- a/src/atomate2/vasp/run.py +++ b/src/atomate2/vasp/run.py @@ -5,10 +5,12 @@ import logging import shlex import subprocess -from os.path import expandvars +from glob import glob +from os.path import exists, expandvars from typing import TYPE_CHECKING, Any from custodian import Custodian +from custodian.custodian import Validator from custodian.vasp.handlers import ( FrozenJobErrorHandler, IncorrectSmearingHandler, @@ -23,7 +25,7 @@ VaspErrorHandler, WalltimeHandler, ) -from custodian.vasp.jobs import VaspJob +from custodian.vasp.jobs import VaspJob, VaspNEBJob from custodian.vasp.validators import VaspFilesValidator, VasprunXMLValidator from jobflow.utils import ValueEnum @@ -31,11 +33,12 @@ if TYPE_CHECKING: from collections.abc import Sequence + from pathlib import Path - from custodian.custodian import ErrorHandler, Validator + from custodian.custodian import ErrorHandler + from emmet.core.neb import NebIntermediateImagesDoc, NebTaskDoc from emmet.core.tasks import TaskDoc - DEFAULT_HANDLERS = ( VaspErrorHandler(), MeshSymmetryErrorHandler(), @@ -66,6 +69,7 @@ class JobType(ValueEnum): :obj:`.VaspJob.metagga_opt_run`. - ``FULL_OPT``: Custodian full optimization run from :obj:`.VaspJob.full_opt_run`. + - ``NEB``: Run a VASP NEB job. """ DIRECT = "direct" @@ -73,6 +77,7 @@ class JobType(ValueEnum): DOUBLE_RELAXATION = "double relaxation" METAGGA_OPT = "metagga opt" FULL_OPT = "full opt" + NEB = "neb" def run_vasp( @@ -82,7 +87,7 @@ def run_vasp( max_errors: int = SETTINGS.VASP_CUSTODIAN_MAX_ERRORS, scratch_dir: str = SETTINGS.CUSTODIAN_SCRATCH_DIR, handlers: Sequence[ErrorHandler] = DEFAULT_HANDLERS, - validators: Sequence[Validator] = _DEFAULT_VALIDATORS, + validators: Sequence[Validator] | None = None, wall_time: int | None = None, vasp_job_kwargs: dict[str, Any] = None, custodian_kwargs: dict[str, Any] = None, @@ -118,6 +123,9 @@ def run_vasp( """ vasp_job_kwargs = vasp_job_kwargs or {} custodian_kwargs = custodian_kwargs or {} + validators = validators or ( + _DEFAULT_VALIDATORS if job_type != JobType.NEB else (VaspNebFilesValidator(),) + ) vasp_cmd = expandvars(vasp_cmd) vasp_gamma_cmd = expandvars(vasp_gamma_cmd) @@ -126,7 +134,8 @@ def run_vasp( vasp_job_kwargs.setdefault("auto_npar", False) - vasp_job_kwargs.update(gamma_vasp_cmd=split_vasp_gamma_cmd) + if job_type != JobType.DOUBLE_RELAXATION: + vasp_job_kwargs.update(gamma_vasp_cmd=split_vasp_gamma_cmd) if job_type == JobType.DIRECT: logger.info(f"Running command: {vasp_cmd}") @@ -142,6 +151,8 @@ def run_vasp( jobs = VaspJob.metagga_opt_run(split_vasp_cmd, **vasp_job_kwargs) elif job_type == JobType.FULL_OPT: jobs = VaspJob.full_opt_run(split_vasp_cmd, **vasp_job_kwargs) + elif job_type == JobType.NEB: + jobs = [VaspNEBJob(split_vasp_cmd, **vasp_job_kwargs)] else: raise ValueError(f"Unsupported {job_type=}") @@ -162,7 +173,7 @@ def run_vasp( def should_stop_children( - task_document: TaskDoc, + task_document: TaskDoc | NebTaskDoc | NebIntermediateImagesDoc, handle_unsuccessful: bool | str = SETTINGS.VASP_HANDLE_UNSUCCESSFUL, ) -> bool: """ @@ -198,3 +209,28 @@ def should_stop_children( ) raise RuntimeError(f"Unknown option for {handle_unsuccessful=}") + + +class VaspNebFilesValidator(Validator): + """ + Validate VASP files for NEB jobs. + + Analog of custodian's VaspFilesValidator for NEB runs. + """ + + def check(self, base_directory: str | Path = "./") -> bool: + """ + Check that VASP ran in each NEB image directory. + + This validator ensures that CONTCAR, OSZICAR, and OUTCAR + files are created in each NEB image directory, consistent + with VaspFilesValidator. + + VASP does not create these files in the endpoint directories. + """ + image_dirs = sorted(glob(f"{base_directory}/[0-9][0-9]"))[1:-1] + return any( + not exists(f"{image_dir}/{vasp_file}") + for vasp_file in ("CONTCAR", "OSZICAR", "OUTCAR") + for image_dir in image_dirs + ) diff --git a/src/atomate2/vasp/sets/approx_neb.py b/src/atomate2/vasp/sets/approx_neb.py new file mode 100644 index 0000000000..9d526802f3 --- /dev/null +++ b/src/atomate2/vasp/sets/approx_neb.py @@ -0,0 +1,63 @@ +"""Module defining ApproxNEB input set generators.""" + +from dataclasses import dataclass +from typing import Literal + +from pymatgen.io.vasp.sets import MPRelaxSet + + +@dataclass +class ApproxNebSetGenerator(MPRelaxSet): + """ + Class to generate VASP ApproxNEB input sets. + + Parameters + ---------- + set_type: str + Can be either "host" or "image", for different stages of relaxation + """ + + auto_ismear: bool = False + auto_kspacing: bool = False + inherit_incar: bool | None = False + bandgap_tol: float = None + force_gamma: bool = True + auto_metal_kpoints: bool = True + symprec: float | None = None + set_type: Literal["image", "host"] = "host" + + def __post_init__(self) -> None: + """Ensure correct settings for class attrs.""" + super().__post_init__() + if self.set_type not in {"image", "host"}: + raise ValueError( + f'Unrecognized {self.set_type=}; must be "image" or "host".' + ) + + @property + def incar_updates(self) -> dict: + """ + Get updates to the INCAR settings for an ApproxNEB job. + + Returns + ------- + dict + A dictionary of updates to apply. + """ + updates = { + "EDIFF": 1e-4, + "EDIFFG": -0.05, + "IBRION": 1, + "ISIF": 3, + "ISMEAR": 0, + "LDAU": False, + "NSW": 400, + "ADDGRID": True, + "ISYM": 1, + "NELMIN": 4, + } + + if self.set_type == "image": + updates.update({"ISIF": 2, "ISYM": 0}) + + return updates diff --git a/src/atomate2/vasp/sets/core.py b/src/atomate2/vasp/sets/core.py index da05fcad1f..d799b7001a 100644 --- a/src/atomate2/vasp/sets/core.py +++ b/src/atomate2/vasp/sets/core.py @@ -744,3 +744,55 @@ def incar_updates(self) -> dict[str, Any]: "LWAVE": True, "ISYM": 0, } + + +@dataclass +class NebSetGenerator(VaspInputGenerator): + """ + Class to generate VASP NEB input sets. + + Parameters + ---------- + num_images : int + Number of NEB images to use. + climbing_image : bool + Whether to enable defaults for climbing image NEB. + **kwargs + Other keyword arguments that will be passed to :obj:`VaspInputGenerator`. + """ + + auto_ismear: bool = False + auto_kspacing: bool = False + inherit_incar: bool = False + num_images: int = 1 + climbing_image: bool = True + + @property + def incar_updates(self) -> dict: + """Get updates to the INCAR for an NEB job. + + Returns + ------- + dict + A dictionary of updates to apply. + """ + updates = { + "ISIF": 2, + "SPRING": -5, + "IMAGES": self.num_images, + "PREC": "Normal", + "NSW": 99, + "LCHARG": False, + "IBRION": 2, + "EDIFF": 1e-6, + } + if self.climbing_image: + updates.update( + { + "LCLIMB": True, + "IOPT": 1, + "IBRION": 3, + "POTIM": 0, + } + ) + return updates diff --git a/tests/ase/test_md.py b/tests/ase/test_md.py index 768bfece29..7300be1a55 100644 --- a/tests/ase/test_md.py +++ b/tests/ase/test_md.py @@ -25,6 +25,53 @@ _mb_velocity_seed = 2820285082114 +def test_npt_init_kwargs(si_structure, clean_dir): + """Checks correct initialization of NPT kwargs.""" + + from ase.md.npt import NPT + from ase.md.nptberendsen import NPTBerendsen + from ase.units import bar + + npt_berend_str = LennardJonesMDMaker( + ensemble="npt", + dynamics="berendsen", + temperature=300, + pressure=1.0, + n_steps=1, + ase_md_kwargs={"compressibility_au": 4.5 * bar}, + ) + # The run_ase is necessary for correct class instantiation + npt_berend_str.run_ase(si_structure) + assert "pressure_au" in npt_berend_str.ase_md_kwargs + assert "externalstress" not in npt_berend_str.ase_md_kwargs + + npt_berend_obj = LennardJonesMDMaker( + ensemble="npt", + dynamics=NPTBerendsen, + temperature=300, + pressure=1.0, + n_steps=1, + ase_md_kwargs={"compressibility_au": 4.5 * bar}, + ) + npt_berend_obj.run_ase(si_structure) + assert "pressure_au" in npt_berend_obj.ase_md_kwargs + assert "externalstress" not in npt_berend_obj.ase_md_kwargs + + npt_nh_str = LennardJonesMDMaker( + ensemble="npt", dynamics="nose-hoover", temperature=300, pressure=1.0, n_steps=1 + ) + npt_nh_str.run_ase(si_structure) + assert "externalstress" in npt_nh_str.ase_md_kwargs + assert "pressure_au" not in npt_nh_str.ase_md_kwargs + + npt_nh_obj = LennardJonesMDMaker( + ensemble="npt", dynamics=NPT, temperature=300, pressure=1.0, n_steps=1 + ) + npt_nh_obj.run_ase(si_structure) + assert "externalstress" in npt_nh_obj.ase_md_kwargs + assert "pressure_au" not in npt_nh_obj.ase_md_kwargs + + @pytest.mark.parametrize("calculator_name", list(name_to_maker)) def test_ase_nvt_maker(calculator_name, lj_fcc_ne_pars, fcc_ne_structure, clean_dir): # Langevin thermostat no longer works with single atom structures in ase>3.24.x diff --git a/tests/ase/test_neb.py b/tests/ase/test_neb.py new file mode 100644 index 0000000000..ab0806d40e --- /dev/null +++ b/tests/ase/test_neb.py @@ -0,0 +1,118 @@ +"""Test ASE NEB jobs.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import TYPE_CHECKING + +import numpy as np +import pytest +from ase.calculators.emt import EMT +from emmet.core.neb import BarrierAnalysis, NebResult +from jobflow import run_locally +from pymatgen.core import Structure + +from atomate2.ase.jobs import EmtRelaxMaker +from atomate2.ase.neb import AseNebFromEndpointsMaker, EmtNebFromImagesMaker +from atomate2.common.jobs.neb import _get_images_from_endpoints + +if TYPE_CHECKING: + from atomate2.ase.jobs import AseRelaxMaker + + +def initial_endpoints(): + structure = Structure( + 4.1 * np.eye(3), + ["Al", "Al", "Al", "Al"], + [[0.0, 0.0, 0.0], [0.0, 0.5, 0.5], [0.5, 0.0, 0.5], [0.5, 0.5, 0.0]], + ) * (2, 2, 2) + + _, n_idx, _, d = structure.get_neighbor_list( + structure.lattice.a / 2, sites=[structure[0]] + ) + min_idx = np.argmin(d) + nn_idx = n_idx[min_idx] + + endpoints = [structure.copy() for _ in range(2)] + endpoints[0].remove_sites([0]) + endpoints[1].remove_sites([nn_idx]) + return endpoints + + +@dataclass +class EmtNebFromEndpointsMaker(AseNebFromEndpointsMaker): + name: str = "ASE EMT NEB from images maker" + + endpoint_relax_maker: AseRelaxMaker = field( + default_factory=EmtRelaxMaker, + ) + + @property + def calculator(self): + return EMT(**self.calculator_kwargs) + + +def test_neb_from_images(): + images = _get_images_from_endpoints(initial_endpoints(), 1, autosort_tol=0.5) + assert len(images) == 3 + job = EmtNebFromImagesMaker().make(images) + response = run_locally(job) + output = response[job.uuid][1].output + assert isinstance(output, NebResult) + assert isinstance(output.barrier_analysis, BarrierAnalysis) + + assert all( + getattr(output, f"{direction}_barrier") == pytest.approx(0.3823904696311531) + for direction in ("forward", "reverse") + ) + + assert all( + output.energies[i] == pytest.approx(ref_energy) + for i, ref_energy in enumerate( + [1.1882799797196175, 1.5706704493507706, 1.1882799797196228] + ) + ) + + assert len(output.images) == 3 + assert all( + images[i] == init_image for i, init_image in enumerate(output.initial_images) + ) + assert output.state.value == "successful" + + +def test_neb_from_endpoints(memory_jobstore): + job = EmtNebFromEndpointsMaker().make(initial_endpoints(), 1, autosort_tol=0.5) + response = run_locally(job, store=memory_jobstore) + + # `job` is a replacement containing the workflow + replacement_job_names = {job.name for job in response[job.uuid][1].replace.jobs} + assert all( + name in replacement_job_names + for name in ( + "EMT relaxation endpoint 1", + "EMT relaxation endpoint 2", + "AseNebFromImagesMaker.make", + ) + ) + + output = NebResult( + **memory_jobstore.query_one({"name": "AseNebFromImagesMaker.make"})["output"] + ) + + assert isinstance(output.barrier_analysis, BarrierAnalysis) + + assert all( + getattr(output, f"{direction}_barrier") + == pytest.approx(0.40925355998834156, rel=1e-6) + for direction in ("forward", "reverse") + ) + + assert all( + output.energies[i] == pytest.approx(ref_energy) + for i, ref_energy in enumerate( + [0.8217810288156584, 1.231034588804, 0.8217810288156824] + ) + ) + + assert len(output.images) == 3 + assert output.state.value == "successful" diff --git a/tests/forcefields/flows/test_approx_neb.py b/tests/forcefields/flows/test_approx_neb.py new file mode 100644 index 0000000000..10fd50b2fe --- /dev/null +++ b/tests/forcefields/flows/test_approx_neb.py @@ -0,0 +1,51 @@ +import pytest +from emmet.core.neb import NebResult +from jobflow import run_locally +from pymatgen.core import Structure + +from atomate2.forcefields.flows.approx_neb import ForceFieldApproxNebFromEndpointsMaker +from atomate2.forcefields.jobs import ForceFieldStaticMaker +from atomate2.utils.testing.common import get_job_uuid_name_map + + +def test_approx_neb_from_endpoints(test_dir, clean_dir): + vasp_aneb_dir = test_dir / "vasp" / "ApproxNEB" + + endpoints = [ + Structure.from_file( + vasp_aneb_dir / f"ApproxNEB_image_relax_endpoint_{idx}/inputs/POSCAR.gz" + ) + for idx in (0, 3) + ] + + flow = ForceFieldApproxNebFromEndpointsMaker( + image_relax_maker=ForceFieldStaticMaker(force_field_name="MATPES_R2SCAN") + ).make("Zn", endpoints, vasp_aneb_dir / "host_structure_relax_2/outputs/CHGCAR.bz2") + + response = run_locally(flow) + output = { + job_name: response[uuid][1].output + for uuid, job_name in get_job_uuid_name_map(flow).items() + } + + assert isinstance(output["collate_images_single_hop"], NebResult) + assert all( + output["collate_images_single_hop"].energies[i] == pytest.approx(energy) + for i, energy in enumerate( + [ + -1559.5150146484375, + -1554.3154296875, + -1529.771484375, + -1525.8846435546875, + -1533.1453857421875, + -1554.561767578125, + -1559.5150146484375, + ] + ) + ) + + assert len(output["collate_images_single_hop"].images) == 7 + assert all( + image.volume == pytest.approx(endpoints[0].volume) + for image in output["collate_images_single_hop"].images + ) diff --git a/tests/forcefields/test_jobs.py b/tests/forcefields/test_jobs.py index 204a7238b5..929520b08a 100644 --- a/tests/forcefields/test_jobs.py +++ b/tests/forcefields/test_jobs.py @@ -192,15 +192,23 @@ def test_m3gnet_relax_maker(si_structure): ) +@pytest.mark.parametrize("dispersion", [False, True]) @mace_paths -def test_mace_static_maker(si_structure: Structure, model): +def test_mace_static_maker(si_structure: Structure, dispersion: bool, model): + from ase.calculators.mixing import SumCalculator + # generate job # NOTE the test model is not trained on Si, so the energy is not accurate - job = ForceFieldStaticMaker( + maker = ForceFieldStaticMaker( force_field_name="MACE", ionic_step_data=("structure", "energy"), - calculator_kwargs={"model": model}, - ).make(si_structure) + calculator_kwargs={"model": model, "dispersion": dispersion}, + ) + job = maker.make(si_structure) + if dispersion: + assert isinstance(maker.calculator, SumCalculator) + else: + assert not isinstance(maker.calculator, SumCalculator) # run the flow or job and ensure that it finished running successfully responses = run_locally(job, ensure_success=True) @@ -208,7 +216,9 @@ def test_mace_static_maker(si_structure: Structure, model): # validation the outputs of the job output1 = responses[job.uuid][1].output assert isinstance(output1, ForceFieldTaskDocument) - assert output1.output.energy == approx(-0.068231, rel=1e-4) + assert output1.output.energy == approx( + -0.6819882079032458 if dispersion else -0.068231, rel=1e-4 + ) assert output1.output.n_steps == 1 assert output1.forcefield_version == get_imported_version("mace-torch") diff --git a/tests/forcefields/test_neb.py b/tests/forcefields/test_neb.py new file mode 100644 index 0000000000..d71118b6df --- /dev/null +++ b/tests/forcefields/test_neb.py @@ -0,0 +1,84 @@ +from pathlib import Path + +import pytest +from jobflow import run_locally +from monty.serialization import loadfn +from pymatgen.core import Structure +from pymatgen.io.vasp.outputs import Xdatcar + +from atomate2.forcefields.neb import ForceFieldNebFromImagesMaker + + +def test_neb_from_images(test_dir, clean_dir): + endpoints = [ + Structure.from_file( + test_dir + / "vasp" + / "Si_NEB" + / f"relax_endpoint_{1 + i}" + / "inputs" + / "POSCAR.gz" + ) + for i in range(2) + ] + + images = endpoints[0].interpolate(endpoints[1], nimages=4, autosort_tol=0.5) + + job = ForceFieldNebFromImagesMaker( + force_field_name="MATPES_PBE", + traj_file="XDATCAR_si_self_diffusion", + traj_file_fmt="xdatcar", + relax_kwargs={"fmax": 0.5}, + ).make(images) + + response = run_locally(job) + output = response[job.uuid][1].output + + cwd = next(Path(p) for p in output.tags if Path(p).exists()) + xdatcars = [ + Xdatcar(cwd / f"XDATCAR_si_self_diffusion-image-{i + 1}") for i in range(5) + ] + + # Check that trajectory initial and final images are consistent with document + assert all( + xdatcars[i].structures[0] == image + for i, image in enumerate(output.initial_images) + ) + + all(xdatcars[i].structures[-1] == image for i, image in enumerate(output.images)) + + assert all( + output.energies[i] == pytest.approx(energy) + for i, energy in enumerate( + [ + -339.3764953613281, + -339.2301025390625, + -338.865234375, + -339.23004150390625, + -339.37652587890625, + ] + ) + ) + + assert output.state.value == "successful" + assert "forces not converged" in output.tags + + images = endpoints[0].interpolate(endpoints[1], nimages=2, autosort_tol=0.5) + job = ForceFieldNebFromImagesMaker( + force_field_name="MACE", + traj_file="si_self_diffusion.json.gz", + traj_file_fmt="pmg", + relax_kwargs={"fmax": 0.5}, + ).make(images) + + response = run_locally(job) + output = response[job.uuid][1].output + + trajectories = [ + loadfn(cwd / f"si_self_diffusion-image-{idx + 1}.json.gz") for idx in range(3) + ] + + assert all( + trajectories[i].frame_properties[-1]["energy"] == pytest.approx(energy) + for i, energy in enumerate(output.energies) + ) diff --git a/tests/forcefields/test_utils.py b/tests/forcefields/test_utils.py index 4e600744ec..bb59de0af9 100644 --- a/tests/forcefields/test_utils.py +++ b/tests/forcefields/test_utils.py @@ -1,7 +1,16 @@ +"""Test machine learning forcefield utility functions.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + import pytest from atomate2.forcefields import MLFF -from atomate2.forcefields.utils import ase_calculator +from atomate2.forcefields.utils import ase_calculator, revert_default_dtype + +if TYPE_CHECKING: + from pymatgen.core import Structure @pytest.mark.parametrize(("force_field"), [mlff.value for mlff in MLFF]) @@ -52,3 +61,33 @@ def test_m3gnet_pot(): assert str(m3gnet_pes_calc.potential) != str(m3gnet_default.potential) assert m3gnet_pes_calc.stress_weight == m3gnet_calculator.stress_weight assert m3gnet_pes_calc.stress_weight == m3gnet_default.stress_weight + + +def test_mace_explicit_dispersion(ba_ti_o3_structure: Structure): + from ase.calculators.mixing import SumCalculator + from mace.calculators.foundations_models import download_mace_mp_checkpoint + + energies = {"mace": -39.969810485839844, "d3": -1.3136245271781846} + + model_path = download_mace_mp_checkpoint("medium-mpa-0") + + atoms = ba_ti_o3_structure.to_ase_atoms() + + with revert_default_dtype(): + calc_no_path = ase_calculator(MLFF.MACE_MPA_0, dispersion=True) + assert isinstance(calc_no_path, SumCalculator) + assert calc_no_path.get_potential_energy(atoms=atoms) == pytest.approx( + sum(energies.values()) + ) + + calc_path = ase_calculator(MLFF.MACE_MPA_0, model=model_path) + assert not isinstance(calc_path, SumCalculator) + assert calc_path.get_potential_energy(atoms=atoms) == pytest.approx( + energies["mace"] + ) + + calc_path = ase_calculator(MLFF.MACE_MPA_0, model=model_path, dispersion=True) + assert isinstance(calc_no_path, SumCalculator) + assert calc_path.get_potential_energy(atoms=atoms) == pytest.approx( + sum(energies.values()) + ) diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/inputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/inputs/INCAR.gz new file mode 100644 index 0000000000..a26c49a9fc Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/inputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/inputs/KPOINTS.gz new file mode 100644 index 0000000000..ad0299d1d0 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/inputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/inputs/POSCAR.gz new file mode 100644 index 0000000000..0c33bec437 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/inputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..beff24ea54 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/CONTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/CONTCAR.relax1.gz new file mode 100644 index 0000000000..7b3f86f329 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/CONTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/CONTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/CONTCAR.relax2.gz new file mode 100644 index 0000000000..acce519100 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/CONTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/INCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..234c1ee15e Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/INCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/INCAR.relax1.gz new file mode 100644 index 0000000000..737da2e51d Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/INCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/INCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/INCAR.relax2.gz new file mode 100644 index 0000000000..604c523310 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/INCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..f4817e0ec2 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/KPOINTS.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/KPOINTS.relax1.gz new file mode 100644 index 0000000000..adec4bf445 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/KPOINTS.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/KPOINTS.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/KPOINTS.relax2.gz new file mode 100644 index 0000000000..ae608fea76 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/KPOINTS.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/OUTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/OUTCAR.relax1.gz new file mode 100644 index 0000000000..0610e73315 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/OUTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/OUTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/OUTCAR.relax2.gz new file mode 100644 index 0000000000..f857ceb58c Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/OUTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/POSCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..9c1201cd3f Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/POSCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/POSCAR.relax1.gz new file mode 100644 index 0000000000..f8e3b4f9a7 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/POSCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/POSCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/POSCAR.relax2.gz new file mode 100644 index 0000000000..ecbca46131 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/POSCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..beff24ea54 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/vasprun.xml.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/vasprun.xml.relax1.gz new file mode 100644 index 0000000000..a15aaf12fc Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/vasprun.xml.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/vasprun.xml.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/vasprun.xml.relax2.gz new file mode 100644 index 0000000000..c41fcf2a1c Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_0/outputs/vasprun.xml.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/inputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/inputs/INCAR.gz new file mode 100644 index 0000000000..07c1c2086c Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/inputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/inputs/KPOINTS.gz new file mode 100644 index 0000000000..fba980f8f9 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/inputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/inputs/POSCAR.gz new file mode 100644 index 0000000000..e7156ef27f Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/inputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..e83cfe7896 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/CONTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/CONTCAR.relax1.gz new file mode 100644 index 0000000000..15a37d4485 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/CONTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/CONTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/CONTCAR.relax2.gz new file mode 100644 index 0000000000..378c749f76 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/CONTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/INCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..92d656f3dc Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/INCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/INCAR.relax1.gz new file mode 100644 index 0000000000..8bd9ca1bc2 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/INCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/INCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/INCAR.relax2.gz new file mode 100644 index 0000000000..b037a33ba7 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/INCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..560e767aee Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/KPOINTS.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/KPOINTS.relax1.gz new file mode 100644 index 0000000000..0f601da355 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/KPOINTS.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/KPOINTS.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/KPOINTS.relax2.gz new file mode 100644 index 0000000000..4b754ce1e9 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/KPOINTS.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/OUTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/OUTCAR.relax1.gz new file mode 100644 index 0000000000..d03f908117 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/OUTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/OUTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/OUTCAR.relax2.gz new file mode 100644 index 0000000000..39846086a8 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/OUTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/POSCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..a916a02e37 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/POSCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/POSCAR.relax1.gz new file mode 100644 index 0000000000..9680392d9b Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/POSCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/POSCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/POSCAR.relax2.gz new file mode 100644 index 0000000000..041aac96ea Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/POSCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..e83cfe7896 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/vasprun.xml.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/vasprun.xml.relax1.gz new file mode 100644 index 0000000000..a38396b701 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/vasprun.xml.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/vasprun.xml.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/vasprun.xml.relax2.gz new file mode 100644 index 0000000000..0eb001b7af Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_1/outputs/vasprun.xml.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/inputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/inputs/INCAR.gz new file mode 100644 index 0000000000..062211b98d Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/inputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/inputs/KPOINTS.gz new file mode 100644 index 0000000000..ffbe1eb1a7 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/inputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/inputs/POSCAR.gz new file mode 100644 index 0000000000..9c3ceb1ab3 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/inputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..3eb1fa11dc Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/CONTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/CONTCAR.relax1.gz new file mode 100644 index 0000000000..5b7604e4df Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/CONTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/CONTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/CONTCAR.relax2.gz new file mode 100644 index 0000000000..c3f472f914 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/CONTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/INCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..ef203bb32c Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/INCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/INCAR.relax1.gz new file mode 100644 index 0000000000..d0b6a396cc Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/INCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/INCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/INCAR.relax2.gz new file mode 100644 index 0000000000..b50081380e Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/INCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..2b63fe3f55 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/KPOINTS.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/KPOINTS.relax1.gz new file mode 100644 index 0000000000..e677121dfa Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/KPOINTS.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/KPOINTS.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/KPOINTS.relax2.gz new file mode 100644 index 0000000000..deeea34031 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/KPOINTS.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/OUTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/OUTCAR.relax1.gz new file mode 100644 index 0000000000..22b90ce997 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/OUTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/OUTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/OUTCAR.relax2.gz new file mode 100644 index 0000000000..8511d9071a Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/OUTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/POSCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..cf92f39f0c Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/POSCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/POSCAR.relax1.gz new file mode 100644 index 0000000000..03d31f862e Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/POSCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/POSCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/POSCAR.relax2.gz new file mode 100644 index 0000000000..7043056b11 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/POSCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..3eb1fa11dc Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/vasprun.xml.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/vasprun.xml.relax1.gz new file mode 100644 index 0000000000..df2e0c2d81 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/vasprun.xml.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/vasprun.xml.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/vasprun.xml.relax2.gz new file mode 100644 index 0000000000..f995367fd1 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_endpoint_3/outputs/vasprun.xml.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/inputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/inputs/INCAR.gz new file mode 100644 index 0000000000..96f55082d4 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/inputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/inputs/KPOINTS.gz new file mode 100644 index 0000000000..27e839640d Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/inputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/inputs/POSCAR.gz new file mode 100644 index 0000000000..c57e4ea3d3 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/inputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..05c084add1 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/CONTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/CONTCAR.relax1.gz new file mode 100644 index 0000000000..3ddbf87a06 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/CONTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/CONTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/CONTCAR.relax2.gz new file mode 100644 index 0000000000..8bcb8caf46 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/CONTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/INCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..29b1f3a307 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/INCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/INCAR.relax1.gz new file mode 100644 index 0000000000..7c7e74326d Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/INCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/INCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/INCAR.relax2.gz new file mode 100644 index 0000000000..c014f8af28 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/INCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..3628741f96 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/KPOINTS.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/KPOINTS.relax1.gz new file mode 100644 index 0000000000..eae306bddc Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/KPOINTS.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/KPOINTS.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/KPOINTS.relax2.gz new file mode 100644 index 0000000000..32acd14cdb Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/KPOINTS.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/OUTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/OUTCAR.relax1.gz new file mode 100644 index 0000000000..a0e270e39e Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/OUTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/OUTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/OUTCAR.relax2.gz new file mode 100644 index 0000000000..101f8766e2 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/OUTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/POSCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..46824bccc6 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/POSCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/POSCAR.relax1.gz new file mode 100644 index 0000000000..cb19c24ecc Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/POSCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/POSCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/POSCAR.relax2.gz new file mode 100644 index 0000000000..ffa52712d5 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/POSCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..05c084add1 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/vasprun.xml.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/vasprun.xml.relax1.gz new file mode 100644 index 0000000000..56584178f0 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/vasprun.xml.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/vasprun.xml.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/vasprun.xml.relax2.gz new file mode 100644 index 0000000000..54efec99d3 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_1/outputs/vasprun.xml.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/inputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/inputs/INCAR.gz new file mode 100644 index 0000000000..33ab6b84c2 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/inputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/inputs/KPOINTS.gz new file mode 100644 index 0000000000..8e48758c61 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/inputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/inputs/POSCAR.gz new file mode 100644 index 0000000000..1982e00c41 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/inputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..6818c36440 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/CONTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/CONTCAR.relax1.gz new file mode 100644 index 0000000000..3de33bf896 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/CONTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/CONTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/CONTCAR.relax2.gz new file mode 100644 index 0000000000..2902158d18 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/CONTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/INCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..ba356ae7a0 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/INCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/INCAR.relax1.gz new file mode 100644 index 0000000000..d678b9713c Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/INCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/INCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/INCAR.relax2.gz new file mode 100644 index 0000000000..b8468b1328 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/INCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..7916c4fdb9 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/KPOINTS.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/KPOINTS.relax1.gz new file mode 100644 index 0000000000..3fb179dd97 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/KPOINTS.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/KPOINTS.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/KPOINTS.relax2.gz new file mode 100644 index 0000000000..75dc82d139 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/KPOINTS.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/OUTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/OUTCAR.relax1.gz new file mode 100644 index 0000000000..f00e92b0bf Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/OUTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/OUTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/OUTCAR.relax2.gz new file mode 100644 index 0000000000..862159a521 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/OUTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/POSCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..c941b3ef0c Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/POSCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/POSCAR.relax1.gz new file mode 100644 index 0000000000..41750ec1b4 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/POSCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/POSCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/POSCAR.relax2.gz new file mode 100644 index 0000000000..3d91ace3bd Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/POSCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..6818c36440 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/vasprun.xml.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/vasprun.xml.relax1.gz new file mode 100644 index 0000000000..6d8bba94fb Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/vasprun.xml.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/vasprun.xml.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/vasprun.xml.relax2.gz new file mode 100644 index 0000000000..dcff22afe7 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_2/outputs/vasprun.xml.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/inputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/inputs/INCAR.gz new file mode 100644 index 0000000000..c5331e8766 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/inputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/inputs/KPOINTS.gz new file mode 100644 index 0000000000..398fbb323f Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/inputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/inputs/POSCAR.gz new file mode 100644 index 0000000000..d076894424 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/inputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..beff24ea54 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/CONTCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/CONTCAR.gz new file mode 100644 index 0000000000..e2d265861a Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/CONTCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/INCAR.gz new file mode 100644 index 0000000000..c5331e8766 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/INCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..15bdc5d5cd Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/KPOINTS.gz new file mode 100644 index 0000000000..398fbb323f Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..fbf548b558 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/OUTCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/OUTCAR.gz new file mode 100644 index 0000000000..b69aee28b9 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/OUTCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/POSCAR.gz new file mode 100644 index 0000000000..d076894424 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/POSCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..fb2bb6c841 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..beff24ea54 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/vasprun.xml.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/vasprun.xml.gz new file mode 100644 index 0000000000..e43de73675 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_3/outputs/vasprun.xml.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/inputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/inputs/INCAR.gz new file mode 100644 index 0000000000..8c73248e28 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/inputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/inputs/KPOINTS.gz new file mode 100644 index 0000000000..007e4c8098 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/inputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/inputs/POSCAR.gz new file mode 100644 index 0000000000..c4d546b391 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/inputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..6cfb1a43c4 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/CONTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/CONTCAR.relax1.gz new file mode 100644 index 0000000000..0c9a51546b Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/CONTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/CONTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/CONTCAR.relax2.gz new file mode 100644 index 0000000000..3080df8219 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/CONTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/INCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..6dc0670422 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/INCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/INCAR.relax1.gz new file mode 100644 index 0000000000..a384745cec Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/INCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/INCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/INCAR.relax2.gz new file mode 100644 index 0000000000..837b033329 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/INCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..4fed2d1725 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/KPOINTS.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/KPOINTS.relax1.gz new file mode 100644 index 0000000000..a508703aec Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/KPOINTS.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/KPOINTS.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/KPOINTS.relax2.gz new file mode 100644 index 0000000000..29c15cae1b Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/KPOINTS.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/OUTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/OUTCAR.relax1.gz new file mode 100644 index 0000000000..f9c93f5cbb Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/OUTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/OUTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/OUTCAR.relax2.gz new file mode 100644 index 0000000000..fa4850c848 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/OUTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/POSCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..8fdceefcb7 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/POSCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/POSCAR.relax1.gz new file mode 100644 index 0000000000..4c983eb644 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/POSCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/POSCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/POSCAR.relax2.gz new file mode 100644 index 0000000000..d54ce41ac6 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/POSCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..6cfb1a43c4 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/vasprun.xml.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/vasprun.xml.relax1.gz new file mode 100644 index 0000000000..950b1a9a87 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/vasprun.xml.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/vasprun.xml.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/vasprun.xml.relax2.gz new file mode 100644 index 0000000000..d71f106572 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_4/outputs/vasprun.xml.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/inputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/inputs/INCAR.gz new file mode 100644 index 0000000000..c418005a9d Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/inputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/inputs/KPOINTS.gz new file mode 100644 index 0000000000..e316c431a5 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/inputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/inputs/POSCAR.gz new file mode 100644 index 0000000000..74011f80d9 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/inputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..beff24ea54 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/CONTCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/CONTCAR.gz new file mode 100644 index 0000000000..7b068e9ff1 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/CONTCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/INCAR.gz new file mode 100644 index 0000000000..c418005a9d Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/INCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..369e0056ac Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/KPOINTS.gz new file mode 100644 index 0000000000..e316c431a5 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..b648550870 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/OUTCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/OUTCAR.gz new file mode 100644 index 0000000000..8600fe53c5 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/OUTCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/POSCAR.gz new file mode 100644 index 0000000000..74011f80d9 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/POSCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..a791cdfd30 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..beff24ea54 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/vasprun.xml.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/vasprun.xml.gz new file mode 100644 index 0000000000..0e5e2dce2a Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+0_image_5/outputs/vasprun.xml.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/inputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/inputs/INCAR.gz new file mode 100644 index 0000000000..8c83412e4e Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/inputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/inputs/KPOINTS.gz new file mode 100644 index 0000000000..ab69d7cc8e Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/inputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/inputs/POSCAR.gz new file mode 100644 index 0000000000..98f43ac4c4 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/inputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..3c5f6a66de Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/CONTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/CONTCAR.relax1.gz new file mode 100644 index 0000000000..10fec04f8e Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/CONTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/CONTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/CONTCAR.relax2.gz new file mode 100644 index 0000000000..d0058cf511 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/CONTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/INCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..fa674c97dd Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/INCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/INCAR.relax1.gz new file mode 100644 index 0000000000..3b3565a834 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/INCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/INCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/INCAR.relax2.gz new file mode 100644 index 0000000000..1e7548a5b8 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/INCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..44c6bc0888 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/KPOINTS.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/KPOINTS.relax1.gz new file mode 100644 index 0000000000..9e7ad923ba Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/KPOINTS.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/KPOINTS.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/KPOINTS.relax2.gz new file mode 100644 index 0000000000..500b0e8dfd Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/KPOINTS.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/OUTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/OUTCAR.relax1.gz new file mode 100644 index 0000000000..d203025ca2 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/OUTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/OUTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/OUTCAR.relax2.gz new file mode 100644 index 0000000000..6f68e6ddd6 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/OUTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/POSCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..a895d45005 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/POSCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/POSCAR.relax1.gz new file mode 100644 index 0000000000..14f9c087c6 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/POSCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/POSCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/POSCAR.relax2.gz new file mode 100644 index 0000000000..965c794da9 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/POSCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..3c5f6a66de Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/vasprun.xml.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/vasprun.xml.relax1.gz new file mode 100644 index 0000000000..30de695d72 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/vasprun.xml.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/vasprun.xml.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/vasprun.xml.relax2.gz new file mode 100644 index 0000000000..9f9853788b Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_1/outputs/vasprun.xml.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/inputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/inputs/INCAR.gz new file mode 100644 index 0000000000..265429e15c Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/inputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/inputs/KPOINTS.gz new file mode 100644 index 0000000000..7f86fcfbdd Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/inputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/inputs/POSCAR.gz new file mode 100644 index 0000000000..947a27a7b8 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/inputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..beff24ea54 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/CONTCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/CONTCAR.gz new file mode 100644 index 0000000000..955ed48dd7 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/CONTCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/INCAR.gz new file mode 100644 index 0000000000..265429e15c Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/INCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..18d68f994b Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/KPOINTS.gz new file mode 100644 index 0000000000..7f86fcfbdd Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..39d181ab63 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/OUTCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/OUTCAR.gz new file mode 100644 index 0000000000..bc9f630dcd Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/OUTCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/POSCAR.gz new file mode 100644 index 0000000000..947a27a7b8 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/POSCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..5941176a78 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..beff24ea54 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/vasprun.xml.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/vasprun.xml.gz new file mode 100644 index 0000000000..c8abfd3172 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_2/outputs/vasprun.xml.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/inputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/inputs/INCAR.gz new file mode 100644 index 0000000000..527c5f9752 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/inputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/inputs/KPOINTS.gz new file mode 100644 index 0000000000..ab0d0811ac Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/inputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/inputs/POSCAR.gz new file mode 100644 index 0000000000..8153975ec4 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/inputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..17df3df301 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/CONTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/CONTCAR.relax1.gz new file mode 100644 index 0000000000..d83664a42e Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/CONTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/CONTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/CONTCAR.relax2.gz new file mode 100644 index 0000000000..54c3d92459 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/CONTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/INCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..710822fe8a Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/INCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/INCAR.relax1.gz new file mode 100644 index 0000000000..9579db122b Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/INCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/INCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/INCAR.relax2.gz new file mode 100644 index 0000000000..27bb0f13f8 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/INCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..1f3beada31 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/KPOINTS.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/KPOINTS.relax1.gz new file mode 100644 index 0000000000..7f94ee98c3 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/KPOINTS.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/KPOINTS.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/KPOINTS.relax2.gz new file mode 100644 index 0000000000..ab3b9a9a74 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/KPOINTS.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/OUTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/OUTCAR.relax1.gz new file mode 100644 index 0000000000..ad9327ca2c Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/OUTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/OUTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/OUTCAR.relax2.gz new file mode 100644 index 0000000000..14bb74edbe Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/OUTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/POSCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..1d8ec3883c Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/POSCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/POSCAR.relax1.gz new file mode 100644 index 0000000000..9ce5491bba Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/POSCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/POSCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/POSCAR.relax2.gz new file mode 100644 index 0000000000..e6db256be6 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/POSCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..17df3df301 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/vasprun.xml.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/vasprun.xml.relax1.gz new file mode 100644 index 0000000000..9f1e20ce99 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/vasprun.xml.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/vasprun.xml.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/vasprun.xml.relax2.gz new file mode 100644 index 0000000000..25ec6d85a0 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_3/outputs/vasprun.xml.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/inputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/inputs/INCAR.gz new file mode 100644 index 0000000000..bc6720779b Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/inputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/inputs/KPOINTS.gz new file mode 100644 index 0000000000..946aee9214 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/inputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/inputs/POSCAR.gz new file mode 100644 index 0000000000..a1726a7308 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/inputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..c421bd2d27 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/CONTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/CONTCAR.relax1.gz new file mode 100644 index 0000000000..ef90964e64 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/CONTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/CONTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/CONTCAR.relax2.gz new file mode 100644 index 0000000000..508f4ef529 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/CONTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/INCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..7b974a64cc Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/INCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/INCAR.relax1.gz new file mode 100644 index 0000000000..4c0c9b22e4 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/INCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/INCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/INCAR.relax2.gz new file mode 100644 index 0000000000..07a0f66a49 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/INCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..0267b8180c Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/KPOINTS.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/KPOINTS.relax1.gz new file mode 100644 index 0000000000..8dd360ebc5 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/KPOINTS.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/KPOINTS.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/KPOINTS.relax2.gz new file mode 100644 index 0000000000..94e4203b76 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/KPOINTS.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/OUTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/OUTCAR.relax1.gz new file mode 100644 index 0000000000..ad6f9dc07b Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/OUTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/OUTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/OUTCAR.relax2.gz new file mode 100644 index 0000000000..1e9c15b6ed Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/OUTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/POSCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..a13e0ed62c Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/POSCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/POSCAR.relax1.gz new file mode 100644 index 0000000000..c3387a9dcb Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/POSCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/POSCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/POSCAR.relax2.gz new file mode 100644 index 0000000000..a05525a792 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/POSCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..c421bd2d27 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/vasprun.xml.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/vasprun.xml.relax1.gz new file mode 100644 index 0000000000..1d837c58ec Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/vasprun.xml.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/vasprun.xml.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/vasprun.xml.relax2.gz new file mode 100644 index 0000000000..2387cd7418 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_4/outputs/vasprun.xml.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/inputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/inputs/INCAR.gz new file mode 100644 index 0000000000..abb5dee043 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/inputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/inputs/KPOINTS.gz new file mode 100644 index 0000000000..4d6cc3137b Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/inputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/inputs/POSCAR.gz new file mode 100644 index 0000000000..a792973bca Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/inputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..ae519e3601 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/CONTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/CONTCAR.relax1.gz new file mode 100644 index 0000000000..1a89d6626c Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/CONTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/CONTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/CONTCAR.relax2.gz new file mode 100644 index 0000000000..7535724e16 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/CONTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/INCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..6d6fda96ba Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/INCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/INCAR.relax1.gz new file mode 100644 index 0000000000..8d0ee9d3f1 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/INCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/INCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/INCAR.relax2.gz new file mode 100644 index 0000000000..ce333425a7 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/INCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..821d6e542c Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/KPOINTS.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/KPOINTS.relax1.gz new file mode 100644 index 0000000000..978d5d7884 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/KPOINTS.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/KPOINTS.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/KPOINTS.relax2.gz new file mode 100644 index 0000000000..1c6d8a4757 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/KPOINTS.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/OUTCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/OUTCAR.relax1.gz new file mode 100644 index 0000000000..f65f388e6f Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/OUTCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/OUTCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/OUTCAR.relax2.gz new file mode 100644 index 0000000000..4e2b77987b Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/OUTCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/POSCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..f98dabd605 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/POSCAR.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/POSCAR.relax1.gz new file mode 100644 index 0000000000..2be811619e Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/POSCAR.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/POSCAR.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/POSCAR.relax2.gz new file mode 100644 index 0000000000..72754caba4 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/POSCAR.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..ae519e3601 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/vasprun.xml.relax1.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/vasprun.xml.relax1.gz new file mode 100644 index 0000000000..cec3faa7d7 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/vasprun.xml.relax1.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/vasprun.xml.relax2.gz b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/vasprun.xml.relax2.gz new file mode 100644 index 0000000000..bdc6e9aaac Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/ApproxNEB_image_relax_hop_3+1_image_5/outputs/vasprun.xml.relax2.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/approx_neb_input.json.gz b/tests/test_data/vasp/ApproxNEB/approx_neb_input.json.gz new file mode 100644 index 0000000000..61be8cd743 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/approx_neb_input.json.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/collate_results.json.gz b/tests/test_data/vasp/ApproxNEB/collate_results.json.gz new file mode 100644 index 0000000000..6ff265efb0 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/collate_results.json.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/get_endpoints_and_relax.json.gz b/tests/test_data/vasp/ApproxNEB/get_endpoints_and_relax.json.gz new file mode 100644 index 0000000000..6be82cc04b Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/get_endpoints_and_relax.json.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/get_images_and_relax.json.gz b/tests/test_data/vasp/ApproxNEB/get_images_and_relax.json.gz new file mode 100644 index 0000000000..f51c749147 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/get_images_and_relax.json.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/inputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/inputs/INCAR.gz new file mode 100644 index 0000000000..2cdbe0a7df Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/inputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/inputs/KPOINTS.gz new file mode 100644 index 0000000000..748bd2eb3b Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/inputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/inputs/POSCAR.gz new file mode 100644 index 0000000000..1d7eeac7f0 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/inputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..e177178445 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/CONTCAR.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/CONTCAR.gz new file mode 100644 index 0000000000..78b55b9a54 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/CONTCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/INCAR.gz new file mode 100644 index 0000000000..2cdbe0a7df Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/INCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..55da51d9cf Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/KPOINTS.gz new file mode 100644 index 0000000000..748bd2eb3b Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..ffe47a4b9d Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/OUTCAR.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/OUTCAR.gz new file mode 100644 index 0000000000..b3d7550d13 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/OUTCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/POSCAR.gz new file mode 100644 index 0000000000..1d7eeac7f0 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/POSCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..8377544f6b Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..e177178445 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/vasprun.xml.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/vasprun.xml.gz new file mode 100644 index 0000000000..3a7830bb8d Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_1/outputs/vasprun.xml.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/inputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/inputs/INCAR.gz new file mode 100644 index 0000000000..006f4ba827 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/inputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/inputs/KPOINTS.gz new file mode 100644 index 0000000000..bb866d882f Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/inputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/inputs/POSCAR.gz new file mode 100644 index 0000000000..c2c5581f71 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/inputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..e177178445 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/CHGCAR.bz2 b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/CHGCAR.bz2 new file mode 100644 index 0000000000..a5eb26552c Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/CHGCAR.bz2 differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/CONTCAR.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/CONTCAR.gz new file mode 100644 index 0000000000..864eb4029d Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/CONTCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/INCAR.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/INCAR.gz new file mode 100644 index 0000000000..006f4ba827 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/INCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/INCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..0dc53912a6 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/KPOINTS.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/KPOINTS.gz new file mode 100644 index 0000000000..bb866d882f Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..f8a85c6591 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/OUTCAR.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/OUTCAR.gz new file mode 100644 index 0000000000..c7dd9b51a3 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/OUTCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/POSCAR.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/POSCAR.gz new file mode 100644 index 0000000000..c2c5581f71 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/POSCAR.orig.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..ab762288ea Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/POTCAR.spec.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..e177178445 Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/vasprun.xml.gz b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/vasprun.xml.gz new file mode 100644 index 0000000000..ee1ad0b1bc Binary files /dev/null and b/tests/test_data/vasp/ApproxNEB/host_structure_relax_2/outputs/vasprun.xml.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/inputs/00/POSCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/inputs/00/POSCAR.gz new file mode 100644 index 0000000000..4ab40f0fc4 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/inputs/00/POSCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/inputs/01/POSCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/inputs/01/POSCAR.gz new file mode 100644 index 0000000000..f8f82a19bb Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/inputs/01/POSCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/inputs/02/POSCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/inputs/02/POSCAR.gz new file mode 100644 index 0000000000..b9c960aad9 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/inputs/02/POSCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/inputs/03/POSCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/inputs/03/POSCAR.gz new file mode 100644 index 0000000000..7ef0fb053e Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/inputs/03/POSCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/inputs/04/POSCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/inputs/04/POSCAR.gz new file mode 100644 index 0000000000..7c8631433c Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/inputs/04/POSCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/inputs/INCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/inputs/INCAR.gz new file mode 100644 index 0000000000..d647c57002 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/inputs/KPOINTS.gz b/tests/test_data/vasp/Si_NEB/NEB/inputs/KPOINTS.gz new file mode 100644 index 0000000000..e5b7094325 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/inputs/POSCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/inputs/POSCAR.gz new file mode 100644 index 0000000000..8ecc79297a Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/inputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_NEB/NEB/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..a8a129ff9a Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/00/POSCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/00/POSCAR.gz new file mode 100644 index 0000000000..4ab40f0fc4 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/00/POSCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/00/POSCAR.orig.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/00/POSCAR.orig.gz new file mode 100644 index 0000000000..7edbfda88d Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/00/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/01/CONTCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/01/CONTCAR.gz new file mode 100644 index 0000000000..ef359caf5a Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/01/CONTCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/01/OUTCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/01/OUTCAR.gz new file mode 100644 index 0000000000..8de1073828 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/01/OUTCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/01/POSCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/01/POSCAR.gz new file mode 100644 index 0000000000..f8f82a19bb Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/01/POSCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/01/POSCAR.orig.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/01/POSCAR.orig.gz new file mode 100644 index 0000000000..272ba20a9a Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/01/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/01/vasprun.xml.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/01/vasprun.xml.gz new file mode 100644 index 0000000000..8be6f3d499 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/01/vasprun.xml.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/02/CONTCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/02/CONTCAR.gz new file mode 100644 index 0000000000..0b1f423e42 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/02/CONTCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/02/OUTCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/02/OUTCAR.gz new file mode 100644 index 0000000000..763a2650d8 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/02/OUTCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/02/POSCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/02/POSCAR.gz new file mode 100644 index 0000000000..b9c960aad9 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/02/POSCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/02/POSCAR.orig.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/02/POSCAR.orig.gz new file mode 100644 index 0000000000..6ee60f157a Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/02/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/02/vasprun.xml.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/02/vasprun.xml.gz new file mode 100644 index 0000000000..d77b6ce40a Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/02/vasprun.xml.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/03/CONTCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/03/CONTCAR.gz new file mode 100644 index 0000000000..792e0ca44d Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/03/CONTCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/03/OUTCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/03/OUTCAR.gz new file mode 100644 index 0000000000..0be3479699 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/03/OUTCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/03/POSCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/03/POSCAR.gz new file mode 100644 index 0000000000..7ef0fb053e Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/03/POSCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/03/POSCAR.orig.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/03/POSCAR.orig.gz new file mode 100644 index 0000000000..cbc3638d22 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/03/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/03/vasprun.xml.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/03/vasprun.xml.gz new file mode 100644 index 0000000000..59f3f8ec5f Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/03/vasprun.xml.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/04/POSCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/04/POSCAR.gz new file mode 100644 index 0000000000..7c8631433c Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/04/POSCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/04/POSCAR.orig.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/04/POSCAR.orig.gz new file mode 100644 index 0000000000..9afed0ffec Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/04/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/INCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/INCAR.gz new file mode 100644 index 0000000000..d647c57002 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/INCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/INCAR.orig.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..63463501a9 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/KPOINTS.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/KPOINTS.gz new file mode 100644 index 0000000000..e5b7094325 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..b5df1be460 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/POSCAR.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/POSCAR.gz new file mode 100644 index 0000000000..8ecc79297a Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/NEB/outputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_NEB/NEB/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..a8a129ff9a Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/NEB/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/Si_NEB/get_images_from_endpoints.json.gz b/tests/test_data/vasp/Si_NEB/get_images_from_endpoints.json.gz new file mode 100644 index 0000000000..f3267c6830 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/get_images_from_endpoints.json.gz differ diff --git a/tests/test_data/vasp/Si_NEB/neb_task_doc.json.gz b/tests/test_data/vasp/Si_NEB/neb_task_doc.json.gz new file mode 100644 index 0000000000..1b230d7ffb Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/neb_task_doc.json.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_1/inputs/INCAR.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/inputs/INCAR.gz new file mode 100644 index 0000000000..9a33d3dc85 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_1/inputs/KPOINTS.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/inputs/KPOINTS.gz new file mode 100644 index 0000000000..bcc6fb1f45 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_1/inputs/POSCAR.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/inputs/POSCAR.gz new file mode 100644 index 0000000000..c5aaec545f Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_1/inputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..a8a129ff9a Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/CONTCAR.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/CONTCAR.gz new file mode 100644 index 0000000000..ec69d42cdc Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/CONTCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/INCAR.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/INCAR.gz new file mode 100644 index 0000000000..9a33d3dc85 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/INCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/INCAR.orig.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..c00c896861 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/KPOINTS.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/KPOINTS.gz new file mode 100644 index 0000000000..bcc6fb1f45 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..28c62226a0 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/OUTCAR.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/OUTCAR.gz new file mode 100644 index 0000000000..65c9f23fc7 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/OUTCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/POSCAR.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/POSCAR.gz new file mode 100644 index 0000000000..c5aaec545f Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..6e635ae485 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..a8a129ff9a Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/vasprun.xml.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/vasprun.xml.gz new file mode 100644 index 0000000000..9f76213a64 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_1/outputs/vasprun.xml.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_2/inputs/INCAR.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/inputs/INCAR.gz new file mode 100644 index 0000000000..f8adf01bd2 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/inputs/INCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_2/inputs/KPOINTS.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/inputs/KPOINTS.gz new file mode 100644 index 0000000000..6c85099de1 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/inputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_2/inputs/POSCAR.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/inputs/POSCAR.gz new file mode 100644 index 0000000000..77a65e40e6 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/inputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_2/inputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/inputs/POTCAR.spec.gz new file mode 100644 index 0000000000..a8a129ff9a Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/inputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/CONTCAR.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/CONTCAR.gz new file mode 100644 index 0000000000..d4b6556e17 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/CONTCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/INCAR.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/INCAR.gz new file mode 100644 index 0000000000..f8adf01bd2 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/INCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/INCAR.orig.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/INCAR.orig.gz new file mode 100644 index 0000000000..aefd679ae1 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/INCAR.orig.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/KPOINTS.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/KPOINTS.gz new file mode 100644 index 0000000000..6c85099de1 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/KPOINTS.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/KPOINTS.orig.gz new file mode 100644 index 0000000000..bdc540b115 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/KPOINTS.orig.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/OUTCAR.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/OUTCAR.gz new file mode 100644 index 0000000000..6a73edce13 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/OUTCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/POSCAR.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/POSCAR.gz new file mode 100644 index 0000000000..77a65e40e6 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/POSCAR.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/POSCAR.orig.gz new file mode 100644 index 0000000000..ff848e2ec0 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/POSCAR.orig.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/POTCAR.spec.gz new file mode 100644 index 0000000000..a8a129ff9a Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/POTCAR.spec.gz differ diff --git a/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/vasprun.xml.gz b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/vasprun.xml.gz new file mode 100644 index 0000000000..e0485e1438 Binary files /dev/null and b/tests/test_data/vasp/Si_NEB/relax_endpoint_2/outputs/vasprun.xml.gz differ diff --git a/tests/vasp/conftest.py b/tests/vasp/conftest.py index a52f67ec1e..ca0366d79d 100644 --- a/tests/vasp/conftest.py +++ b/tests/vasp/conftest.py @@ -2,7 +2,7 @@ import logging from pathlib import Path -from typing import TYPE_CHECKING, Any, Final +from typing import TYPE_CHECKING, Any import pytest from monty.os.path import zpath as monty_zpath @@ -16,10 +16,6 @@ logger = logging.getLogger("atomate2") -_VFILES: Final = ("incar", "kpoints", "potcar", "poscar") -_REF_PATHS: dict[str, str | Path] = {} -_FAKE_RUN_VASP_KWARGS: dict[str, dict] = {} - def zpath(path: str | Path) -> Path: return Path(monty_zpath(str(path))) diff --git a/tests/vasp/flows/test_approx_neb.py b/tests/vasp/flows/test_approx_neb.py new file mode 100644 index 0000000000..a415c3d328 --- /dev/null +++ b/tests/vasp/flows/test_approx_neb.py @@ -0,0 +1,146 @@ +"""Test ApproxNEB workflow.""" + +from pathlib import Path + +import pytest +from emmet.core.neb import NebPathwayResult, NebResult +from jobflow import run_locally +from monty.serialization import loadfn +from pymatgen.core import Structure + +from atomate2.utils.testing.common import get_job_uuid_name_map +from atomate2.vasp.flows.approx_neb import ApproxNebMaker + + +def test_approx_neb_flow(mock_vasp, clean_dir, vasp_test_dir): + base_dir = Path(vasp_test_dir) / "ApproxNEB" + flow_input = loadfn(base_dir / "approx_neb_input.json.gz") + + flow = ApproxNebMaker().make( + *[ + flow_input[k] + for k in ( + "host_structure", + "working_ion", + "inserted_coords_dict", + "inserted_coords_combo", + ) + ] + ) + + ref_paths = { + job_name: f"ApproxNEB/{job_name.replace(' ', '_')}" + for job_name in [ + "host structure relax 1", + "host structure relax 2", + "ApproxNEB image relax endpoint 0", + "ApproxNEB image relax endpoint 1", + "ApproxNEB image relax endpoint 3", + "ApproxNEB image relax hop 3+0 image 1", + "ApproxNEB image relax hop 3+0 image 2", + "ApproxNEB image relax hop 3+0 image 3", + "ApproxNEB image relax hop 3+0 image 4", + "ApproxNEB image relax hop 3+0 image 5", + "ApproxNEB image relax hop 3+1 image 1", + "ApproxNEB image relax hop 3+1 image 2", + "ApproxNEB image relax hop 3+1 image 3", + "ApproxNEB image relax hop 3+1 image 4", + "ApproxNEB image relax hop 3+1 image 5", + ] + } + + fake_run_vasp_kwargs = { + key: { + "incar_exclude": ["IBRION", "ALGO", "MAGMOM"] + } # updated this to generate flow test data more quickly + for key in ref_paths + } + + # POSCAR generation for images pretty sensitive to CHGCAR + for key in [k for k in fake_run_vasp_kwargs if "relax hop" in k]: + fake_run_vasp_kwargs[key]["check_inputs"] = ["incar", "kpoints", "potcar"] + + mock_vasp(ref_paths, fake_run_vasp_kwargs) + # Do not ensure success here as the following jobs failed, but the flow should not: + # ApproxNEB image relax hop 3+0 image 3 + # ApproxNEB image relax hop 3+0 image 5 + # ApproxNEB image relax hop 3+1 image 2 + responses = run_locally(flow, create_folders=True, ensure_success=False) + output = { + job_name: responses[uuid][1].output + for uuid, job_name in get_job_uuid_name_map(flow).items() + } + + assert len(output["collate_results"].hops) == 2 + + assert all( + len(getattr(output["collate_results"].hops["3+0"], k)) + == 5 # two failed image calcs + for k in ("energies", "images") + ) + assert all( + len(getattr(output["collate_results"].hops["3+1"], k)) + == 6 # one failed image calc + for k in ("energies", "images") + ) + + assert all( + isinstance(image, Structure) + for hop in output["collate_results"].hops.values() + for image in hop.images + ) + + assert isinstance(output["collate_results"], NebPathwayResult) + assert all( + isinstance(hop, NebResult) for hop in output["collate_results"].hops.values() + ) + assert all( + output["collate_results"].max_barriers[k] + == pytest.approx( + max( + output["collate_results"].forward_barriers[k], + output["collate_results"].forward_barriers[k], + ) + ) + for k in output["collate_results"].hops + ) + + ref_results = loadfn(base_dir / "collate_results.json.gz") + + assert all( + output["collate_results"].hops[k].energies[idx] == pytest.approx(energy) + for k, ref_hop in ref_results.hops.items() + for idx, energy in enumerate(ref_hop.energies) + ) + + assert all( + getattr(output["collate_results"], f"{direction}_barriers")[k] + == pytest.approx(getattr(ref_results, f"{direction}_barriers")[k]) + for direction in ("forward", "reverse") + for k in ref_results.hops + ) + + +def test_approx_neb_checks(clean_dir, vasp_test_dir): + base_dir = Path(vasp_test_dir) / "ApproxNEB" + flow_input = loadfn(base_dir / "approx_neb_input.json.gz") + + idxs = list(flow_input["inserted_coords_dict"]) + for idx in (0, -1): + flow_input["inserted_coords_dict"].pop(idxs[idx]) + + with pytest.raises( + ValueError, + match="Missing working ion insertion indices in `inserted_coords_dict`", + ): + ApproxNebMaker().make( + *[ + flow_input[k] + for k in ( + "host_structure", + "working_ion", + "inserted_coords_dict", + "inserted_coords_combo", + ) + ] + ) diff --git a/tests/vasp/jobs/test_neb.py b/tests/vasp/jobs/test_neb.py new file mode 100644 index 0000000000..ccd23a6de3 --- /dev/null +++ b/tests/vasp/jobs/test_neb.py @@ -0,0 +1,206 @@ +"""Test VASP NEB Flows""" + +from pathlib import Path + +import pytest +from emmet.core.neb import ( + BarrierAnalysis, + NebIntermediateImagesDoc, + NebMethod, + NebTaskDoc, +) +from jobflow import run_locally +from monty.serialization import loadfn +from pymatgen.core import Structure + +from atomate2.common.jobs.neb import _get_images_from_endpoints +from atomate2.vasp.jobs.core import RelaxMaker +from atomate2.vasp.jobs.neb import NebFromEndpointsMaker, NebFromImagesMaker +from atomate2.vasp.sets.core import RelaxSetGenerator + +expected_incar_tags_relax = [ + "ALGO", + "EDIFFG", + "ENAUG", + "ENCUT", + "GGA", + "IBRION", + "ISIF", + "ISMEAR", + "ISPIN", + "LAECHG", + "LASPH", + "LCHARG", + "LELF", + "LMIXTAU", + "LORBIT", + "LREAL", + "LVTOT", + "LWAVE", + "NELM", + "NSW", + "PREC", +] +expected_incar_tags_neb = [ + "IMAGES", + "IOPT", # VTST specific + "LCLIMB", # VTST specific + "POTIM", + "SPRING", + *expected_incar_tags_relax, +] + + +def test_neb_from_endpoints_maker(mock_vasp, clean_dir, vasp_test_dir): + """Test nearest-neighbor vacancy migration in Si supercell.""" + + num_images = 5 + intermed_images = num_images - 2 + base_neb_dir = Path(vasp_test_dir) / "Si_NEB" + + ref_paths = { + k: str(base_neb_dir / k.replace(" ", "_")) + for k in ("relax endpoint 1", "relax endpoint 2", "NEB") + } + + fake_run_vasp_kwargs = { + k: { + "incar_settings": expected_incar_tags_neb + if k == "NEB" + else expected_incar_tags_relax + } + for k in ref_paths + } + + mock_vasp(ref_paths, fake_run_vasp_kwargs) + + """ + # NB the endpoint structures were generated as follows: + + ```py + structure = si_structure.to_conventional() * (2, 2, 2) + + _, n_idx, _, d = structure.get_neighbor_list( + structure.lattice.a / 4, sites=[structure[0]] + ) + min_idx = np.argmin(d) + nn_idx = n_idx[min_idx] + + endpoints = [structure.copy() for _ in range(2)] + endpoints[0].remove_sites([0]) + endpoints[1].remove_sites([nn_idx]) + ``` + """ + endpoints = [ + Structure.from_file( + base_neb_dir / f"relax_endpoint_{idx + 1}" / "inputs" / "POSCAR.gz" + ) + for idx in range(2) + ] + + relax_maker = RelaxMaker( + input_set_generator=RelaxSetGenerator( + user_incar_settings={"ISIF": 2, "EDIFFG": -0.05} + ) + ) + + neb_job = NebFromEndpointsMaker( + endpoint_relax_maker=relax_maker, + ).make( + endpoints=endpoints, + num_images=intermed_images, + autosort_tol=0.5, + ) + + # ensure flow runs successfully + responses = run_locally(neb_job, create_folders=True, ensure_success=True) + output = {job.name: responses[job.uuid][1].output for job in neb_job.jobs} + + fixed_cell_vol = endpoints[0].volume + assert all( + output[f"relax endpoint {1 + idx}"].output.structure.volume + == pytest.approx(fixed_cell_vol) + for idx in range(2) + ) + + expected_images = loadfn(str(base_neb_dir / "get_images_from_endpoints.json.gz")) + assert len(output["get_images_from_endpoints"]) == num_images + assert ( + output["get_images_from_endpoints"][idx] == image + for idx, image in enumerate(expected_images) + ) + + assert isinstance(output["collect_neb_output"], NebTaskDoc) + expected_neb_result = NebTaskDoc( + **loadfn(str(base_neb_dir / "neb_task_doc.json.gz")) + ) + assert all( + output["collect_neb_output"].energies[i] == pytest.approx(energy) + for i, energy in enumerate(expected_neb_result.energies) + ) + + # endpoints + intermediate images + assert len(output["collect_neb_output"].images) == num_images + + # just intermediate images + assert len(output["NEB"].images) == intermed_images + + assert all( + getattr(output["collect_neb_output"], f"{direction}_barrier") + == pytest.approx(getattr(expected_neb_result, f"{direction}_barrier")) + for direction in ("forward", "reverse") + ) + + assert isinstance(output["collect_neb_output"].barrier_analysis, BarrierAnalysis) + assert set(output["collect_neb_output"].barrier_analysis.model_dump()) == { + "cubic_spline_pars", + "energies", + "forward_barrier", + "frame_index", + "reverse_barrier", + "ts_energy", + "ts_frame_index", + "ts_in_frames", + } + + +def test_neb_from_images_maker(mock_vasp, clean_dir, vasp_test_dir): + num_images = 5 + intermed_images = num_images - 2 + base_neb_dir = Path(vasp_test_dir) / "Si_NEB" + + endpoints = [ + Structure.from_file( + base_neb_dir / f"relax_endpoint_{1 + idx}/outputs/CONTCAR.gz" + ) + for idx in range(2) + ] + images = endpoints[0].interpolate( + endpoints[1], nimages=intermed_images + 1, autosort_tol=0.5 + ) + assert all( + images[idx] == image + for idx, image in enumerate( + _get_images_from_endpoints( + endpoints, num_images=intermed_images, autosort_tol=0.5 + ) + ) + ) + + ref_paths = {"NEB": str(base_neb_dir / "NEB")} + + mock_vasp(ref_paths, {"NEB": {"incar_settings": expected_incar_tags_neb}}) + neb_job = NebFromImagesMaker().make(images) + response = run_locally(neb_job, create_folders=True, ensure_success=True) + output = response[neb_job.uuid][1].output + + ref_neb_task = NebIntermediateImagesDoc.from_directory( + Path(ref_paths["NEB"]) / "outputs" + ) + assert isinstance(output, NebIntermediateImagesDoc) + assert len(output.images) == num_images - 2 + assert all( + output.energies[i] == pytest.approx(energy) + for i, energy in enumerate(ref_neb_task.energies) + ) + assert output.neb_method == NebMethod.CLIMBING_IMAGE