Skip to content

Commit f0846ca

Browse files
committed
Allow addLabelsForReaction to properly handle identical references
If a reaction object, which had Molecule or Species objects with identical references, was input to addLabelsForReaction, the conflicting references would not allow the same molecule to hold multiple labels, leading to errors. This creates a deep copy of the Species object when identical references are found, since seprate references are necessary for the labeling. This commit also adds three test methods for addLabelsForReaction which previously had no test methods.
1 parent fda3c51 commit f0846ca

File tree

2 files changed

+140
-4
lines changed

2 files changed

+140
-4
lines changed

rmgpy/data/kinetics/family.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2132,6 +2132,15 @@ def addAtomLabelsForReaction(self, reaction, output_with_resonance = True):
21322132
# make sure we start with reaction with species objects
21332133
reaction.ensure_species(reactant_resonance=False, product_resonance=False)
21342134

2135+
reactants = reaction.reactants
2136+
products = reaction.products
2137+
# ensure all species are independent references
2138+
if len(reactants + products) > len(set([id(s) for s in reactants + products])):
2139+
logging.debug('Copying reactants and products for reaction {} since they have identical species references'.format(reaction))
2140+
# not all species are independent
2141+
reactants = [s.copy(deep=True) for s in reactants]
2142+
products = [s.copy(deep=True) for s in products]
2143+
21352144
# get all possible pairs of resonance structures
21362145
reactant_pairs = list(itertools.product(*[s.molecule for s in reaction.reactants]))
21372146
product_pairs = list(itertools.product(*[s.molecule for s in reaction.products]))
@@ -2152,20 +2161,22 @@ def addAtomLabelsForReaction(self, reaction, output_with_resonance = True):
21522161

21532162
# place the molecules in reaction's species object
21542163
# this prevents overwriting of attributes of species objects by this method
2155-
for species in reaction.products:
2164+
for index, species in enumerate(products):
21562165
for labeled_molecule in labeled_products:
21572166
if species.isIsomorphic(labeled_molecule):
21582167
species.molecule = [labeled_molecule]
2168+
reaction.products[index] = species
21592169
break
21602170
else:
2161-
raise ActionError('Could not find isomorphic molecule to fit the original product {}'.format(species))
2162-
for species in reaction.reactants:
2171+
raise ActionError('Could not find isomorphic molecule to fit the original product {} from reaction {}'.format(species, reaction))
2172+
for index, species in enumerate(reactants):
21632173
for labeled_molecule in labeled_reactants:
21642174
if species.isIsomorphic(labeled_molecule):
21652175
species.molecule = [labeled_molecule]
2176+
reaction.reactants[index] = species
21662177
break
21672178
else:
2168-
raise ActionError('Could not find isomorphic molecule to fit the original reactant {}'.format(species))
2179+
raise ActionError('Could not find isomorphic molecule to fit the original reactant {} from reaction {}'.format(species, reaction))
21692180

21702181
if output_with_resonance:
21712182
# convert the molecules to species objects with resonance structures

rmgpy/data/kinetics/kineticsTest.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,3 +897,128 @@ def test_generate_reactions_from_libraries2(self):
897897
reaction_list_2 = self.database.kinetics.generate_reactions_from_libraries(reactants, products)
898898

