Skip to content

Commit 1d1c42c

Browse files
committed
T13
1 parent fec7261 commit 1d1c42c

2 files changed

Lines changed: 152 additions & 114 deletions

File tree

arc/job/adapters/ts/linear.py

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)