@@ -606,7 +606,12 @@ def _kabsch_rmsd(pa: np.ndarray, pb: np.ndarray) -> float:
606606 return float ("inf" )
607607
608608 E0 = np .sum (pa ** 2 ) + np .sum (pb ** 2 )
609- rmsd_sq = max (0.0 , E0 - 2.0 * (S [0 ] + S [1 ] + d * S [2 ])) / len (pa )
609+ # After the d < 0 guard we know d > 0 (proper rotation), so the
610+ # sign correction is +1 and all three singular values contribute.
611+ # Using `d * S[2]` (where d is a float ≈ 1.0 ± 1e-15) is technically
612+ # incorrect and introduces unnecessary floating-point bias; np.sum(S)
613+ # is exact.
614+ rmsd_sq = max (0.0 , E0 - 2.0 * np .sum (S )) / len (pa )
610615 return float (np .sqrt (rmsd_sq ))
611616
612617
@@ -2414,12 +2419,6 @@ def _make_executor() -> ProcessPoolExecutor:
24142419 self ._finalize_iteration (run_dir , task , "FAILED" , [], priority_log )
24152420 continue
24162421
2417- # Persist the exploration record only after confirming success
2418- self .explored_log .record (task .node_id , atom_i , atom_j , gamma_sign )
2419- # Call release() after record() so that is_submitted() returns
2420- # True throughout the entire [pop() → record() → release()] window
2421- self .queue .release ((task .node_id , tuple (task .afir_params )))
2422-
24232422 logger .info (
24242423 "Iter %06d: _run_autots returned %d profile director%s." ,
24252424 self ._iteration , len (profile_dirs ),
@@ -2428,6 +2427,17 @@ def _make_executor() -> ProcessPoolExecutor:
24282427 for pdir in profile_dirs :
24292428 self ._process_profile (pdir , run_dir )
24302429
2430+ # Persist the exploration record only after confirming success
2431+ # (profile processing complete). Placing record() before
2432+ # _process_profile() — the previous order — would mark the task
2433+ # as explored even when _process_profile raises (e.g. disk full
2434+ # in _persist_node_xyz), making it non-retryable on resume.
2435+ # Must mirror the parallel path in _process_single_result.
2436+ self .explored_log .record (task .node_id , atom_i , atom_j , gamma_sign )
2437+ # Call release() after record() so that is_submitted() returns
2438+ # True throughout the entire [pop() → record() → release()] window
2439+ self .queue .release ((task .node_id , tuple (task .afir_params )))
2440+
24312441 # Notify queue of updated graph (required by RCMCQueue)
24322442 if hasattr (self .queue , "set_graph" ):
24332443 self .queue .set_graph (self .graph )
@@ -2533,10 +2543,11 @@ def _new_executor() -> ProcessPoolExecutor:
25332543 # ── Inner helpers ──────────────────────────────────────────────────
25342544
25352545 _stop_logged = False
2546+ _max_iter_logged = False
25362547
25372548 def _should_stop () -> bool :
25382549 """True when the outer loop should not submit more tasks."""
2539- nonlocal _stop_logged
2550+ nonlocal _stop_logged , _max_iter_logged
25402551 if os .path .isfile (os .path .join (self .output_dir , "stop.txt" )):
25412552 if not _stop_logged :
25422553 logger .info (
@@ -2547,6 +2558,11 @@ def _should_stop() -> bool:
25472558 _stop_logged = True
25482559 return True
25492560 if self .max_iterations > 0 and self ._iteration >= self .max_iterations :
2561+ if not _max_iter_logged :
2562+ logger .info (
2563+ "Reached max_iterations (%d). Stopping." , self .max_iterations
2564+ )
2565+ _max_iter_logged = True
25502566 return True
25512567 return False
25522568
0 commit comments