Skip to content

Commit 263e613

Browse files
committed
Merge pull request #208 from connie/master
Modifying species constraints checks
2 parents a896980 + b0bd7ab commit 263e613

File tree

8 files changed

+211
-77
lines changed

8 files changed

+211
-77
lines changed

documentation/source/users/rmg/database/kinetics.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,46 @@ Additionally, groups can also be defined as unions of other groups. For example,
162162

163163
label="X_H_or_Xrad_H",
164164
group=OR{X_H, Xrad_H},
165+
166+
167+
Forbidden Groups
168+
----------------
169+
Forbidden groups can be defined to ban structures globally in RMG or to
170+
ban pathways in a specific kinetic family.
171+
172+
Globally forbidden structures will ban all reactions containing either reactants
173+
or products that are forbidden. These groups are stored in in the file located at
174+
``RMG-database/input/forbiddenStructures.py``.
175+
176+
177+
To ban certain specific pathways in the kinetics
178+
families, a `forbidden` group must be created, like the following group
179+
in the ``intra_H_migration`` family ::
180+
181+
forbidden(
182+
label = "bridged56_1254",
183+
group =
184+
"""""""
185+
1 *1 C 1 {2,S} {6,S}
186+
2 *4 C 0 {1,S} {3,S} {7,S}
187+
3 C 0 {2,S} {4,S}
188+
4 *2 C 0 {3,S} {5,S} {8,S}
189+
5 *5 C 0 {4,S} {6,S} {7,S}
190+
6 C 0 {1,S} {5,S}
191+
7 C 0 {2,S} {5,S}
192+
8 *3 H 0 {4,S}
193+
""",
194+
shortDesc = u"""""",
195+
longDesc =
196+
u"""
197+
198+
""",
199+
)
200+
201+
Forbidden groups should be placed inside the groups.py file located inside the
202+
specific kinetics family's folder ``RMG-database/input/kinetics/family_name/``
203+
alongside normal group entries. The starred atoms in the forbidden group
204+
ban the specified reaction recipe from occurring in matched products and reactants.
165205

166206
Hierarchical Trees
167207
------------------

documentation/source/users/rmg/input.rst

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -360,14 +360,22 @@ Miscellaneous Options
360360

361361
Miscellaneous options::
362362

363-
options(
364-
units='si',
365-
saveRestartPeriod=(1,'hour'),
366-
drawMolecules=False,
367-
generatePlots=False,
368-
)
363+
options(
364+
units='si',
365+
saveRestartPeriod=(1,'hour'),
366+
drawMolecules=False,
367+
generatePlots=False,
368+
)
369+
370+
Species Constraints
371+
=====================
369372

370-
generatedSpeciesConstraints(
373+
RMG can generate mechanisms with a number of optional species constraints,
374+
such as total number of carbon atoms or electrons per species. These are applied to
375+
all of RMG's reaction families. ::
376+
377+
generatedSpeciesConstraints(
378+
allowed=['input species','seed mechanisms','reaction libraries'],
371379
maximumCarbonAtoms=10,
372380
maximumHydrogenAtoms=10,
373381
maximumOxygenAtoms=10,
@@ -376,7 +384,18 @@ Miscellaneous options::
376384
maximumSulfurAtoms=10,
377385
maximumHeavyAtoms=10,
378386
maximumRadicalElectrons=10,
379-
)
387+
)
388+
389+
An additional flag ``allowed`` can be set to allow species
390+
from either the input file, seed mechanisms, or reaction libraries to bypass these constraints.
391+
Note that this should be done with caution, since the constraints will still apply to subsequent
392+
products that form.
393+
394+
Note that under all circumstances all forbidden species will still be banned unless they are
395+
manually removed from the database. See :ref:`kineticsDatabase` for more information on
396+
forbidden groups.
397+
398+
380399

381400
Examples
382401
========

rmgpy/data/kinetics/__init__.py

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ def __init__(self):
6262
self.recommendedFamilies = {}
6363
self.families = {}
6464
self.libraries = {}
65-
self.libraryOrder = []
65+
self.libraryOrder = [] # a list of tuples in the format ('library_label', LibraryType),
66+
# where LibraryType is set to either 'Reaction Library' or 'Seed'.
6667
self.local_context = {
6768
'KineticsData': KineticsData,
6869
'Arrhenius': Arrhenius,
@@ -201,7 +202,7 @@ def loadLibraries(self, path, libraries=None):
201202
The `path` points to the folder of kinetics libraries in the database,
202203
and the libraries should be in files like :file:`<path>/<library>.py`.
203204
"""
204-
self.libraries = {}; self.libraryOrder = []
205+
self.libraries = {}
205206

206207
if libraries is not None:
207208
for library_name in libraries:
@@ -211,11 +212,12 @@ def loadLibraries(self, path, libraries=None):
211212
library = KineticsLibrary(label=library_name)
212213
library.load(library_file, self.local_context, self.global_context)
213214
self.libraries[library.label] = library
214-
self.libraryOrder.append(library.label)
215215
else:
216216
raise IOError("Couldn't find kinetics library {0}".format(library_file))
217-
assert (self.libraryOrder == libraries)
218-
else:# load all the libraries you can find
217+
# library order should've been set prior to this, with the given seed mechs and reaction libraries
218+
assert (len(self.libraryOrder) == len(libraries))
219+
else:# load all the libraries you can find (this cannot be activated in a normal RMG job. Only activated when loading the database for other purposes)
220+
self.libraryOrder = []
219221
for (root, dirs, files) in os.walk(os.path.join(path)):
220222
for f in files:
221223
name, ext = os.path.splitext(f)
@@ -226,7 +228,7 @@ def loadLibraries(self, path, libraries=None):
226228
library = KineticsLibrary(label=label)
227229
library.load(library_file, self.local_context, self.global_context)
228230
self.libraries[library.label] = library
229-
self.libraryOrder.append(library.label)
231+
self.libraryOrder.append((library.label,'Reaction Library'))
230232

231233
def save(self, path):
232234
"""
@@ -330,29 +332,31 @@ def saveOld(self, path):
330332
onoff = 'on ' if self.recommendedFamilies[label] else 'off'
331333
f.write("{num:<2d} {onoff} {label}\n".format(num=number, label=label, onoff=onoff))
332334

333-
def generateReactions(self, reactants, products=None, **options):
335+
def generateReactions(self, reactants, products=None, failsSpeciesConstraints=None):
334336
"""
335337
Generate all reactions between the provided list of one or two
336338
`reactants`, which should be :class:`Molecule` objects. This method
337339
searches the depository, libraries, and groups, in that order.
338340
"""
339341
reactionList = []
340-
reactionList.extend(self.generateReactionsFromLibraries(reactants, products, **options))
341-
reactionList.extend(self.generateReactionsFromFamilies(reactants, products, **options))
342+
reactionList.extend(self.generateReactionsFromLibraries(reactants, products, failsSpeciesConstraints=failsSpeciesConstraints))
343+
reactionList.extend(self.generateReactionsFromFamilies(reactants, products, failsSpeciesConstraints=failsSpeciesConstraints))
342344
return reactionList
343345

344-
def generateReactionsFromLibraries(self, reactants, products, **options):
346+
def generateReactionsFromLibraries(self, reactants, products, failsSpeciesConstraints=None):
345347
"""
346348
Generate all reactions between the provided list of one or two
347349
`reactants`, which should be :class:`Molecule` objects. This method
348350
searches the depository.
349351
"""
350352
reactionList = []
351-
for label in self.libraryOrder:
352-
reactionList.extend(self.generateReactionsFromLibrary(reactants, products, self.libraries[label], **options))
353+
for label, libraryType in self.libraryOrder:
354+
# Generate reactions from reaction libraries (no need to generate them from seeds)
355+
if libraryType == "Reaction Library":
356+
reactionList.extend(self.generateReactionsFromLibrary(reactants, products, self.libraries[label], failsSpeciesConstraints=failsSpeciesConstraints))
353357
return reactionList
354358

355-
def generateReactionsFromLibrary(self, reactants, products, library, **options):
359+
def generateReactionsFromLibrary(self, reactants, products, library, failsSpeciesConstraints=None):
356360
"""
357361
Generate all reactions between the provided list of one or two
358362
`reactants`, which should be :class:`Molecule` objects. This method
@@ -375,7 +379,7 @@ def generateReactionsFromLibrary(self, reactants, products, library, **options):
375379
reactionList = filterReactions(reactants, products, reactionList)
376380
return reactionList
377381

378-
def generateReactionsFromFamilies(self, reactants, products, only_families=None, **options):
382+
def generateReactionsFromFamilies(self, reactants, products, only_families=None, failsSpeciesConstraints=None):
379383
"""
380384
Generate all reactions between the provided list of one or two
381385
`reactants`, which should be :class:`Molecule` objects. This method
@@ -392,7 +396,7 @@ def generateReactionsFromFamilies(self, reactants, products, only_families=None,
392396
reactionList = []
393397
for label, family in self.families.iteritems():
394398
if only_families is None or label in only_families:
395-
reactionList.extend(family.generateReactions(reactants, **options))
399+
reactionList.extend(family.generateReactions(reactants, failsSpeciesConstraints=failsSpeciesConstraints))
396400
if products:
397401
reactionList = filterReactions(reactants, products, reactionList)
398402
return reactionList

rmgpy/data/kinetics/family.py

Lines changed: 17 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,7 +1078,7 @@ def applyRecipe(self, reactantStructures, forward=True, unique=True):
10781078
# Return the product structures
10791079
return productStructures
10801080

1081-
def __generateProductStructures(self, reactantStructures, maps, forward, **options):
1081+
def __generateProductStructures(self, reactantStructures, maps, forward, failsSpeciesConstraints=None):
10821082
"""
10831083
For a given set of `reactantStructures` and a given set of `maps`,
10841084
generate and return the corresponding product structures. The
@@ -1087,6 +1087,8 @@ def __generateProductStructures(self, reactantStructures, maps, forward, **optio
10871087
parameter is a list of mappings of the top-level tree node of each
10881088
*template* reactant to the corresponding *structure*. This function
10891089
returns a list of the product structures.
1090+
`failsSpeciesConstraints` is a function that accepts a :class:`Molecule`
1091+
structure and returns `True` if it is forbidden.
10901092
"""
10911093

10921094
productStructuresList = []
@@ -1125,33 +1127,11 @@ def __generateProductStructures(self, reactantStructures, maps, forward, **optio
11251127
productStructures.reverse()
11261128

11271129
# Apply the generated species constraints (if given)
1128-
if options:
1129-
maxCarbonAtoms = options.get('maximumCarbonAtoms', 1000000)
1130-
maxHydrogenAtoms = options.get('maximumHydrogenAtoms', 1000000)
1131-
maxOxygenAtoms = options.get('maximumOxygenAtoms', 1000000)
1132-
maxNitrogenAtoms = options.get('maximumNitrogenAtoms', 1000000)
1133-
maxSiliconAtoms = options.get('maximumSiliconAtoms', 1000000)
1134-
maxSulfurAtoms = options.get('maximumSulfurAtoms', 1000000)
1135-
maxHeavyAtoms = options.get('maximumHeavyAtoms', 1000000)
1136-
maxRadicals = options.get('maximumRadicalElectrons', 1000000)
1130+
if failsSpeciesConstraints:
11371131
for struct in productStructures:
1138-
H = struct.getNumAtoms('H')
1139-
if struct.getNumAtoms('C') > maxCarbonAtoms:
1140-
raise ForbiddenStructureException()
1141-
if H > maxHydrogenAtoms:
1142-
raise ForbiddenStructureException()
1143-
if struct.getNumAtoms('O') > maxOxygenAtoms:
1144-
raise ForbiddenStructureException()
1145-
if struct.getNumAtoms('N') > maxNitrogenAtoms:
1146-
raise ForbiddenStructureException()
1147-
if struct.getNumAtoms('Si') > maxSiliconAtoms:
1148-
raise ForbiddenStructureException()
1149-
if struct.getNumAtoms('S') > maxSulfurAtoms:
1150-
raise ForbiddenStructureException()
1151-
if len(struct.atoms) - H > maxHeavyAtoms:
1152-
raise ForbiddenStructureException()
1153-
if (struct.getNumberOfRadicalElectrons() > maxRadicals) and (len(struct.atoms) - H > 1):
1154-
raise ForbiddenStructureException()
1132+
if failsSpeciesConstraints(struct):
1133+
raise ForbiddenStructureException()
1134+
11551135

11561136
# Generate other possible electronic states
11571137
electronicStructuresList1 = []
@@ -1389,7 +1369,7 @@ def __matchReactantToTemplate(self, reactant, templateReactant):
13891369
elif isinstance(struct, Group):
13901370
return reactant.findSubgraphIsomorphisms(struct)
13911371

1392-
def generateReactions(self, reactants, **options):
1372+
def generateReactions(self, reactants, failsSpeciesConstraints=None):
13931373
"""
13941374
Generate all reactions between the provided list of one or two
13951375
`reactants`, which should be either single :class:`Molecule` objects
@@ -1402,12 +1382,12 @@ def generateReactions(self, reactants, **options):
14021382
reactionList = []
14031383

14041384
# Forward direction (the direction in which kinetics is defined)
1405-
reactionList.extend(self.__generateReactions(reactants, forward=True, **options))
1385+
reactionList.extend(self.__generateReactions(reactants, forward=True, failsSpeciesConstraints=failsSpeciesConstraints))
14061386

14071387
if self.ownReverse:
14081388
# for each reaction, make its reverse reaction and store in a 'reverse' attribute
14091389
for rxn in reactionList:
1410-
reactions = self.__generateReactions(rxn.products, products=rxn.reactants, forward=True, **options)
1390+
reactions = self.__generateReactions(rxn.products, products=rxn.reactants, forward=True, failsSpeciesConstraints=failsSpeciesConstraints)
14111391
if len(reactions) != 1:
14121392
logging.error("Expecting one matching reverse reaction, not {0} in reaction family {1} for forward reaction {2}.\n".format(len(reactions), self.label, str(rxn)))
14131393
for reactant in rxn.reactants:
@@ -1421,7 +1401,7 @@ def generateReactions(self, reactants, **options):
14211401

14221402
else: # family is not ownReverse
14231403
# Reverse direction (the direction in which kinetics is not defined)
1424-
reactionList.extend(self.__generateReactions(reactants, forward=False, **options))
1404+
reactionList.extend(self.__generateReactions(reactants, forward=False, failsSpeciesConstraints=failsSpeciesConstraints))
14251405

14261406
# Return the reactions as containing Species objects, not Molecule objects
14271407
for reaction in reactionList:
@@ -1450,14 +1430,16 @@ def calculateDegeneracy(self, reaction):
14501430
raise Exception('Unable to calculate degeneracy for reaction {0} in reaction family {1}.'.format(reaction, self.label))
14511431
return reactions[0].degeneracy
14521432

1453-
def __generateReactions(self, reactants, products=None, forward=True, **options):
1433+
def __generateReactions(self, reactants, products=None, forward=True, failsSpeciesConstraints=None):
14541434
"""
14551435
Generate a list of all of the possible reactions of this family between
14561436
the list of `reactants`. The number of reactants provided must match
14571437
the number of reactants expected by the template, or this function
14581438
will return an empty list. Each item in the list of reactants should
14591439
be a list of :class:`Molecule` objects, each representing a resonance
14601440
isomer of the species of interest.
1441+
`failsSpeciesConstraints` is an optional function that accepts a :class:`Molecule`
1442+
structure and returns `True` if it is forbidden.
14611443
"""
14621444

14631445
rxnList = []; speciesList = []
@@ -1493,7 +1475,7 @@ def __generateReactions(self, reactants, products=None, forward=True, **options)
14931475
for map in mappings:
14941476
reactantStructures = [molecule]
14951477
try:
1496-
productStructuresList = self.__generateProductStructures(reactantStructures, [map], forward, **options)
1478+
productStructuresList = self.__generateProductStructures(reactantStructures, [map], forward, failsSpeciesConstraints=failsSpeciesConstraints)
14971479
except ForbiddenStructureException:
14981480
pass
14991481
else:
@@ -1521,7 +1503,7 @@ def __generateReactions(self, reactants, products=None, forward=True, **options)
15211503
for mapB in mappingsB:
15221504
reactantStructures = [moleculeA, moleculeB]
15231505
try:
1524-
productStructuresList = self.__generateProductStructures(reactantStructures, [mapA, mapB], forward, **options)
1506+
productStructuresList = self.__generateProductStructures(reactantStructures, [mapA, mapB], forward, failsSpeciesConstraints=failsSpeciesConstraints)
15251507
except ForbiddenStructureException:
15261508
pass
15271509
else:
@@ -1542,7 +1524,7 @@ def __generateReactions(self, reactants, products=None, forward=True, **options)
15421524
for mapB in mappingsB:
15431525
reactantStructures = [moleculeA, moleculeB]
15441526
try:
1545-
productStructuresList = self.__generateProductStructures(reactantStructures, [mapA, mapB], forward, **options)
1527+
productStructuresList = self.__generateProductStructures(reactantStructures, [mapA, mapB], forward, failsSpeciesConstraints=failsSpeciesConstraints)
15461528
except ForbiddenStructureException:
15471529
pass
15481530
else:

rmgpy/data/rmg.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,20 @@ def loadKinetics(self,
139139
`path` points to the top-level folder of the RMG kinetics database.
140140
"""
141141
kineticsLibraries = []
142-
if reactionLibraries is not None and seedMechanisms is not None:
143-
kineticsLibraries.extend(seedMechanisms)
144-
kineticsLibraries.extend(reactionLibraries)
145-
elif reactionLibraries is not None and seedMechanisms is None:
146-
kineticsLibraries.extend(reactionLibraries)
147-
elif reactionLibraries is None and seedMechanisms is not None:
148-
kineticsLibraries.extend(seedMechanisms)
149-
else:
142+
libraryOrder = []
143+
if seedMechanisms is None and reactionLibraries is None:
150144
kineticsLibraries = None
151-
145+
if seedMechanisms is not None:
146+
for library in seedMechanisms:
147+
kineticsLibraries.append(library)
148+
libraryOrder.append((library,'Seed'))
149+
if reactionLibraries is not None:
150+
for library in reactionLibraries:
151+
kineticsLibraries.append(library)
152+
libraryOrder.append((library,'Reaction Library'))
153+
152154
self.kinetics = KineticsDatabase()
155+
self.kinetics.libraryOrder = libraryOrder
153156
self.kinetics.load(path,
154157
families=kineticsFamilies,
155158
libraries=kineticsLibraries,

rmgpy/rmg/input.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ def options(units='si', saveRestartPeriod=None, drawMolecules=False, generatePlo
253253

254254
def generatedSpeciesConstraints(**kwargs):
255255
validConstraints = [
256+
'allowed',
256257
'maximumCarbonAtoms',
257258
'maximumHydrogenAtoms',
258259
'maximumOxygenAtoms',
@@ -266,7 +267,7 @@ def generatedSpeciesConstraints(**kwargs):
266267
for key, value in kwargs.items():
267268
if key not in validConstraints:
268269
raise InputError('Invalid generated species constraint {0!r}.'.format(key))
269-
rmg.reactionGenerationOptions[key] = value
270+
rmg.speciesConstraints[key] = value
270271

271272
################################################################################
272273

0 commit comments

Comments
 (0)