diff --git a/examples/rmg/minimal/input.py b/examples/rmg/minimal/input.py index 5a2effffbc..93dd6d1c26 100644 --- a/examples/rmg/minimal/input.py +++ b/examples/rmg/minimal/input.py @@ -4,7 +4,7 @@ reactionLibraries = [], seedMechanisms = [], kineticsDepositories = ['training'], - kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O'], + kineticsFamilies = 'default', kineticsEstimator = 'rate rules', ) diff --git a/rmgpy/data/kinetics/__init__.py b/rmgpy/data/kinetics/__init__.py index 8935ff041f..77100efabd 100644 --- a/rmgpy/data/kinetics/__init__.py +++ b/rmgpy/data/kinetics/__init__.py @@ -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 @@ -59,6 +59,7 @@ class KineticsDatabase(object): """ def __init__(self): + self.recommendedFamilies = {} self.families = {} self.libraries = {} self.libraryOrder = [] @@ -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 = {} @@ -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): """ diff --git a/rmgpy/data/kinetics/kineticsTest.py b/rmgpy/data/kinetics/kineticsTest.py new file mode 100644 index 0000000000..1d7d70beb4 --- /dev/null +++ b/rmgpy/data/kinetics/kineticsTest.py @@ -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=[]) \ No newline at end of file diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index 4f00636602..f3ccd43d21 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -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):