def get_db(name=''): """ Returns the RMG database object that corresponds to the parameter name. First, the module level is queried. If this variable is empty, the broadcasted variables are queried. """ global database if database: if name == '': return database elif name == 'kinetics': return database.kinetics elif name == 'thermo': return database.thermo elif name == 'transport': return database.transport elif name == 'solvation': return database.solvation elif name == 'statmech': return database.statmech elif name == 'forbidden': return database.forbidden_structures else: raise ValueError('Unrecognized database keyword: {}'.format(name)) raise DatabaseError('Could not get database with name: {}'.format(name))
def load_entry(self, index, label, group, kinetics, reference=None, referenceType='', shortDesc='', longDesc='', nodalDistance=None): """ Method for parsing entries in database files. Note that these argument names are retained for backward compatibility. nodal_distance is the distance between a given entry and its parent specified by a float """ if (group[0:3].upper() == 'OR{' or group[0:4].upper() == 'AND{' or group[0:7].upper() == 'NOT OR{' or group[0:8].upper() == 'NOT AND{'): item = make_logic_node(group) else: item = Group().from_adjacency_list(group) if label in self.entries: raise DatabaseError("Duplicate group name {label} found in kinetics groups for {family} " "family.".format(label=label, family=self.label)) self.entries[label] = Entry( index=index, label=label, item=item, data=kinetics, reference=reference, reference_type=referenceType, short_desc=shortDesc, long_desc=longDesc.strip(), nodal_distance=nodalDistance )
def loadEntry(self, index, label, group, kinetics, reference=None, referenceType='', shortDesc='', longDesc='', nodalDistance=None): """ nodalDistance is the distance between a given entry and its parent specified by a float """ if group[0:3].upper() == 'OR{' or group[0:4].upper( ) == 'AND{' or group[0:7].upper() == 'NOT OR{' or group[0:8].upper( ) == 'NOT AND{': item = makeLogicNode(group) else: item = Group().fromAdjacencyList(group) if label in self.entries: raise DatabaseError( "Duplicate group name {label} found in kinetics groups for {family} family." .format(label=label, family=self.label)) self.entries[label] = Entry(index=index, label=label, item=item, data=kinetics, reference=reference, referenceType=referenceType, shortDesc=shortDesc, longDesc=longDesc.strip(), nodalDistance=nodalDistance)
def loadOld(self, path, groups, numLabels): """ Load a set of old rate rules for kinetics groups into this depository. """ warnings.warn("The old kinetics databases are no longer supported and may be" " removed in version 2.3.", DeprecationWarning) # Parse the old library entries = self.parseOldLibrary(os.path.join(path, 'rateLibrary.txt'), numParameters=10, numLabels=numLabels) self.entries = {} for entry in entries: index, label, data, shortDesc = entry if isinstance(data, (str,unicode)): kinetics = data rank = 0 elif isinstance(data, tuple) and len(data) == 2: kinetics, rank = data else: raise DatabaseError('Unexpected data {0!r} for entry {1!s}.'.format(data, entry)) reactants = [groups.entries[l].item for l in label.split(';')] item = Reaction(reactants=reactants, products=[]) entry = Entry( index = index, label = label, item = item, data = kinetics, rank = rank, shortDesc = shortDesc ) try: self.entries[label].append(entry) except KeyError: self.entries[label] = [entry] self.__loadOldComments(path)
def load_recommended_families(self, filepath): """ Load the recommended families from the given file. The file is usually stored at 'kinetics/families/recommended.py'. The old style was as a dictionary named `recommendedFamilies` containing all family names as keys with True/False values. The new style is as multiple sets with unique names which can be used individually or in combination. Both styles can be loaded by this method. """ import imp # Load the recommended.py file as a module try: rec = imp.load_source('rec', filepath) except Exception as e: raise DatabaseError('Unable to load recommended.py file for kinetics families: {0!s}'.format(e)) # For backward compatibility, check for old-style recommendedFamilies dictionary if hasattr(rec, 'recommendedFamilies'): default = set() for family, recommended in rec.recommendedFamilies.items(): if recommended: default.add(family) self.recommended_families = {'default': default} else: self.recommended_families = {name: value for name, value in rec.__dict__.items() if not name.startswith('_')}
def test_write_entry_to_database(self): """Test we can write an entry to the database""" test_entry = Entry( index=100, label="Me111", binding_energies={ 'H': Energy(0., 'eV/molecule'), 'C': Energy(0., 'eV/molecule'), 'N': Energy(0., 'eV/molecule'), 'O': Energy(0., 'eV/molecule'), }, surface_site_density=SurfaceConcentration(0., 'mol/cm^2'), facet="111", metal="Me", short_desc=u"fcc", long_desc=u""" Test """, ) MetalLib = self.database.libraries['surface'] self.database.add_entry(test_entry) # test to see if the entry was added self.assertEqual( repr(self.database.get_binding_energies(test_entry.label)), repr(test_entry.binding_energies)) self.assertEqual( repr(self.database.get_surface_site_density(test_entry.label)), repr(test_entry.surface_site_density)) # write the new entry self.database.save( os.path.join(settings['database.directory'], 'surface')) # MetalLib.save_entry(os.path.join(settings['database.directory'], 'surface/libraries/metal.py'), test_entry) # test to see if entry was written with open( os.path.join(settings['database.directory'], 'surface/libraries/metal.py'), "r") as f: if "Me111" in f.read(): self.database.remove_entry(test_entry) self.database.save( os.path.join(settings['database.directory'], 'surface')) else: raise DatabaseError("Unable to write entry to database.")
def getDB(name=''): """ Returns the RMG database object that corresponds to the parameter name. First, the module level is queried. If this variable is empty, the broadcasted variables are queried. """ global database if database: if name == '': return database elif name == 'kinetics': return database.kinetics elif name == 'thermo': return database.thermo elif name == 'transport': return database.transport elif name == 'solvation': return database.solvation elif name == 'statmech': return database.statmech elif name == 'forbidden': return database.forbiddenStructures else: raise Exception('Unrecognized database keyword: {}'.format(name)) else: try: db = get(name) if db: return db else: raise DatabaseError except DatabaseError: logging.debug( "Did not find a way to obtain the broadcasted database for {}." .format(name)) raise raise DatabaseError('Could not get database with name: {}'.format(name))
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 Exception, e: raise DatabaseError( 'Error while reading list of recommended families from {0}/recommended.py.\n{1}' .format(filepath, e))
def get_reaction_template(self, reaction): """ For a given `reaction` with properly-labeled :class:`Molecule` objects as the reactants, determine the most specific nodes in the tree that describe the reaction. """ # Get forward reaction template and remove any duplicates forward_template = self.top[:] temporary = [] symmetric_tree = False for entry in forward_template: if entry not in temporary: temporary.append(entry) else: # duplicate node found at top of tree # eg. R_recombination: ['Y_rad', 'Y_rad'] if len(forward_template) != 2: raise DatabaseError('Can currently only do symmetric trees with nothing else in them') symmetric_tree = True forward_template = temporary # Descend reactant trees as far as possible template = [] special_cases = ['peroxyl_disproportionation', 'bimolec_hydroperoxide_decomposition'] if (len(forward_template) == 1 and len(reaction.reactants) > len(forward_template) and self.label.lower().split('/')[0] not in special_cases): entry = forward_template[0] group = entry.item r = None for react in reaction.reactants: if isinstance(react, Species): react = react.molecule[0] if r: r = r.merge(react) else: r = deepcopy(react) atoms = r.get_all_labeled_atoms() matched_node = self.descend_tree(r, atoms, root=entry, strict=True) if matched_node is not None: template.append(matched_node) else: for entry in forward_template: # entry is a top-level node that should be matched group = entry.item # Identify the atom labels in a group if it is not a logical node atom_list = [] if not isinstance(entry.item, LogicNode): atom_list = group.get_all_labeled_atoms() for reactant in reaction.reactants: if isinstance(reactant, Species): reactant = reactant.molecule[0] # Match labeled atoms # Check that this reactant has each of the atom labels in this group. # If it is a LogicNode, the atom_list is empty and # it will proceed directly to the descend_tree step. if not all([reactant.contains_labeled_atom(label) for label in atom_list]): continue # don't try to match this structure - the atoms aren't there! # Match structures atoms = reactant.get_all_labeled_atoms() # Descend the tree, making sure to match atomlabels exactly using strict = True matched_node = self.descend_tree(reactant, atoms, root=entry, strict=True) if matched_node is not None: template.append(matched_node) # else: # logging.warning("Couldn't find match for {0} in {1}".format(entry,atom_list)) # logging.warning(reactant.to_adjacency_list()) # Get fresh templates (with duplicate nodes back in) forward_template = self.top[:] if (self.label.lower().startswith('peroxyl_disproportionation') or self.label.lower().startswith('bimolec_hydroperoxide_decomposition')): forward_template.append(forward_template[0]) # Check that we were able to match the template. # template is a list of the actual matched nodes # forward_template is a list of the top level nodes that should be matched if len(template) != len(forward_template): msg = 'Unable to find matching template for reaction {0} in reaction family {1}.'.format(str(reaction), str(self)) msg += 'Trying to match {0} but matched {1}'.format(str(forward_template), str(template)) raise UndeterminableKineticsError(reaction, message=msg) return template
def saveEntry(f, entry): """ Save an `entry` in the kinetics database by writing a string to the given file object `f`. """ from arkane.output import prettify def sortEfficiencies(efficiencies0): efficiencies = {} for mol, eff in efficiencies0.iteritems(): if isinstance(mol, str): # already in SMILES string format smiles = mol else: smiles = mol.toSMILES() efficiencies[smiles] = eff keys = efficiencies.keys() keys.sort() return [(key, efficiencies[key]) for key in keys] f.write('entry(\n') f.write(' index = {0:d},\n'.format(entry.index)) if entry.label != '': f.write(' label = "{0}",\n'.format(entry.label)) #Entries for kinetic rules, libraries, training reactions #and depositories will have a Reaction object for its item if isinstance(entry.item, Reaction): #Write out additional data if depository or library #kinetic rules would have a Group object for its reactants instead of Species if isinstance(entry.item.reactants[0], Species): # Add degeneracy if the reaction is coming from a depository or kinetics library f.write(' degeneracy = {0:.1f},\n'.format(entry.item.degeneracy)) if entry.item.duplicate: f.write(' duplicate = {0!r},\n'.format(entry.item.duplicate)) if not entry.item.reversible: f.write(' reversible = {0!r},\n'.format(entry.item.reversible)) if entry.item.allow_pdep_route: f.write(' allow_pdep_route = {0!r},\n'.format(entry.item.allow_pdep_route)) if entry.item.elementary_high_p: f.write(' elementary_high_p = {0!r},\n'.format(entry.item.elementary_high_p)) if entry.item.allow_max_rate_violation: f.write(' allow_max_rate_violation = {0!r},\n'.format(entry.item.allow_max_rate_violation)) #Entries for groups with have a group or logicNode for its item elif isinstance(entry.item, Group): f.write(' group = \n') f.write('"""\n') f.write(entry.item.toAdjacencyList()) f.write('""",\n') elif isinstance(entry.item, LogicNode): f.write(' group = "{0}",\n'.format(entry.item)) else: raise DatabaseError("Encountered unexpected item of type {0} while saving database.".format(entry.item.__class__)) # Write kinetics if isinstance(entry.data, str): f.write(' kinetics = "{0}",\n'.format(entry.data)) elif entry.data is not None: efficiencies = None if hasattr(entry.data, 'efficiencies'): efficiencies = entry.data.efficiencies entry.data.efficiencies = dict(sortEfficiencies(entry.data.efficiencies)) kinetics = prettify(repr(entry.data)) kinetics = ' kinetics = {0},\n'.format(kinetics.replace('\n', '\n ')) f.write(kinetics) if hasattr(entry.data, 'efficiencies'): entry.data.efficiencies = efficiencies else: f.write(' kinetics = None,\n') # Write reference if entry.reference is not None: reference = entry.reference.toPrettyRepr() lines = reference.splitlines() f.write(' reference = {0}\n'.format(lines[0])) for line in lines[1:-1]: f.write(' {0}\n'.format(line)) f.write(' ),\n'.format(lines[0])) if entry.referenceType != "": f.write(' referenceType = "{0}",\n'.format(entry.referenceType)) if entry.rank is not None: f.write(' rank = {0},\n'.format(entry.rank)) if entry.shortDesc.strip() !='': f.write(' shortDesc = u"""') try: f.write(entry.shortDesc.encode('utf-8')) except: f.write(entry.shortDesc.strip().encode('ascii', 'ignore')+ "\n") f.write('""",\n') if entry.longDesc.strip() !='': f.write(' longDesc = \n') f.write('u"""\n') try: f.write(entry.longDesc.strip().encode('utf-8') + "\n") except: f.write(entry.longDesc.strip().encode('ascii', 'ignore')+ "\n") f.write('""",\n') f.write(')\n\n')
if database: if name == 'kinetics': return database.kinetics elif name == 'thermo': return database.thermo elif name == 'transport': return database.transport elif name == 'solvation': return database.solvation elif name == 'statmech': return database.statmech elif name == 'forbidden': return database.forbiddenStructures else: raise Exception('Unrecognized database keyword: {}'.format(name)) else: try: db = get(name) if db: return db else: raise DatabaseError except DatabaseError, e: logging.debug( "Did not find a way to obtain the broadcasted database for {}." .format(name)) raise e raise DatabaseError('Could not get database with name: {}'.format(name))
def species(label, *args, **kwargs): """Load a species from an input file""" global species_dict, job_list if label in species_dict: raise ValueError( 'Multiple occurrences of species with label {0!r}.'.format(label)) logging.info('Loading species {0}...'.format(label)) spec = Species(label=label) species_dict[label] = spec path = None if len(args) == 1: # The argument is a path to a conformer input file path = args[0] job = StatMechJob(species=spec, path=path) logging.debug('Added species {0} to a stat mech job.'.format(label)) job_list.append(job) elif len(args) > 1: raise InputError('species {0} can only have two non-keyword argument ' 'which should be the species label and the ' 'path to a quantum file.'.format(spec.label)) if len(kwargs) > 0: # The species parameters are given explicitly structure = None E0 = None modes = [] spin_multiplicity = 0 optical_isomers = 1 molecular_weight = None collision_model = None energy_transfer_model = None thermo = None reactive = True for key, value in kwargs.items(): if key == 'structure': structure = value elif key == 'E0': E0 = value elif key == 'modes': modes = value elif key == 'spinMultiplicity': spin_multiplicity = value elif key == 'opticalIsomers': optical_isomers = value elif key == 'molecularWeight': molecular_weight = value elif key == 'collisionModel': collision_model = value elif key == 'energyTransferModel': energy_transfer_model = value elif key == 'thermo': thermo = value elif key == 'reactive': reactive = value else: raise TypeError( 'species() got an unexpected keyword argument {0!r}.'. format(key)) if structure: spec.molecule = [structure] spec.conformer = Conformer(E0=E0, modes=modes, spin_multiplicity=spin_multiplicity, optical_isomers=optical_isomers) if molecular_weight is not None: spec.molecular_weight = molecular_weight elif spec.molecular_weight is None and is_pdep(job_list): # If a structure was given, simply calling spec.molecular_weight will calculate the molecular weight # If one of the jobs is pdep and no molecular weight is given or calculated, raise an error raise ValueError( "No molecularWeight was entered for species {0}. Since a structure wasn't given" " as well, the molecularWeight, which is important for pressure dependent jobs," " cannot be reconstructed.".format(spec.label)) spec.transport_data = collision_model spec.energy_transfer_model = energy_transfer_model spec.thermo = thermo spec.reactive = reactive if spec.reactive and path is None and spec.thermo is None and spec.conformer.E0 is None: if not spec.molecule: raise InputError( 'Neither thermo, E0, species file path, nor structure specified, cannot estimate' ' thermo properties of species {0}'.format(spec.label)) try: db = get_db('thermo') if db is None: raise DatabaseError('Thermo database is None.') except DatabaseError: logging.warning( "The database isn't loaded, cannot estimate thermo for {0}. " "If it is a bath gas, set reactive = False to avoid generating " "thermo.".format(spec.label)) else: logging.info( 'No E0 or thermo found, estimating thermo and E0 of species {0} using' ' RMG-Database...'.format(spec.label)) spec.thermo = db.get_thermo_data(spec) if spec.thermo.E0 is None: th = spec.thermo.to_wilhoit() spec.conformer.E0 = th.E0 spec.thermo.E0 = th.E0 else: spec.conformer.E0 = spec.thermo.E0 if spec.reactive and spec.thermo and not spec.has_statmech( ) and structure is not None: # generate stat mech info if it wasn't provided before spec.generate_statmech() if not energy_transfer_model: # default to RMG's method of generating energy_transfer_model spec.generate_energy_transfer_model() return spec
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. The `families` argument accepts a single item or list of the following: - Specific kinetics family labels - Names of family sets defined in recommended.py - 'all' - 'none' If all items begin with a `!` (e.g. ['!H_Abstraction']), then the selection will be inverted to families NOT in the list. """ for (root, dirs, files) in os.walk(os.path.join(path)): if root == path: break # all we wanted was the list of dirs in the base path all_families = set(dirs) # Convert input to a list to simplify processing if not isinstance(families, list): families = [families] # Check for ! syntax, which allows specification of undesired families if all(item.startswith('!') for item in families): inverse = True families = [family[1:] for family in families] elif not any(item.startswith('!') for item in families): inverse = False else: raise DatabaseError( 'Families list must either all or none have prefix "!", but not a mix.' ) # Compile the set of selected families selected_families = set() for item in families: if item.lower() == 'all': selected_families = all_families break elif item.lower() == 'none': selected_families = set() break elif item in self.recommendedFamilies: family_set = self.recommendedFamilies[item] missing_fams = [ fam for fam in family_set if fam not in all_families ] if missing_fams: raise DatabaseError( 'Unable to load recommended set "{0}", ' 'some families could not be found: {1}'.format( item, missing_fams)) selected_families.update(family_set) elif item in all_families: selected_families.add(item) else: raise DatabaseError( 'Unrecognized item "{0}" in list of families to load. ' 'Items should be names of kinetics families, ' 'a predefined subset from recommended.py, ' 'or the "all" or "none" options.'.format(item)) # If families were specified using ! syntax, invert the selection if inverse: selected_families = all_families - selected_families # Sort alphabetically for consistency, this also converts to a list selected_families = sorted(selected_families) # Now we know what families to load, so let's load them self.families = {} for label in selected_families: familyPath = os.path.join(path, label) family = KineticsFamily(label=label) family.load(familyPath, self.local_context, self.global_context, depositoryLabels=depositories) self.families[label] = family
class KineticsDatabase(object): """ A class for working with the RMG kinetics database. """ def __init__(self): self.recommendedFamilies = {} self.families = {} self.libraries = {} self.libraryOrder = [ ] # a list of tuples in the format ('library_label', LibraryType), # where LibraryType is set to either 'Reaction Library' or 'Seed'. self.local_context = { 'KineticsData': KineticsData, 'Arrhenius': Arrhenius, 'ArrheniusEP': ArrheniusEP, 'MultiArrhenius': MultiArrhenius, 'MultiPDepArrhenius': MultiPDepArrhenius, 'PDepArrhenius': PDepArrhenius, 'Chebyshev': Chebyshev, 'ThirdBody': ThirdBody, 'Lindemann': Lindemann, 'Troe': Troe, 'R': constants.R, } self.global_context = {} def __reduce__(self): """ A helper function used when pickling a KineticsDatabase object. """ d = { 'families': self.families, 'libraries': self.libraries, 'libraryOrder': self.libraryOrder, } return (KineticsDatabase, (), d) def __setstate__(self, d): """ A helper function used when unpickling a KineticsDatabase object. """ self.families = d['families'] self.libraries = d['libraries'] self.libraryOrder = d['libraryOrder'] 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 Exception, e: raise DatabaseError( 'Error while reading list of recommended families from {0}/recommended.py.\n{1}' .format(filepath, e)) 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. """ familiesToLoad = [] for (root, dirs, files) in os.walk(os.path.join(path)): if root == path: 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) 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 = {} for label in familiesToLoad: familyPath = os.path.join(path, label) family = KineticsFamily(label=label) family.load(familyPath, self.local_context, self.global_context, depositoryLabels=depositories) self.families[label] = family