Skip to content

Commit f030b20

Browse files
authored
Merge pull request #1187 from ReactionMechanismGenerator/carbene_constraint
Add carbene species constraint
2 parents 81396e4 + 09ef13c commit f030b20

9 files changed

Lines changed: 414 additions & 167 deletions

File tree

documentation/source/users/rmg/input.rst

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -586,13 +586,15 @@ all of RMG's reaction families. ::
586586
generatedSpeciesConstraints(
587587
allowed=['input species','seed mechanisms','reaction libraries'],
588588
maximumCarbonAtoms=10,
589-
maximumHydrogenAtoms=10,
590-
maximumOxygenAtoms=10,
591-
maximumNitrogenAtoms=10,
592-
maximumSiliconAtoms=10,
593-
maximumSulfurAtoms=10,
589+
maximumOxygenAtoms=2,
590+
maximumNitrogenAtoms=2,
591+
maximumSiliconAtoms=2,
592+
maximumSulfurAtoms=2,
594593
maximumHeavyAtoms=10,
595-
maximumRadicalElectrons=10,
594+
maximumRadicalElectrons=2,
595+
maximumSingletCarbenes=1,
596+
maximumCarbeneRadicals=0,
597+
maximumIsotopicAtoms=2,
596598
allowSingletO2 = False,
597599
)
598600

examples/rmg/commented/input.py

Lines changed: 160 additions & 155 deletions
Large diffs are not rendered by default.

rmgpy/constraints.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ def failsSpeciesConstraints(species):
9393
if (struct.getRadicalCount() > maxRadicals):
9494
return True
9595

96+
maxCarbenes = speciesConstraints.get('maximumSingletCarbenes', 1)
97+
if maxRadicals != -1:
98+
if struct.getSingletCarbeneCount() > maxCarbenes:
99+
return True
100+
101+
maxCarbeneRadicals = speciesConstraints.get('maximumCarbeneRadicals', 0)
102+
if maxCarbeneRadicals != -1:
103+
if struct.getSingletCarbeneCount() > 0 and struct.getRadicalCount() > maxCarbeneRadicals:
104+
return True
105+
96106
maxIsotopes = speciesConstraints.get('maximumIsotopicAtoms', -1)
97107
if maxIsotopes != -1:
98108
counter = 0

rmgpy/constraintsTest.py

