Skip to content

Commit

Permalink
Merge pull request #195 from connie/master
Browse files Browse the repository at this point in the history
Change input file's behavior of loading families through `kineticFamilies` field, and various fixes.

There's a lot more error checking now, and the error checking is tested in a unit test!
  • Loading branch information
rwest committed Apr 3, 2014
2 parents 7f6d934 + aa881be commit 5b5349c
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 24 deletions.
2 changes: 1 addition & 1 deletion examples/rmg/minimal/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
reactionLibraries = [],
seedMechanisms = [],
kineticsDepositories = ['training'],
kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O'],
kineticsFamilies = 'default',
kineticsEstimator = 'rate rules',
)

Expand Down
92 changes: 74 additions & 18 deletions rmgpy/data/kinetics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
from rmgpy.molecule import Molecule, Group
from rmgpy.species import Species
from rmgpy.reaction import Reaction
from rmgpy.data.base import LogicNode
from rmgpy.data.base import LogicNode, DatabaseError

from .common import KineticsError, saveEntry
from .depository import DepositoryReaction, KineticsDepository
Expand All @@ -59,6 +59,7 @@ class KineticsDatabase(object):
"""

def __init__(self):
self.recommendedFamilies = {}
self.families = {}
self.libraries = {}
self.libraryOrder = []
Expand Down Expand Up @@ -101,34 +102,88 @@ def load(self, path, families=None, libraries=None, depositories=None):
Load the kinetics database from the given `path` on disk, where `path`
points to the top-level folder of the families database.
"""
self.loadRecommendedFamiliesList(os.path.join(path, 'families', 'recommended.py')),
self.loadFamilies(os.path.join(path, 'families'), families, depositories)
self.loadLibraries(os.path.join(path, 'libraries'), libraries)

def loadRecommendedFamiliesList(self, filepath):
"""
Load the list of recommended families from the given file
The file is usually 'kinetics/families/recommended.py'.
This is stored as a dictionary of True or False values (checked here),
and should contain entries for every available family (checked in loadFamilies).
"""
try:
global_context = {}
global_context['__builtins__'] = None
global_context['True'] = True
global_context['False'] = False
local_context = {}
local_context['__builtins__'] = None
f = open(filepath, 'r')
exec f in global_context, local_context
f.close()
self.recommendedFamilies = local_context['recommendedFamilies']
except:
raise DatabaseError('Error while reading list of recommended families from {0}/recommended.py.'.format(path))
for recommended in self.recommendedFamilies.values():
if not isinstance(recommended, bool):
raise DatabaseError("recommendedFamilies dictionary should contain only True or False values")

def loadFamilies(self, path, families=None, depositories=None):
"""
Load the kinetics families from the given `path` on disk, where `path`
points to the top-level folder of the kinetics families.
"""
logging.info('Loading kinetics families from {0}'.format(path))

familiesToLoad = []
for (root, dirs, files) in os.walk(os.path.join(path)):
if root == path:
if families is None or families == 'all':
# All families are loaded by default
for d in dirs:
break # all we wanted was the list of dirs in the base path

if families == 'default':
logging.info('Loading default kinetics families from {0}'.format(path))
for d in dirs: # load them in directory listing order, like other methods (better than a random dict order)
try:
recommended = self.recommendedFamilies[d]
except KeyError:
raise DatabaseError('Family {0} not found in recommendation list (probably at {1}/recommended.py)'.format(d, path))
if recommended:
familiesToLoad.append(d)
for label, value in self.recommendedFamilies.iteritems():
if label not in dirs:
raise DatabaseError('Family {0} found (in {1}/recommended.py) not found on disk.'.format(label, path))

