Skip to content

Commit 9636b7c

Browse files
authored
Add files via upload
1 parent c226b56 commit 9636b7c

1 file changed

Lines changed: 33 additions & 35 deletions

File tree

multioptpy/Wrapper/mapper.py

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
from __future__ import annotations
1010

11-
import bisect
1211
import glob
1312
import heapq
1413
import json
@@ -642,15 +641,19 @@ def pop(self) -> ExplorationTask | None:
642641
643642
Also removes the task from ``_tasks`` so subclasses that iterate
644643
``_tasks`` see a consistent state.
644+
645+
Stale heap entries (tasks that exist in the heap but have already
646+
been removed from ``_tasks`` by a prior rebuild) are silently
647+
skipped so that only valid tasks are ever returned to the caller.
645648
"""
646-
if not self._heap:
647-
return None
648-
_, _, task = heapq.heappop(self._heap)
649-
try:
650-
self._tasks.remove(task) # O(n) but only called in base-class path
651-
except ValueError:
652-
pass
653-
return task
649+
while self._heap:
650+
_, _, task = heapq.heappop(self._heap)
651+
try:
652+
self._tasks.remove(task)
653+
return task
654+
except ValueError:
655+
continue # stale entry — skip and try the next one
656+
return None
654657

655658
def should_add(self, node: "EQNode", reference_energy: float, **kwargs) -> bool:
656659
"""Decide probabilistically whether to enqueue a node.
@@ -723,9 +726,13 @@ def refresh_priorities(self, ref_e: float | None) -> None:
723726
task.priority = self.compute_priority(task)
724727

725728
# Rebuild the heap from the updated _tasks list.
726-
self._heap = [(-t.priority, i, t) for i, t in enumerate(self._tasks)]
729+
# Use _push_counter-based indices to guarantee unique tie-breakers,
730+
# then advance _push_counter so that subsequent push() calls never
731+
# reuse a counter value (monotonic increase requirement).
732+
base = self._push_counter
733+
self._heap = [(-t.priority, base + i, t) for i, t in enumerate(self._tasks)]
727734
heapq.heapify(self._heap)
728-
self._push_counter = len(self._heap)
735+
self._push_counter = base + len(self._tasks)
729736

730737
def export_queue_status(self) -> list[dict]:
731738
return [
@@ -1551,8 +1558,6 @@ def _validate_mapper_settings(cls, config: dict) -> None:
15511558
("max_pairs", int, lambda v: v > 0, "must be a positive integer"),
15521559
("dist_lower_ang", (int, float), lambda v: v >= 0, "must be >= 0"),
15531560
("dist_upper_ang", (int, float), lambda v: v > 0, "must be > 0"),
1554-
("rcmc_temperature_K", (int, float), lambda v: v > 0, "must be > 0"),
1555-
("rcmc_reaction_time_s", (int, float), lambda v: v > 0, "must be > 0"),
15561561
("rng_seed", int, lambda v: v >= 0, "must be a non-negative integer"),
15571562
]
15581563

@@ -1581,18 +1586,18 @@ def _validate_mapper_settings(cls, config: dict) -> None:
15811586
f"string (got {type(ms['output_dir']).__name__})."
15821587
)
15831588

1584-
# ── exclude_nodes: optional, must be list[int] when present ──────
1585-
if "exclude_nodes" in ms:
1586-
en = ms["exclude_nodes"]
1589+
# ── excluded_node_ids: optional, must be list[int] when present ──────
1590+
if "excluded_node_ids" in ms:
1591+
en = ms["excluded_node_ids"]
15871592
if not isinstance(en, list):
15881593
raise ValueError(
1589-
f"Config validation failed: 'mapper_settings.exclude_nodes' must be "
1594+
f"Config validation failed: 'mapper_settings.excluded_node_ids' must be "
15901595
f"a list of integers (got {type(en).__name__})."
15911596
)
15921597
for idx, item in enumerate(en):
15931598
if not isinstance(item, int):
15941599
raise ValueError(
1595-
f"Config validation failed: 'mapper_settings.exclude_nodes[{idx}]' "
1600+
f"Config validation failed: 'mapper_settings.excluded_node_ids[{idx}]' "
15961601
f"must be an integer (got {type(item).__name__}: {item!r})."
15971602
)
15981603

@@ -2328,13 +2333,17 @@ def _process_profile(self, profile_dir: str, run_dir: str) -> None:
23282333
if not existing_edge.has_coords:
23292334
continue
23302335

2331-
# Energy pre-filter
2332-
energy_diff = abs(ts_energy - (existing_edge.ts_energy or 0.0))
2333-
if energy_diff >= self.energy_tolerance:
2334-
continue
2336+
# Energy pre-filter: skip RMSD computation when energies
2337+
# differ by more than the tolerance (fast path).
2338+
# When either energy is None the filter is skipped so that
2339+
# structures without parsed energies still undergo the
2340+
# geometric check.
2341+
if ts_energy is not None and existing_edge.ts_energy is not None:
2342+
energy_diff = abs(ts_energy - existing_edge.ts_energy)
2343+
if energy_diff >= self.energy_tolerance:
2344+
continue
23352345

2336-
2337-
# Geometric check via RMSD (evaluated only if topological check fails)
2346+
# Geometric check via RMSD
23382347
if self.checker.are_similar(
23392348
ts_sym, ts_coords,
23402349
existing_edge.symbols, existing_edge.coords,
@@ -2346,17 +2355,6 @@ def _process_profile(self, profile_dir: str, run_dir: str) -> None:
23462355
node_id_1, node_id_2,
23472356
)
23482357
return
2349-
2350-
# Topological check (order-independent node matching)
2351-
nodes_match = {node_id_1, node_id_2} == {existing_edge.node_id_1, existing_edge.node_id_2}
2352-
if nodes_match:
2353-
logger.info(
2354-
"Duplicate TS skipped (matches topological connection of TS%06d, "
2355-
"energy diff=%.6f Ha) EQ%d -- EQ%d",
2356-
existing_edge.edge_id, energy_diff, node_id_1, node_id_2
2357-
)
2358-
return # Not registered anywhere
2359-
23602358

23612359
# ── Step 4: unique TS — persist XYZ and register edge ─────────────
23622360
edge_id = self.graph.next_edge_id()

0 commit comments

Comments
 (0)