Lines changed: 193 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,210 @@
3333
"""
3434

3535
import unittest
36+
import mock
3637

37-
from rmgpy.constraints import *
38+
from rmgpy.rmg.main import RMG
39+
from rmgpy.constraints import failsSpeciesConstraints
40+
from rmgpy.species import Species
41+
from rmgpy.molecule import Molecule
42+
import rmgpy.rmg.input
3843

3944
################################################################################
4045

4146
class TestFailsSpeciesConstraints(unittest.TestCase):
4247
"""
4348
Contains unit tests of the failsSpeciesConstraints function.
4449
"""
45-
46-
def testConstraintsNotLoaded(self):
50+
51+
@classmethod
52+
def setUpClass(cls):
53+
"""
54+
A function run ONCE before all unit tests in this class.
55+
"""
56+
cls.rmg = RMG()
57+
rmgpy.rmg.input.rmg = cls.rmg
58+
rmgpy.rmg.input.generatedSpeciesConstraints(
59+
maximumCarbonAtoms=2,
60+
maximumOxygenAtoms=1,
61+
maximumNitrogenAtoms=1,
62+
maximumSiliconAtoms=1,
63+
maximumSulfurAtoms=1,
64+
maximumHeavyAtoms=3,
65+
maximumRadicalElectrons=2,
66+
maximumSingletCarbenes=1,
67+
maximumCarbeneRadicals=0,
68+
maximumIsotopicAtoms=2,
69+
)
70+
71+
@classmethod
72+
def tearDownClass(cls):
73+
"""
74+
A function run ONCE after all unit tests in this class.
75+
"""
76+
rmgpy.rmg.input.rmg = None
77+
78+
@mock.patch('rmgpy.constraints.logging')
79+
def testConstraintsNotLoaded(self, mock_logging):
4780
"""
4881
Test what happens when constraints are not loaded.
4982
"""
50-
from rmgpy.species import Species
83+
# Reset module level rmg variable in rmgpy.rmg.input
84+
rmgpy.rmg.input.rmg = None
85+
86+
mol = Molecule(SMILES='C')
5187

88+
self.assertFalse(failsSpeciesConstraints(mol))
89+
90+
mock_logging.debug.assert_called_with('Species constraints could not be found.')
91+
92+
# Restore module level rmg variable in rmgpy.rmg.input
93+
rmgpy.rmg.input.rmg = self.rmg
94+
95+
def testSpeciesInput(self):
96+
"""
97+
Test that failsSpeciesConstraints can handle a Species object.
98+
"""
5299
spc = Species().fromSMILES('C')
53100

54-
fails = failsSpeciesConstraints(spc)
55-
self.assertFalse(fails)
101+
self.assertFalse(failsSpeciesConstraints(spc))
102+
103+
def testExplicitlyAllowedMolecules(self):
104+
"""
105+
Test that we can explicitly allow molecules in species constraints.
106+
"""
107+
mol = Molecule(SMILES='CCCC')
108+
self.assertTrue(failsSpeciesConstraints(mol))
109+
110+
self.rmg.speciesConstraints['explicitlyAllowedMolecules'] = [Molecule(SMILES='CCCC')]
111+
self.assertFalse(failsSpeciesConstraints(mol))
112+
113+
def testCarbonConstraint(self):
114+
"""
115+
Test that we can constrain the max number of carbon atoms.
116+
"""
117+
mol1 = Molecule(SMILES='CC')
118+
self.assertFalse(failsSpeciesConstraints(mol1))
119+
120+
mol2 = Molecule(SMILES='CCC')
121+
self.assertTrue(failsSpeciesConstraints(mol2))
122+
123+
def testOxygenConstraint(self):
124+
"""
125+
Test that we can constrain the max number of oxygen atoms.
126+
"""
127+
mol1 = Molecule(SMILES='C=O')
128+
self.assertFalse(failsSpeciesConstraints(mol1))
129+
130+
mol2 = Molecule(SMILES='OC=O')
131+
self.assertTrue(failsSpeciesConstraints(mol2))
132+
133+
def testNitrogenConstraint(self):
134+
"""
135+
Test that we can constrain the max number of nitrogen atoms.
136+
"""
137+
mol1 = Molecule(SMILES='CN')
138+
self.assertFalse(failsSpeciesConstraints(mol1))
139+
140+
mol2 = Molecule(SMILES='NCN')
141+
self.assertTrue(failsSpeciesConstraints(mol2))
142+
143+
def testSiliconConstraint(self):
144+
"""
145+
Test that we can constrain the max number of silicon atoms.
146+
"""
147+
mol1 = Molecule(SMILES='[SiH4]')
148+
self.assertFalse(failsSpeciesConstraints(mol1))
149+
150+
mol2 = Molecule(SMILES='[SiH3][SiH3]')
151+
self.assertTrue(failsSpeciesConstraints(mol2))
152+
153+
def testSulfurConstraint(self):
154+
"""
155+
Test that we can constrain the max number of sulfur atoms.
156+
"""
157+
mol1 = Molecule(SMILES='CS')
158+
self.assertFalse(failsSpeciesConstraints(mol1))
159+
160+
mol2 = Molecule(SMILES='SCS')
161+
self.assertTrue(failsSpeciesConstraints(mol2))
162+
163+
def testHeavyConstraint(self):
164+
"""
165+
Test that we can constrain the max number of heavy atoms.
166+
"""
167+
mol1 = Molecule(SMILES='CCO')
168+
self.assertFalse(failsSpeciesConstraints(mol1))
169+
170+
mol2 = Molecule(SMILES='CCN=O')
171+
self.assertTrue(failsSpeciesConstraints(mol2))
172+
173+
def testRadicalConstraint(self):
174+
"""
175+
Test that we can constrain the max number of radical electrons.
176+
"""
177+
mol1 = Molecule(SMILES='[CH2][CH2]')
178+
self.assertFalse(failsSpeciesConstraints(mol1))
179+
180+
mol2 = Molecule(SMILES='[CH2][CH][CH2]')
181+
self.assertTrue(failsSpeciesConstraints(mol2))
182+
183+
def testCarbeneConstraint(self):
184+
"""
185+
Test that we can constrain the max number of singlet carbenes.
186+
"""
187+
mol1 = Molecule().fromAdjacencyList("""
188+
1 C u0 p1 c0 {2,S} {3,S}
189+
2 H u0 p0 c0 {1,S}
190+
3 H u0 p0 c0 {1,S}
191+
""")
192+
self.assertFalse(failsSpeciesConstraints(mol1))
193+
194+
mol2 = Molecule().fromAdjacencyList("""
195+
1 C u0 p1 c0 {2,S} {3,S}
196+
2 H u0 p0 c0 {1,S}
197+
3 C u0 p1 c0 {1,S} {4,S}
198+
4 H u0 p0 c0 {3,S}
199+
""")
200+
self.assertTrue(failsSpeciesConstraints(mol2))
201+
202+
def testCarbeneRadicalConstraint(self):
203+
"""
204+
Test that we can constrain the max number of radical electrons with a carbene.
205+
"""
206+
mol1 = Molecule().fromAdjacencyList("""
207+
1 C u0 p1 c0 {2,S} {3,S}
208+
2 H u0 p0 c0 {1,S}
209+
3 H u0 p0 c0 {1,S}
210+
""")
211+
self.assertFalse(failsSpeciesConstraints(mol1))
212+
213+
mol2 = Molecule().fromAdjacencyList("""
214+
1 C u0 p1 c0 {2,S} {3,S}
215+
2 H u0 p0 c0 {1,S}
216+
3 C u1 p0 c0 {1,S} {4,S} {5,S}
217+
4 H u0 p0 c0 {3,S}
218+
5 H u0 p0 c0 {3,S}
219+
""")
220+
self.assertTrue(failsSpeciesConstraints(mol2))
221+
222+
def testIsotopeConstraint(self):
223+
"""
224+
Test that we can constrain the max number of isotopic atoms.
225+
"""
226+
mol1 = Molecule().fromAdjacencyList("""
227+
1 C u0 p0 c0 {2,S} {3,S} {4,S} {5,S}
228+
2 D u0 p0 c0 {1,S}
229+
3 D u0 p0 c0 {1,S}
230+
4 H u0 p0 c0 {1,S}
231+
5 H u0 p0 c0 {1,S}
232+
""")
233+
self.assertFalse(failsSpeciesConstraints(mol1))
234+
235+
mol2 = Molecule().fromAdjacencyList("""
236+
1 C u0 p0 c0 {2,S} {3,S} {4,S} {5,S}
237+
2 D u0 p0 c0 {1,S}
238+
3 D u0 p0 c0 {1,S}
239+
4 D u0 p0 c0 {1,S}
240+
5 H u0 p0 c0 {1,S}
241+
""")
242+
self.assertTrue(failsSpeciesConstraints(mol2))

rmgpy/data/kinetics/family.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,8 +1329,11 @@ def isMoleculeForbidden(self, molecule):
13291329

13301330
forbidden_structures = getDB('forbidden')
13311331

1332+
# check family-specific forbidden structures
13321333
if self.forbidden is not None and self.forbidden.isMoleculeForbidden(molecule):
13331334
return True
1335+
1336+
# check RMG globally forbidden structures
13341337
if forbidden_structures.isMoleculeForbidden(molecule):
13351338
return True
13361339
return False

rmgpy/molecule/molecule.pxd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ cdef class Molecule(Graph):
145145

146146
cpdef short getRadicalCount(self)
147147

148+
cpdef short getSingletCarbeneCount(self)
149+
148150
cpdef double getMolecularWeight(self)
149151

150152
cpdef int getNumAtoms(self, str element=?)

rmgpy/molecule/molecule.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,19 @@ def getRadicalCount(self):
884884
radicals += atom.radicalElectrons
885885
return radicals
886886

887+
def getSingletCarbeneCount(self):
888+
"""
889+
Return the total number of singlet carbenes (lone pair on a carbon atom)
890+
in the molecule. Counts the number of carbon atoms with a lone pair.
891+
In the case of [C] with two lone pairs, this method will return 1.
892+
"""
893+
cython.declare(atom=Atom, carbenes=cython.short)
894+
carbenes = 0
895+
for atom in self.vertices:
896+
if atom.isCarbon() and atom.lonePairs > 0:
897+
carbenes += 1
898+
return carbenes
899+
887900
def getNumAtoms(self, element = None):
888901
"""
889902
Return the number of atoms in molecule. If element is given, ie. "H" or "C",