899899
self.assertEqual(len(reaction_list_2), 1)
900+
901+
def test_add_atom_labels_for_reaction(self):
902+
"""Test that addAtomLabelsForReaction can identify reactions with resonance
903+
The molecule [CH]=C=C has resonance in this reaction"""
904+
from rmgpy.data.rmg import getDB
905+
reactants = [
906+
Molecule().fromSMILES('C=C=C'),
907+
Molecule().fromSMILES('[CH]=C=C'),
908+
]
909+
products = [
910+
Molecule().fromSMILES('C#C[CH2]'),
911+
Molecule().fromSMILES('C#CC'),
912+
]
913+
reaction = TemplateReaction(reactants =reactants,
914+
products = products,
915+
family = 'H_Abstraction')
916+
reaction.ensure_species(reactant_resonance=True, product_resonance=True)
917+
family = getDB('kinetics').families['H_Abstraction']
918+
family.addAtomLabelsForReaction(reaction, output_with_resonance=False)
919+
920+
# test that the reaction has labels
921+
found_labels = []
922+
for species in reaction.reactants:
923+
for atom in species.molecule[0].atoms:
924+
if atom.label != '':
925+
found_labels.append(atom.label)
926+
self.assertEqual(len(found_labels), 3)
927+
self.assertIn('*1',found_labels)
928+
self.assertIn('*2',found_labels)
929+
self.assertIn('*3',found_labels)
930+
931+
# test for the products too
932+
found_labels = []
933+
for species in reaction.products:
934+
for atom in species.molecule[0].atoms:
935+
if atom.label != '':
936+
found_labels.append(atom.label)
937+
self.assertEqual(len(found_labels), 3)
938+
self.assertIn('*1',found_labels)
939+
self.assertIn('*2',found_labels)
940+
self.assertIn('*3',found_labels)
941+
942+
def test_add_atom_labels_for_reaction_2(self):
943+
"""Test that addAtomLabelsForReaction can identify reactions with identical references
944+
The molecule [CH]=C=C has resonance in this reaction"""
945+
from rmgpy.data.rmg import getDB
946+
s1 = Species().fromSMILES('C=C=C')
947+
s2 = Species().fromSMILES('C=C=[CH]')
948+
s3 = Species().fromSMILES('C#CC')
949+
s2.generate_resonance_structures()
950+
reactants = [s1,s2]
951+
products = [s2,s3]
952+
reaction = TemplateReaction(reactants =reactants,
953+
products = products,
954+
family = 'H_Abstraction')
955+
family = getDB('kinetics').families['H_Abstraction']
956+
print reaction.reactants
957+
print reaction.products
958+
family.addAtomLabelsForReaction(reaction, output_with_resonance=False)
959+
960+
# test that the reaction has labels
961+
found_labels = []
962+
for species in reaction.reactants:
963+
for atom in species.molecule[0].atoms:
964+
if atom.label != '':
965+
found_labels.append(atom.label)
966+
self.assertEqual(len(found_labels), 3,'wrong number of labels found {0}'.format(found_labels))
967+
self.assertIn('*1',found_labels)
968+
self.assertIn('*2',found_labels)
969+
self.assertIn('*3',found_labels)
970+
971+
# test for the products too
972+
found_labels = []
973+
for species in reaction.products:
974+
for atom in species.molecule[0].atoms:
975+
if atom.label != '':
976+
found_labels.append(atom.label)
977+
self.assertEqual(len(found_labels), 3)
978+
self.assertIn('*1',found_labels)
979+
self.assertIn('*2',found_labels)
980+
self.assertIn('*3',found_labels)
981+
982+
def test_add_atom_labels_for_reaction_3(self):
983+
"""Test that addAtomLabelsForReaction can identify reactions with resonance and isotopes"""
984+
from rmgpy.data.rmg import getDB
985+
mr0 = Molecule().fromAdjacencyList('1 C u0 p0 c0 i13 {3,D} {4,S} {5,S}\n2 *1 C u0 p0 c0 {3,D} {6,S} {7,S}\n3 C u0 p0 c0 {1,D} {2,D}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {2,S}\n7 *4 H u0 p0 c0 {2,S}\n')
986+
mr1a = Molecule().fromAdjacencyList('multiplicity 2\n1 C u0 p0 c0 i13 {2,D} {4,S} {5,S}\n2 C u0 p0 c0 {1,D} {3,D}\n3 *1 C u1 p0 c0 {2,D} {6,S}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {3,S}\n')
987+
mr1b = Molecule().fromAdjacencyList('multiplicity 2\n1 C u1 p0 c0 i13 {2,S} {4,S} {5,S}\n2 C u0 p0 c0 {1,S} {3,T}\n3 *1 C u0 p0 c0 {2,T} {6,S}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {3,S}\n')
988+
mp1a = Molecule().fromAdjacencyList('multiplicity 2\n1 C u0 p0 c0 {2,D} {4,S} {5,S}\n2 C u0 p0 c0 {1,D} {3,D}\n3 *1 C u1 p0 c0 i13 {2,D} {6,S}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {3,S}\n')
989+
mp1b = Molecule().fromAdjacencyList('multiplicity 2\n1 C u1 p0 c0 {2,S} {4,S} {5,S}\n2 C u0 p0 c0 {1,S} {3,T}\n3 *1 C u0 p0 c0 i13 {2,T} {6,S}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {3,S}\n')
990+
s1 = Species(molecule = [mr0])
991+
s2 = Species(molecule = [mr1a,mr1b])
992+
s3 = Species(molecule = [mp1a,mp1b])
993+
reactants = [s1,s2]
994+
products = [s1,s3]
995+
reaction = TemplateReaction(reactants =reactants,
996+
products = products,
997+
family = 'H_Abstraction')
998+
family = getDB('kinetics').families['H_Abstraction']
999+
print reaction.reactants
1000+
print reaction.products
1001+
family.addAtomLabelsForReaction(reaction, output_with_resonance=False)
1002+
1003+
# test that the reaction has labels
1004+
found_labels = []
1005+
for species in reaction.reactants:
1006+
for atom in species.molecule[0].atoms:
1007+
if atom.label != '':
1008+
found_labels.append(atom.label)
1009+
self.assertEqual(len(found_labels), 3,'wrong number of labels found {0}'.format(found_labels))
1010+
self.assertIn('*1',found_labels)
1011+
self.assertIn('*2',found_labels)
1012+
self.assertIn('*3',found_labels)
1013+
1014+
# test for the products too
1015+
found_labels = []
1016+
for species in reaction.products:
1017+
for atom in species.molecule[0].atoms:
1018+
if atom.label != '':
1019+
found_labels.append(atom.label)
1020+
self.assertEqual(len(found_labels), 3)
1021+
self.assertIn('*1',found_labels)
1022+
self.assertIn('*2',found_labels)
1023+
self.assertIn('*3',found_labels)
1024+

0 commit comments

Comments
 (0)