@@ -617,31 +617,34 @@ def interpolate_addition(rxn: 'ARCReaction',
617617 if ts_xyz is not None :
618618 ts_xyzs .append (ts_xyz )
619619
620- # ----- Strategy 2: fragmentation fallback -----
621- if not ts_xyzs :
622- cut_lists = _find_split_bonds_by_fragmentation (uni_mol , multi_species )
623- for cut in cut_lists :
624- sb_key = frozenset (cut )
625- if sb_key in seen_split_sets :
620+ # ----- Strategy 2: fragmentation supplement -----
621+ # Always run fragmentation-based search in addition to the template path.
622+ # The template's r_label_map can mis-identify the primary fragmentation
623+ # (e.g. marking a C-H bond as the split instead of the C-O bond in
624+ # 1,3_Insertion_ROR), so fragmentation supplements those results.
625+ cut_lists = _find_split_bonds_by_fragmentation (uni_mol , multi_species )
626+ for cut in cut_lists :
627+ sb_key = frozenset (cut )
628+ if sb_key in seen_split_sets :
629+ continue
630+ seen_split_sets .add (sb_key )
631+ ts_xyz = _stretch_bond (uni_xyz , uni_mol , cut , cross_bonds = None ,
632+ weight = weight , label = f'rxn={ rxn .label } , frag-fallback' )
633+ if ts_xyz is not None :
634+ # If H atoms need to migrate between fragments to match
635+ # product compositions, partially displace them now.
636+ ts_xyz = _migrate_h_between_fragments (
637+ ts_xyz , uni_mol , cut , multi_species , weight )
638+ # Revalidate: the migration may have introduced collisions
639+ # or other geometry defects not present in the pre-migration guess.
640+ is_valid , reason = _validate_ts_guess (
641+ ts_xyz , set (), cut , uni_mol ,
642+ label = f'rxn={ rxn .label } , frag-fallback-post-migrate' )
643+ if not is_valid :
644+ logger .debug (f'Linear (rxn={ rxn .label } , frag-fallback): '
645+ f'post-migration guess rejected — { reason } .' )
626646 continue
627- seen_split_sets .add (sb_key )
628- ts_xyz = _stretch_bond (uni_xyz , uni_mol , cut , cross_bonds = None ,
629- weight = weight , label = f'rxn={ rxn .label } , frag-fallback' )
630- if ts_xyz is not None :
631- # If H atoms need to migrate between fragments to match
632- # product compositions, partially displace them now.
633- ts_xyz = _migrate_h_between_fragments (
634- ts_xyz , uni_mol , cut , multi_species , weight )
635- # Revalidate: the migration may have introduced collisions
636- # or other geometry defects not present in the pre-migration guess.
637- is_valid , reason = _validate_ts_guess (
638- ts_xyz , set (), cut , uni_mol ,
639- label = f'rxn={ rxn .label } , frag-fallback-post-migrate' )
640- if not is_valid :
641- logger .debug (f'Linear (rxn={ rxn .label } , frag-fallback): '
642- f'post-migration guess rejected — { reason } .' )
643- continue
644- ts_xyzs .append (ts_xyz )
647+ ts_xyzs .append (ts_xyz )
645648
646649 # Deduplicate near-identical guesses.
647650 unique : List [dict ] = []
@@ -931,16 +934,32 @@ def heavy_formula(f: Dict[str, int]) -> Dict[str, int]:
931934
932935 deficit_heavy_coords = ts_coords [deficit_heavy ]
933936
934- # Sort H atoms by min distance to any deficit-fragment heavy atom.
935- h_dists = []
937+ # Identify split-bond anchor atoms in this fragment. In
938+ # insertion/elimination reactions the migrating H should come from
939+ # a *non-anchor* heavy atom to create a proper TS ring (e.g. O on
940+ # one C of ethylene and H migrating from the other C).
941+ split_anchors_in_frag : Set [int ] = set ()
942+ for a , b in split_bonds :
943+ if a in fragments [s_fi ]:
944+ split_anchors_in_frag .add (a )
945+ if b in fragments [s_fi ]:
946+ split_anchors_in_frag .add (b )
947+
948+ # Sort H atoms by: (1) prefer H not bonded to a split-bond anchor,
949+ # (2) then by min distance to any deficit-fragment heavy atom.
950+ h_dists : List [Tuple [int , float , int , bool ]] = []
936951 for h_idx in h_indices :
937952 dists = np .linalg .norm (deficit_heavy_coords - ts_coords [h_idx ], axis = 1 )
938953 min_dist = float (dists .min ())
939954 nearest_heavy = deficit_heavy [int (dists .argmin ())]
940- h_dists .append ((h_idx , min_dist , nearest_heavy ))
941- h_dists .sort (key = lambda x : x [1 ])
942-
943- for h_idx , _ , nearest_heavy in h_dists [:n_to_move ]:
955+ on_anchor = any (
956+ atom_to_idx [nbr ] in split_anchors_in_frag
957+ for nbr in uni_mol .atoms [h_idx ].bonds .keys ()
958+ )
959+ h_dists .append ((h_idx , min_dist , nearest_heavy , on_anchor ))
960+ h_dists .sort (key = lambda x : (x [3 ], x [1 ]))
961+
962+ for h_idx , _ , nearest_heavy , _ in h_dists [:n_to_move ]:
944963 # Find the donor heavy atom: the heavy atom bonded to this H in the
945964 # source fragment.
946965 donor_heavy = None
0 commit comments