Skip to content

Commit 58b5698

Browse files
authored
Merge pull request #542 from ReactionMechanismGenerator/scissors_fix
Scissors fix
2 parents 05e5ab6 + bb985c3 commit 58b5698

File tree

5 files changed

+106
-12
lines changed

5 files changed

+106
-12
lines changed

arc/common.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,3 +1616,18 @@ def safe_copy_file(source: str,
16161616
break
16171617
if i >= max_cycles:
16181618
break
1619+
1620+
1621+
def sort_atoms_in_decending_label_order(mol: 'Molecule') -> None:
1622+
"""
1623+
A helper function, helpful in the context of atom mapping.
1624+
This function reassign the .atoms in Molecule with a list of atoms
1625+
with the orders based on the labels of the atoms.
1626+
for example, [int(atom.label) for atom in mol.atoms] is [1, 4, 32, 7],
1627+
then the function will re-assign the new atom with the order [1, 4, 7, 32]
1628+
1629+
Args:
1630+
mol (Molecule): An RMG Molecule object, with labeled atoms
1631+
"""
1632+
mol.atoms = sorted(mol.atoms, key= lambda x : int(x.label))
1633+

arc/commonTest.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import copy
99
import datetime
1010
import os
11+
import random
1112
import time
1213
import unittest
1314

@@ -1186,6 +1187,23 @@ def test_calc_rmsd(self):
11861187
rmsd_5 = common.calc_rmsd(a_5, b_5)
11871188
self.assertAlmostEqual(rmsd_5, 3.1622776601683795)
11881189

1190+
def test_sort_atoms_in_decending_label_order(self):
1191+
"""tests the sort_atoms_in_decending_label_order function"""
1192+
mol = Molecule(smiles="C1CCCC1")
1193+
for index, atom in enumerate(mol.atoms):
1194+
atom.label = str(index)
1195+
random.shuffle(mol.atoms)
1196+
common.sort_atoms_in_decending_label_order(mol)
1197+
for index, atom in enumerate(mol.atoms):
1198+
self.assertEqual(str(index), atom.label)
1199+
mol = Molecule(smiles="NC1=NC=NC2=C1N=CN2")
1200+
for index, atom in enumerate(mol.atoms):
1201+
atom.label = str(index)
1202+
random.shuffle(mol.atoms)
1203+
common.sort_atoms_in_decending_label_order(mol)
1204+
for index, atom in enumerate(mol.atoms):
1205+
self.assertEqual(str(index), atom.label)
1206+
11891207
def test_check_r_n_p_symbols_between_rmg_and_arc_rxns(self):
11901208
"""Test the _check_r_n_p_symbols_between_rmg_and_arc_rxns() function"""
11911209
arc_rxn = ARCReaction(r_species=[ARCSpecies(label='CH4', smiles='C'), ARCSpecies(label='OH', smiles='[OH]')],

arc/species/mapping.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ def map_h_abstraction(rxn: 'ARCReaction',
266266
)
267267
spc_r1_h2.final_xyz = spc_r1_h2.get_xyz() # Scissors requires the .final_xyz attribute to be populated.
268268
try:
269-
spc_r1_h2_cuts = spc_r1_h2.scissors()
269+
spc_r1_h2_cuts = spc_r1_h2.scissors(sort_atom_labels=True)
270270
except SpeciesError:
271271
return None
272272
spc_r1_h2_cut = [spc for spc in spc_r1_h2_cuts if spc.label != 'H'][0] \
@@ -279,7 +279,7 @@ def map_h_abstraction(rxn: 'ARCReaction',
279279
)
280280
spc_r3_h2.final_xyz = spc_r3_h2.get_xyz()
281281
try:
282-
spc_r3_h2_cuts = spc_r3_h2.scissors()
282+
spc_r3_h2_cuts = spc_r3_h2.scissors(sort_atom_labels=True)
283283
except SpeciesError:
284284
return None
285285
spc_r3_h2_cut = [spc for spc in spc_r3_h2_cuts if spc.label != 'H'][0] \
@@ -447,7 +447,7 @@ def map_intra_h_migration(rxn: 'ARCReaction',
447447
)
448448
spc_r.final_xyz = spc_r.get_xyz() # Scissors requires the .final_xyz attribute to be populated.
449449
try:
450-
spc_r_dot = [spc for spc in spc_r.scissors() if spc.label != 'H'][0]
450+
spc_r_dot = [spc for spc in spc_r.scissors(sort_atom_labels=True) if spc.label != 'H'][0]
451451
except SpeciesError:
452452
return None
453453
spc_p = ARCSpecies(label='P',
@@ -457,7 +457,7 @@ def map_intra_h_migration(rxn: 'ARCReaction',
457457
)
458458
spc_p.final_xyz = spc_p.get_xyz()
459459
try:
460-
spc_p_dot = [spc for spc in spc_p.scissors() if spc.label != 'H'][0]
460+
spc_p_dot = [spc for spc in spc_p.scissors(sort_atom_labels=True) if spc.label != 'H'][0]
461461
except SpeciesError:
462462
return None
463463
map_ = map_two_species(spc_r_dot, spc_p_dot, backend=backend)

arc/species/species.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
rmg_mol_from_dict_repr,
3333
rmg_mol_to_dict_repr,
3434
timedelta_from_str,
35+
sort_atoms_in_decending_label_order,
3536
)
3637
from arc.exceptions import InputError, RotorError, SpeciesError, TSError
3738
from arc.imports import settings
@@ -248,6 +249,7 @@ class ARCSpecies(object):
248249
rxn_index (int): The reaction index which is the respective key to the Scheduler rxn_dict.
249250
arkane_file (str): Path to the Arkane Species file generated in processor.
250251
yml_path (str): Path to an Arkane YAML file representing a species (for loading the object).
252+
keep_mol (bool): Label to prevent the generation of a new Molecule object.
251253
checkfile (str): The local path to the latest checkfile by Gaussian for the species.
252254
external_symmetry (int): The external symmetry of the species (not including rotor symmetries).
253255
optical_isomers (int): Whether (=2) or not (=1) the species has chiral center/s.
@@ -317,6 +319,7 @@ def __init__(self,
317319
ts_number: Optional[int] = None,
318320
xyz: Optional[Union[list, dict, str]] = None,
319321
yml_path: Optional[str] = None,
322+
keep_mol: bool = False,
320323
):
321324
self.t1 = None
322325
self.ts_number = ts_number
@@ -347,6 +350,7 @@ def __init__(self,
347350
self.checkfile = checkfile
348351
self.transport_data = TransportData()
349352
self.yml_path = yml_path
353+
self.keep_mol = keep_mol
350354
self.fragments = fragments
351355
self.original_label = None
352356
self.chosen_ts = None
@@ -1543,7 +1547,8 @@ def mol_from_xyz(self,
15431547
f'{self.mol.copy(deep=True).to_smiles()}\n'
15441548
f'{self.mol.copy(deep=True).to_adjacency_list()}')
15451549
raise SpeciesError(f'XYZ and the 2D graph representation for {self.label} are not compliant.')
1546-
self.mol = perceived_mol
1550+
if not self.keep_mol:
1551+
self.mol = perceived_mol
15471552
else:
15481553
mol_s, mol_b = molecules_from_xyz(xyz, multiplicity=self.multiplicity, charge=self.charge)
15491554
if mol_b is not None and len(mol_b.atoms) == self.number_of_atoms:
@@ -1764,13 +1769,25 @@ def check_xyz_isomorphism(self,
17641769
logger.warning('Allowing nonisomorphic 2D')
17651770
return isomorphic
17661771

1767-
def scissors(self) -> list:
1772+
def label_atoms(self):
1773+
"""
1774+
Labels atoms in order.
1775+
The label is stored in the atom.label property.
1776+
"""
1777+
for index, atom in enumerate(self.mol.atoms):
1778+
atom.label = str(index)
1779+
1780+
def scissors(self,
1781+
sort_atom_labels: bool = False) -> list:
17681782
"""
17691783
Cut chemical bonds to create new species from the original one according to the .bdes attribute,
17701784
preserving the 3D geometry other than the scissioned bond.
17711785
If one of the scission-resulting species is a hydrogen atom, it will be returned last, labeled as 'H'.
17721786
Other species labels will be <original species label>_BDE_index1_index2_X, where "X" is either "A" or "B",
17731787
and the indices are 1-indexed.
1788+
1789+
Args:
1790+
sort_atom_labels (bool, optional): Boolean flag, dettermines whether or not sorting is required.
17741791
17751792
Returns: list
17761793
The scission-resulting species.
@@ -1794,21 +1811,26 @@ def scissors(self) -> list:
17941811
atom_indices_reverse = (atom_indices[1], atom_indices[0])
17951812
if atom_indices not in self.bdes and atom_indices_reverse not in self.bdes:
17961813
self.bdes.append(atom_indices)
1814+
if sort_atom_labels:
1815+
self.label_atoms()
17971816
resulting_species = list()
17981817
for index_tuple in self.bdes:
1799-
new_species_list = self._scissors(indices=index_tuple)
1818+
new_species_list = self._scissors(indices=index_tuple, sort_atom_labels=sort_atom_labels)
18001819
for new_species in new_species_list:
18011820
if new_species.label not in [existing_species.label for existing_species in resulting_species]:
18021821
# Mainly checks that the H species doesn't already exist.
18031822
resulting_species.append(new_species)
18041823
return resulting_species
18051824

1806-
def _scissors(self, indices: tuple) -> list:
1825+
def _scissors(self,
1826+
indices: tuple,
1827+
sort_atom_labels: bool = True) -> list:
18071828
"""
18081829
Cut a chemical bond to create two new species from the original one, preserving the 3D geometry.
18091830
18101831
Args:
18111832
indices (tuple): The atom indices between which to cut (1-indexed, atoms must be bonded).
1833+
sort_atom_labels (bool, optional): Boolean flag, dettermines whether or not sorting is required.
18121834
18131835
Returns: list
18141836
The scission-resulting species, a list of either one or two species, if the scissored location is linear,
@@ -1860,6 +1882,10 @@ def _scissors(self, indices: tuple) -> list:
18601882
logger.warning(f'Scissors were requested to remove a non-single bond in {self.label}.')
18611883
mol_copy.remove_bond(bond)
18621884
mol_splits = mol_copy.split()
1885+
if sort_atom_labels:
1886+
for split in mol_splits:
1887+
sort_atoms_in_decending_label_order(split)
1888+
18631889
if len(mol_splits) == 1: # If cutting leads to only one split, then the split is cyclic.
18641890
spc1 = ARCSpecies(label=self.label + '_BDE_' + str(indices[0] + 1) + '_' + str(indices[1] + 1) + '_cyclic',
18651891
mol=mol_splits[0],
@@ -1902,8 +1928,8 @@ def _scissors(self, indices: tuple) -> list:
19021928
else:
19031929
raise SpeciesError(f'Could not figure out which atom should gain a radical '
19041930
f'due to scission in {self.label}')
1905-
mol1.update(raise_atomtype_exception=False)
1906-
mol2.update(raise_atomtype_exception=False)
1931+
mol1.update(log_species=False, raise_atomtype_exception=False, sort_atoms=False)
1932+
mol2.update(log_species=False, raise_atomtype_exception=False, sort_atoms=False)
19071933

19081934
# match xyz to mol:
19091935
if len(mol1.atoms) != len(mol2.atoms):
@@ -1934,7 +1960,8 @@ def _scissors(self, indices: tuple) -> list:
19341960
multiplicity=mol1.multiplicity,
19351961
charge=mol1.get_net_charge(),
19361962
compute_thermo=False,
1937-
e0_only=True)
1963+
e0_only=True,
1964+
keep_mol=True)
19381965
spc1.generate_conformers()
19391966
spc1.rotors_dict = None
19401967
spc2 = ARCSpecies(label=label2,
@@ -1943,7 +1970,8 @@ def _scissors(self, indices: tuple) -> list:
19431970
multiplicity=mol2.multiplicity,
19441971
charge=mol2.get_net_charge(),
19451972
compute_thermo=False,
1946-
e0_only=True)
1973+
e0_only=True,
1974+
keep_mol=True)
19471975
spc2.generate_conformers()
19481976
spc2.rotors_dict = None
19491977

arc/species/speciesTest.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,13 @@ def test_from_dict(self):
12411241
spc = ARCSpecies(species_dict=species_dict)
12421242
self.assertTrue(spc.is_ts)
12431243

1244+
def test_label_atoms(self):
1245+
"""Test the label_atoms method"""
1246+
spc_copy = self.spc6.copy()
1247+
spc_copy.label_atoms()
1248+
for index, atom in enumerate(spc_copy.mol.atoms):
1249+
self.assertEqual(str(index), atom.label)
1250+
12441251
def test_copy(self):
12451252
"""Test the copy() method."""
12461253
spc_copy = self.spc6.copy()
@@ -2028,6 +2035,32 @@ def test_scissors(self):
20282035
self.assertTrue(cycle_scissors[0].mol.is_isomorphic(ARCSpecies(label="check",smiles ="[CH2+]C[CH2+]").mol))
20292036
self.assertEqual(len(cycle_scissors), 1)
20302037

2038+
benzyl_alcohol = ARCSpecies(label='benzyl_alcohol', smiles='c1ccccc1CO',
2039+
xyz="""O 2.64838903 0.03033680 1.02963866
2040+
C 2.08223673 -0.09327854 -0.26813441
2041+
C 0.58011672 -0.03951284 -0.19914397
2042+
C -0.09047623 1.18918897 -0.26985124
2043+
C -0.16442536 -1.21163631 -0.00891767
2044+
C -1.48186739 1.24136671 -0.17379396
2045+
C -2.21381021 0.06846364 0.00253129
2046+
C -1.55574847 -1.15724814 0.08689917
2047+
H 2.47222737 0.71379644 -0.89724902
2048+
H 2.41824638 -1.03876722 -0.70676479
2049+
H 0.46950724 2.11319745 -0.39919154
2050+
H -1.99496868 2.19776599 -0.23432175
2051+
H -3.29735459 0.10998171 0.07745660
2052+
H -2.12646340 -2.07132623 0.22966400
2053+
H 0.33744916 -2.17418164 0.06678265
2054+
H 1.91694170 0.12185320 1.66439598""")
2055+
benzyl_alcohol.bdes = [(7, 13)]
2056+
benzyl_alcohol.final_xyz = benzyl_alcohol.get_xyz()
2057+
species = benzyl_alcohol.scissors(sort_atom_labels=True)
2058+
for spc in species:
2059+
if spc.label != 'H':
2060+
for i, atom in enumerate(spc.mol.atoms):
2061+
if atom.radical_electrons:
2062+
self.assertEqual(i, 6)
2063+
20312064
def test_net_charged_species(self):
20322065
"""Test that we can define, process, and manipulate ions"""
20332066
nh4 = ARCSpecies(label='NH4', smiles='[NH4+]', charge=1)

0 commit comments

Comments
 (0)