rmgpy/molecule/moleculeTest.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,29 @@ def testRadicalCH2CH2CH2(self):
10891089
"""
10901090
molecule = Molecule().fromSMILES('[CH2]C[CH2]')
10911091
self.assertEqual(molecule.getRadicalCount(), 2)
1092+
1093+
def testSingletCarbene(self):
1094+
"""Test radical and carbene count on singlet carbene."""
1095+
mol = Molecule().fromAdjacencyList("""
1096+
1 C u0 p1 {2,S}
1097+
2 C u0 p1 {1,S}
1098+
""", saturateH=True)
1099+
self.assertEqual(mol.getRadicalCount(), 0)
1100+
self.assertEqual(mol.getSingletCarbeneCount(), 2)
1101+
1102+
def testTripletCarbene(self):
1103+
"""Test radical and carbene count on triplet carbene."""
1104+
mol = Molecule().fromAdjacencyList("""
1105+
1 C u2 p0 {2,S}
1106+
2 C u0 p1 {1,S}
1107+
""", saturateH=True)
1108+
self.assertEqual(mol.getRadicalCount(), 2)
1109+
self.assertEqual(mol.getSingletCarbeneCount(), 1)
1110+
1111+
def testSingletCarbon(self):
1112+
"""Test that getSingletCarbeneCount returns 1 for singlet carbon atom."""
1113+
mol = Molecule().fromAdjacencyList('1 C u0 p2')
1114+
self.assertEqual(mol.getSingletCarbeneCount(), 1)
10921115

10931116
def testSMILES(self):
10941117
"""

rmgpy/rmg/input.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,8 @@ def generatedSpeciesConstraints(**kwargs):
353353
'maximumSulfurAtoms',
354354
'maximumHeavyAtoms',
355355
'maximumRadicalElectrons',
356+
'maximumSingletCarbenes',
357+
'maximumCarbeneRadicals',
356358
'allowSingletO2',
357359
'maximumIsotopicAtoms'
358360
]

0 commit comments

Comments
 (0)