From 933c4a8c5bcb4df87ab538e8cded7c6b22fc0bad Mon Sep 17 00:00:00 2001 From: Roberta Pascazio Date: Tue, 16 Dec 2025 13:24:55 -0800 Subject: [PATCH 1/5] fix failure for missing ep in get_images_and_relax --- src/atomate2/common/jobs/approx_neb.py | 30 ++++++++++++++++---------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/atomate2/common/jobs/approx_neb.py b/src/atomate2/common/jobs/approx_neb.py index 92bf34ad09..252bd7e83b 100644 --- a/src/atomate2/common/jobs/approx_neb.py +++ b/src/atomate2/common/jobs/approx_neb.py @@ -10,6 +10,7 @@ from jobflow import Flow, Response, job from pymatgen.analysis.diffusion.neb.pathfinder import ChgcarPotential, NEBPathfinder from pymatgen.core import Element +import math if TYPE_CHECKING: from collections.abc import Callable, Sequence @@ -260,6 +261,7 @@ def get_images_and_relax( """ # 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 @@ -284,6 +286,7 @@ def get_images_and_relax( # all elements have an atomic radius in pymatgen min_hop_distance = atomic_radius + i = 0 for hop_idx, combo in enumerate(inserted_combo_list): ini_ind, fin_ind = combo.split("+") @@ -292,13 +295,14 @@ def get_images_and_relax( 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) + elif ( + math.isnan(min_hop_distance) #isinstance(min_hop_distance, float) + or (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) @@ -307,14 +311,18 @@ def get_images_and_relax( 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"] + try: + 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"] + except Exception as e: + skip_reasons.append(HopFailureReason.ENDPOINT) + continue # add selective dynamics to structure if selective_dynamics_scheme == "fix_two_atoms": From dbc0942f9f6a150ad2258ca02847092fb9cc9055 Mon Sep 17 00:00:00 2001 From: RobertaPascazio <74239295+RobertaPascazio@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:38:45 -0800 Subject: [PATCH 2/5] fix failure for missing ep in get_images_and_relax (updated) --- src/atomate2/common/jobs/approx_neb.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/atomate2/common/jobs/approx_neb.py b/src/atomate2/common/jobs/approx_neb.py index 252bd7e83b..cfb4b9358f 100644 --- a/src/atomate2/common/jobs/approx_neb.py +++ b/src/atomate2/common/jobs/approx_neb.py @@ -10,7 +10,6 @@ from jobflow import Flow, Response, job from pymatgen.analysis.diffusion.neb.pathfinder import ChgcarPotential, NEBPathfinder from pymatgen.core import Element -import math if TYPE_CHECKING: from collections.abc import Callable, Sequence @@ -286,7 +285,6 @@ def get_images_and_relax( # all elements have an atomic radius in pymatgen min_hop_distance = atomic_radius - i = 0 for hop_idx, combo in enumerate(inserted_combo_list): ini_ind, fin_ind = combo.split("+") From 0a75dfcdb10cd2546ef5c57eba2074d3f9659f1b Mon Sep 17 00:00:00 2001 From: RobertaPascazio Date: Sat, 3 Jan 2026 12:47:06 +0100 Subject: [PATCH 3/5] fixes for multiple working ions in host structure --- src/atomate2/common/jobs/approx_neb.py | 71 +++++++++++++++++--------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/src/atomate2/common/jobs/approx_neb.py b/src/atomate2/common/jobs/approx_neb.py index cfb4b9358f..23da88e7c0 100644 --- a/src/atomate2/common/jobs/approx_neb.py +++ b/src/atomate2/common/jobs/approx_neb.py @@ -2,6 +2,8 @@ from __future__ import annotations +import logging +from collections import defaultdict from typing import TYPE_CHECKING import numpy as np @@ -188,7 +190,7 @@ def collate_results( ) hop_dist[combo_name] = get_hop_distance_from_endpoints( - [ep_calc["structure"] for ep_calc in endpoint_calcs], working_ion + [ep_calc["structure"] for ep_calc in endpoint_calcs], working_ion, tol=0.3 ) return NebPathwayResult( @@ -294,13 +296,12 @@ def get_images_and_relax( # At least one endpoint calculation failed skip_reasons.append(HopFailureReason.ENDPOINT) elif ( - math.isnan(min_hop_distance) #isinstance(min_hop_distance, float) - or (isinstance(min_hop_distance, float) + isinstance(min_hop_distance, float) and get_hop_distance_from_endpoints( - [ep_structures[ini_ind], ep_structures[fin_ind]], working_ion + [ep_structures[ini_ind], ep_structures[fin_ind]], working_ion, tol=0.3 ) < min_hop_distance - )): + ): # The working ion hop distance is below the specified threshold skip_reasons.append(HopFailureReason.MIN_DIST) @@ -309,18 +310,18 @@ def get_images_and_relax( continue # potential place for uuid logic if depth first is desirable - try: - 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"] - except Exception as e: - skip_reasons.append(HopFailureReason.ENDPOINT) - continue + # try: + 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"] + # except Exception: + # skip_reasons.append(HopFailureReason.ENDPOINT) + # continue # add selective dynamics to structure if selective_dynamics_scheme == "fix_two_atoms": @@ -482,7 +483,7 @@ def get_working_ion_index( def get_hop_distance_from_endpoints( - endpoint_structures: Sequence[Structure], working_ion: CompositionLike + endpoint_structures: Sequence[Structure], working_ion: CompositionLike, tol: float ) -> float: """ Find the hop distance of a working ion from two endpoint structures. @@ -498,6 +499,8 @@ def get_hop_distance_from_endpoints( ------- float - the hop distance """ + logger = logging.getLogger(__name__) + working_ion_sites = [ [ site @@ -507,11 +510,29 @@ def get_hop_distance_from_endpoints( 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] - ) + sitea_coords = np.array([site.frac_coords for site in working_ion_sites[0]]) + siteb_coords = np.array([site.frac_coords for site in working_ion_sites[1]]) + + dist_matrix = [ + endpoint_structures[0].lattice.get_all_distances(sitea_coords, siteb_coords) + ] + site_mappings: dict[int, list[int]] = defaultdict(list) + unmapped_start_idxs = [] + for idx, row in enumerate(dist_matrix): + ind = np.where(row < tol)[0] + if len(ind) == 1: + site_mappings[idx].append(ind[0]) + else: + unmapped_start_idxs.append(idx) + + if len(unmapped_start_idxs) == 1: + unmapped_start_ind = unmapped_start_idxs[0] + else: + logger.debug("Too many working ions. Consider lowering the tolerance.") + return 10e-6 + + site_a, site_b = [sites[unmapped_start_ind] for sites in working_ion_sites[:2]] + return np.linalg.norm(site_a.coords - site_b.coords) @job @@ -564,7 +585,9 @@ def collate_images_single_hop( 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 + [ep_calc["structure"] for ep_calc in endpoint_calc_output], + working_ion, + tol=0.3, ) return NebResult( From f1b61604361774fa9d06651a5fb7f62324d16419 Mon Sep 17 00:00:00 2001 From: RobertaPascazio <74239295+RobertaPascazio@users.noreply.github.com> Date: Sat, 3 Jan 2026 13:00:48 +0100 Subject: [PATCH 4/5] Last cleanup --- src/atomate2/common/jobs/approx_neb.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/atomate2/common/jobs/approx_neb.py b/src/atomate2/common/jobs/approx_neb.py index 23da88e7c0..ab57a7c104 100644 --- a/src/atomate2/common/jobs/approx_neb.py +++ b/src/atomate2/common/jobs/approx_neb.py @@ -190,7 +190,7 @@ def collate_results( ) hop_dist[combo_name] = get_hop_distance_from_endpoints( - [ep_calc["structure"] for ep_calc in endpoint_calcs], working_ion, tol=0.3 + [ep_calc["structure"] for ep_calc in endpoint_calcs], working_ion, tol=tol ) return NebPathwayResult( @@ -218,6 +218,7 @@ def get_images_and_relax( relax_maker: Maker, selective_dynamics_scheme: Literal["fix_two_atoms"] | None = "fix_two_atoms", min_hop_distance: float | bool = True, + tol: float = 0.3 ) -> Response: """ Get and relax image input structures. @@ -298,7 +299,7 @@ def get_images_and_relax( elif ( isinstance(min_hop_distance, float) and get_hop_distance_from_endpoints( - [ep_structures[ini_ind], ep_structures[fin_ind]], working_ion, tol=0.3 + [ep_structures[ini_ind], ep_structures[fin_ind]], working_ion, tol=tol ) < min_hop_distance ): @@ -310,7 +311,6 @@ def get_images_and_relax( continue # potential place for uuid logic if depth first is desirable - # try: pathfinder_output = get_pathfinder_results( ep_structures[ini_ind], ep_structures[fin_ind], @@ -319,9 +319,6 @@ def get_images_and_relax( host_chgcar, ) images_list = pathfinder_output["images"] - # except Exception: - # skip_reasons.append(HopFailureReason.ENDPOINT) - # continue # add selective dynamics to structure if selective_dynamics_scheme == "fix_two_atoms": @@ -587,7 +584,7 @@ def collate_images_single_hop( hop_dist = get_hop_distance_from_endpoints( [ep_calc["structure"] for ep_calc in endpoint_calc_output], working_ion, - tol=0.3, + tol=tol, ) return NebResult( From 692de31a2d8d3d469e84fe399d77d2e33335de41 Mon Sep 17 00:00:00 2001 From: RobertaPascazio Date: Sat, 3 Jan 2026 13:14:03 +0100 Subject: [PATCH 5/5] ValueEnums dependence in `src/atomate2/ase/schemas.py` --- src/atomate2/ase/schemas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atomate2/ase/schemas.py b/src/atomate2/ase/schemas.py index cd79f3b684..6cdca66109 100644 --- a/src/atomate2/ase/schemas.py +++ b/src/atomate2/ase/schemas.py @@ -17,7 +17,7 @@ from emmet.core.structure import MoleculeMetadata, StructureMetadata from emmet.core.tasks import TaskState from emmet.core.trajectory import AtomTrajectory -from emmet.core.utils import ValueEnum +from emmet.core.types.enums import ValueEnum # emmet.core.utils from emmet.core.vasp.calculation import StoreTrajectoryOption from pydantic import BaseModel, Field from pymatgen.core import Molecule, Structure