elif families == 'all':
# All families are loaded
logging.info('Loading all of the kinetics families from {0}'.format(path))
for d in dirs:
familiesToLoad.append(d)
elif families == 'none':
logging.info('Not loading any of the kinetics families from {0}'.format(path))
# Don't load any of the families
familiesToLoad = []
elif isinstance(families, list):
logging.info('Loading the user-specified kinetics families from {0}'.format(path))
# If all items in the list start with !, all families will be loaded except these
if len(families) == 0:
raise DatabaseError('Kinetics families should be a non-empty list, or set to `default`, `all`, or `none`.')
elif all([label.startswith('!') for label in families]):
for d in dirs:
if '!{0}'.format(d) not in families:
familiesToLoad.append(d)
elif any([label.startswith('!') for label in families]):
raise DatabaseError('Families list must either all or none have prefix "!", but not a mix.')
else: # only the families given will be loaded
for d in dirs:
if d in families:
familiesToLoad.append(d)
elif isinstance(families, list) or isinstance(families, tuple):
# If all items in the list start with !, all families will be loaded except these
if all([label.startswith('!') for label in families]):
for d in dirs:
if '!{0}'.format(d) not in families:
familiesToLoad.append(d)
# Otherwise only the families given will be loaded
else:
for d in dirs:
if d in families:
familiesToLoad.append(d)
for label in families:
if label not in dirs:
raise DatabaseError('Family {0} not found on disk.'.format(label))
else:
raise DatabaseError('Kinetics families was not specified properly. Should be set to `default`,`all`,`none`, or a list.')

# Now we know what families to load, so let's load them
self.families = {}
Expand Down Expand Up @@ -265,14 +320,15 @@ def saveOld(self, path):
// Families can be deactivated by simply changing the "on/off" column to off.
//
// This file was generated by exporting the entirety of RMG-Py,
// so has all families on by default. You may want to turn some off.
// so the on/off decisions come from the defaults there.
//
////////////////////////////////////////////////////////////////////////////////
// No. on/off Forward reaction
""")
for number, label in enumerate(sorted(self.families.keys())):
f.write("{num:<2d} on {label}\n".format(num=number, label=label))
onoff = 'on ' if self.recommendedFamilies[label] else 'off'
f.write("{num:<2d} {onoff} {label}\n".format(num=number, label=label, onoff=onoff))

def generateReactions(self, reactants, products=None, **options):
"""
Expand Down
26 changes: 26 additions & 0 deletions rmgpy/data/kinetics/kineticsTest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import os
import unittest
from external.wip import work_in_progress

from rmgpy import settings
from rmgpy.data.kinetics import *
from rmgpy.data.base import DatabaseError
###################################################

class TestKineticsDatabase(unittest.TestCase):

def testLoadFamilies(self):
"""
Test that the loadFamilies function raises the correct exceptions
"""
path = os.path.join(settings['database.directory'],'kinetics','families')
database = KineticsDatabase()

with self.assertRaises(DatabaseError):
database.loadFamilies(path, families='random')
with self.assertRaises(DatabaseError):
database.loadFamilies(path, families=['!H_Abstraction','Disproportionation'])
with self.assertRaises(DatabaseError):
database.loadFamilies(path, families=['fake_family'])
with self.assertRaises(DatabaseError):
database.loadFamilies(path, families=[])
8 changes: 3 additions & 5 deletions rmgpy/rmg/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,10 @@ def database(
else:
assert isinstance(kineticsDepositories,list), "kineticsDepositories should be either 'default', 'all', or a list of names eg. ['training','PrIMe']."
rmg.kineticsDepositories = kineticsDepositories
if kineticsFamilies == 'default':
pass
elif kineticsFamilies == 'all':
pass
if kineticsFamilies in ('default', 'all', 'none'):
rmg.kineticsFamilies = kineticsFamilies
else:
assert isinstance(kineticsFamilies,list), "kineticsFamilies should be either 'default', 'all', or a list of names eg. ['H_Abstraction','!Intra_Disproportionation']."
assert isinstance(kineticsFamilies,list), "kineticsFamilies should be either 'default', 'all', 'none', or a list of names eg. ['H_Abstraction','R_Recombination'] or ['!Intra_Disproportionation']."
rmg.kineticsFamilies = kineticsFamilies

def species(label, structure, reactive=True):
Expand Down

0 comments on commit 5b5349c

Please sign in to comment.