Skip to content

Commit 5514eed

Browse files
committed
perceive: represent inter-fragment contacts in transition states as reaction bonds
When perceiving a molecule from coordinates, transition states (TS) often consist of multiple fragments held together by the breaking/forming bonds. Previously, these inter-fragment contacts were either ignored or assigned bond orders based on radical availability. Changes: - Pass an `is_ts` flag through the perception pipeline. - In `_add_interfragment_bonds`, explicitly assign a bond order of 0.05 (reaction bond) to inter-fragment contacts if the species is a TS. - Update `to_rdkit_mol` to skip reaction and van der Waals bonds, as RDKit cannot represent these non-covalent or partial bond types. - Update `ARCSpecies` perception logic to propagate the `is_ts` status, ensuring TS connectivity is correctly captured during coordinate-to-graph conversion.
1 parent dc2d4c0 commit 5514eed

3 files changed

Lines changed: 18 additions & 5 deletions

File tree

arc/molecule/converter.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ def to_rdkit_mol(mol, remove_h=True, return_mapping=False, sanitize=True, save_o
5757
# Add the bonds
5858
for atom1 in mol.vertices:
5959
for atom2, bond in atom1.edges.items():
60-
if bond.is_hydrogen_bond():
60+
# Skip non-covalent bonds RDKit cannot represent (H-bonds, vdW, reaction bonds).
61+
if bond.is_hydrogen_bond() or bond.is_van_der_waals() or bond.is_reaction_bond():
6162
continue
6263
index1 = atoms.index(atom1)
6364
index2 = atoms.index(atom2)

arc/species/perceive.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def perceive_molecule_from_xyz(
3333
n_radicals: int | None = None,
3434
n_fragments: int | None = None,
3535
single_bond_tolerance: float = 1.20,
36+
is_ts: bool = False,
3637
) -> Molecule | None:
3738
"""
3839
Infer a chemically valid Molecule with localized Lewis structure from Cartesian coordinates.
@@ -119,7 +120,7 @@ def perceive_molecule_from_xyz(
119120
# if we expected multiple fragments, hand off to the multi‐frag helper
120121
if len(fragments) != 1:
121122
if len(fragments) == n_fragments:
122-
return _combine_fragments(symbols, coords, fragments, charge)
123+
return _combine_fragments(symbols, coords, fragments, charge, is_ts=is_ts)
123124
return None
124125

125126
# otherwise fall back on the existing single‐molecule code
@@ -149,6 +150,7 @@ def _combine_fragments(
149150
coords: tuple[tuple[float, float, float], ...],
150151
fragments: list[list[int]],
151152
total_charge: int,
153+
is_ts: bool = False,
152154
) -> Molecule:
153155
"""
154156
Build disconnected fragments separately, then stitch them into one Molecule with charges distributed.
@@ -273,7 +275,7 @@ def _perceive_frag(idxs: list[int], charge: int) -> Molecule | None:
273275
best_mol.multiplicity = max(sm.multiplicity for sm in submols)
274276
assign_formal_charges(best_mol)
275277
enforce_target_charge(best_mol, total_charge)
276-
_add_interfragment_bonds(best_mol, fragments, coords)
278+
_add_interfragment_bonds(best_mol, fragments, coords, is_ts=is_ts)
277279

278280
# restore original atom order
279281
idx_map: dict[int, int] = dict()
@@ -297,13 +299,16 @@ def _add_interfragment_bonds(
297299
mol: Molecule,
298300
fragments: list[list[int]],
299301
coords: tuple[tuple[float, float, float], ...],
302+
is_ts: bool = False,
300303
) -> None:
301304
"""
302305
Connect separate fragments in a molecule by adding inter-fragment bonds.
303306
304307
For each adjacent fragment pair, the algorithm finds the closest atom pair
305308
(based on `coords`) and adds a bond between them:
306-
• If at least one atom is a radical, use bond order = 1.0 by default.
309+
• For a TS, always use bond order = 0.05 (reaction bond) since the
310+
inter-fragment contact is the breaking/forming bond by construction.
311+
• Otherwise, if at least one atom is a radical, use bond order = 1.0 by default.
307312
If neither has available valence, use bond order = 0.05 (weak link).
308313
• If neither atom is a radical, set bond order = 0.0 (no real bond).
309314
@@ -337,7 +342,9 @@ def _add_interfragment_bonds(
337342
a1 = mol.atoms[idx_map[i0]]
338343
a2 = mol.atoms[idx_map[j0]]
339344

340-
if a1.radical_electrons > 0 or a2.radical_electrons > 0:
345+
if is_ts:
346+
bond_order = 0.05
347+
elif a1.radical_electrons > 0 or a2.radical_electrons > 0:
341348
bond_order = 1.0 if n_missing_electrons(a1) or n_missing_electrons(a2) else 0.05
342349
else:
343350
bond_order = 0.0

arc/species/species.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,6 +1432,7 @@ def set_dihedral(self,
14321432
multiplicity=self.multiplicity,
14331433
n_radicals=self.number_of_radicals,
14341434
n_fragments=self.get_n_fragments(),
1435+
is_ts=self.is_ts,
14351436
)
14361437
if chk_rotor_list:
14371438
for rotor in self.rotors_dict.values():
@@ -1639,6 +1640,7 @@ def mol_from_xyz(self,
16391640
multiplicity=self.multiplicity,
16401641
n_radicals=self.number_of_radicals,
16411642
n_fragments=self.get_n_fragments(),
1643+
is_ts=self.is_ts,
16421644
)
16431645
if perceived_mol is not None:
16441646
if self.is_ts:
@@ -1673,13 +1675,15 @@ def mol_from_xyz(self,
16731675
multiplicity=self.multiplicity,
16741676
n_radicals=self.number_of_radicals,
16751677
n_fragments=self.get_n_fragments(),
1678+
is_ts=self.is_ts,
16761679
)
16771680
if perceived_mol is None and self.is_ts:
16781681
perceived_mol = perceive_molecule_from_xyz(xyz,
16791682
charge=self.charge,
16801683
multiplicity=self.multiplicity,
16811684
n_radicals=self.number_of_radicals,
16821685
n_fragments=2,
1686+
is_ts=True,
16831687
)
16841688
if perceived_mol is not None:
16851689
self.mol = perceived_mol
@@ -1853,6 +1857,7 @@ def check_xyz_isomorphism(self,
18531857
multiplicity=self.multiplicity,
18541858
n_radicals=self.number_of_radicals,
18551859
n_fragments=self.get_n_fragments(),
1860+
is_ts=self.is_ts,
18561861
)
18571862

18581863
# 2. A. Check isomorphism with bond orders using b_mol

0 commit comments

Comments
 (0)