def correctDegeneracyOfReverseReactions(reactionList, reactants): """ This method corrects the degeneracy of reactions found when the backwards template is used. Given the following parameters: reactionList - list of reactions with their degeneracies already counted reactants - list/tuple of species used in the generateReactions method This method modifies reactionList in place and returns nothing This does not adjust for identical reactants, you need to use `reduceSameReactantDegeneracy` to adjust for that. """ from rmgpy.reaction import _isomorphicSpeciesList from rmgpy.reaction import ReactionError for rxn in reactionList: if _isomorphicSpeciesList(rxn.reactants, reactants): # was forward reaction so ignore continue elif _isomorphicSpeciesList(rxn.products, reactants): # was reverse reaction so should find degeneracy family = getDB('kinetics').families[rxn.family] if not family.ownReverse: rxn.degeneracy = family.calculateDegeneracy( rxn, ignoreSameReactants=True) else: # wrong reaction was sent here raise ReactionError( 'Reaction in reactionList did not match reactants. Reaction: {}, Reactants: {}' .format(rxn, reactants))
def generateStatMech(self): """ Generate molecular degree of freedom data for the species. You must have already provided a thermodynamics model using e.g. :meth:`generateThermoData()`. """ logging.debug("Generating statmech for species {}".format(self.label)) from rmgpy.data.rmg import getDB try: statmechDB = getDB('statmech') if not statmechDB: raise Exception except Exception: logging.debug('Could not obtain the stat. mech database. Not generating stat. mech...') raise molecule = self.molecule[0] conformer = statmechDB.getStatmechData(molecule, self.getThermoData()) if self.conformer is None: self.conformer = Conformer() if self.conformer.E0 is None: self.setE0WithThermo() self.conformer.modes = conformer.modes self.conformer.spinMultiplicity = conformer.spinMultiplicity if self.conformer.E0 is None or not self.hasStatMech(): from rmgpy.exceptions import StatmechError logging.error('The conformer in question is {}'.format(self.conformer)) raise StatmechError('Species {0} does not have stat mech after generateStatMech called'.format(self.label))
def reactMolecules(moleculeTuples): """ Performs a reaction between the resonance isomers. The parameter contains a list of tuples with each tuple: (Molecule, index of the core species it belongs to) """ families = getDB('kinetics').families molecules, reactantIndices = zip(*moleculeTuples) reactionList = [] for _, family in families.iteritems(): rxns = family.generateReactions(molecules) reactionList.extend(rxns) for reactant in molecules: reactant.clearLabeledAtoms() for rxn in reactionList: deflate(rxn, molecules, reactantIndices) return reactionList
def test_calculate_degeneracy_for_non_reactive_molecule(self): """ tests that the calculateDegeneracy method gets the degeneracy correct for unreactive molecules and that __generateReactions work correctly with the react_non_reactive flag set to `True`. """ from rmgpy.data.rmg import getDB from rmgpy.data.kinetics.family import TemplateReaction adjlist = [ ''' multiplicity 2 1 H u1 p0 c0''', ''' multiplicity 2 1 O u1 p1 c+1 {2,D} 2 N u0 p2 c-1 {1,D}''', ''' 1 O u0 p1 c+1 {2,D} {3,S} 2 N u0 p2 c-1 {1,D} 3 H u0 p0 c0 {1,S}''' ] family = getDB('kinetics').families['R_Recombination'] r1 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[0])]) r2 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[1]) ]) # r2 is not the representative structure of # NO, but it is the correct structure participating in this reaction p1 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[2])]) r2.generate_resonance_structures(keep_isomorphic=True) rxn = TemplateReaction(reactants=[r1, r2], products=[p1]) rxn.degeneracy = family.calculateDegeneracy(rxn) self.assertEqual(rxn.degeneracy, 1)
def ensure_reaction_direction(isotopomerRxns): """ given a list of reactions with varying isotope labels but identical structure, obtained from the `cluster` method, this method remakes the kinetics so that they all face the same direction. """ # find isotopeless reaction as standard reference = isotopomerRxns[0] family = getDB('kinetics').families[reference.family] if family.ownReverse: for rxn in isotopomerRxns: if not compare_isotopomers(rxn, reference, eitherDirection=False): # the reaction is in the oposite direction logging.info('isotope: identified flipped reaction direction in reaction number {} of reaction {}. Altering the direction.'.format(rxn.index, str(rxn))) # obtain reverse attribute with template and degeneracy family.addReverseAttribute(rxn) if frozenset(rxn.reverse.template) != frozenset(reference.template): logging.warning("Reaction {} did not find proper reverse template, might cause degeneracy error.".format(str(rxn))) # reverse reactants and products of original reaction rxn.reactants, rxn.products = rxn.products, rxn.reactants rxn.pairs = [(p,r) for r,p in rxn.pairs] # set degeneracy to isotopeless reaction rxn.degeneracy = reference.degeneracy # make this reaction have kinetics of isotopeless reaction newKinetics = deepcopy(reference.kinetics) rxn.kinetics = newKinetics rxn.template = reference.template # set degeneracy to new reaction rxn.degeneracy = rxn.reverse.degeneracy #delete reverse attribute rxn.reverse = None
def react_all(core_spc_list, numOldCoreSpecies, unimolecularReact, bimolecularReact, trimolecularReact=None, procnum=1): """ Reacts the core species list via uni-, bi-, and trimolecular reactions and splits reaction families per task for improved load balancing in parallel runs. """ # Select reactive species that can undergo unimolecular reactions: spc_tuples = [(core_spc_list[i],) for i in xrange(numOldCoreSpecies) if (unimolecularReact[i] and core_spc_list[i].reactive)] for i in xrange(numOldCoreSpecies): for j in xrange(i, numOldCoreSpecies): # Find reactions involving the species that are bimolecular. # This includes a species reacting with itself (if its own concentration is high enough). if bimolecularReact[i, j]: if core_spc_list[i].reactive and core_spc_list[j].reactive: spc_tuples.append((core_spc_list[i], core_spc_list[j])) if trimolecularReact is not None: for i in xrange(numOldCoreSpecies): for j in xrange(i, numOldCoreSpecies): for k in xrange(j, numOldCoreSpecies): # Find reactions involving the species that are trimolecular. if trimolecularReact[i, j, k]: if core_spc_list[i].reactive and core_spc_list[j].reactive and core_spc_list[k].reactive: spc_tuples.append((core_spc_list[i], core_spc_list[j], core_spc_list[k])) if procnum == 1: # React all families like normal (provide empty argument for only_families) spc_fam_tuples = zip(spc_tuples) else: # Identify and split families that are prone to generate many reactions into sublists. family_list = getDB('kinetics').families.keys() major_families = [ 'H_Abstraction', 'R_Recombination', 'Intra_Disproportionation', 'Intra_RH_Add_Endocyclic', 'Singlet_Carbene_Intra_Disproportionation', 'Intra_ene_reaction', 'Disproportionation', '1,4_Linear_birad_scission', 'R_Addition_MultipleBond', '2+2_cycloaddition_Cd', 'Diels_alder_addition', 'Intra_RH_Add_Exocyclic', 'Intra_Retro_Diels_alder_bicyclic', 'Intra_2+2_cycloaddition_Cd', 'Birad_recombination', 'Intra_Diels_alder_monocyclic', '1,4_Cyclic_birad_scission', '1,2_Insertion_carbene', ] split_list = [] leftovers = [] for fam in family_list: if fam in major_families: split_list.append([fam]) else: leftovers.append(fam) split_list.append(leftovers) # Only employ family splitting for reactants that have a larger number than min_atoms min_atoms = 10 spc_fam_tuples = [] for i, spc_tuple in enumerate(spc_tuples): if any([len(spc.molecule[0].atoms) > min_atoms for spc in spc_tuple]): for item in split_list: spc_fam_tuples.append((spc_tuple, item)) else: spc_fam_tuples.append((spc_tuple, )) return list(react(spc_fam_tuples, procnum))
def test_calculate_degeneracy_for_non_reactive_molecule(self): """ tests that the calculateDegeneracy method gets the degeneracy correct for unreactive molecules and that __generateReactions work correctly with the react_non_reactive flag set to `True`. """ from rmgpy.data.rmg import getDB from rmgpy.data.kinetics.family import TemplateReaction adjlist = [''' multiplicity 2 1 H u1 p0 c0''', ''' multiplicity 2 1 O u1 p1 c+1 {2,D} 2 N u0 p2 c-1 {1,D}''', ''' 1 O u0 p1 c+1 {2,D} {3,S} 2 N u0 p2 c-1 {1,D} 3 H u0 p0 c0 {1,S}'''] family = getDB('kinetics').families['R_Recombination'] r1 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[0])]) r2 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[1])]) # r2 is not the representative structure of # NO, but it is the correct structure participating in this reaction p1 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[2])]) r2.generate_resonance_structures(keep_isomorphic=True) rxn = TemplateReaction(reactants=[r1, r2], products=[p1]) rxn.degeneracy = family.calculateDegeneracy(rxn) self.assertEqual(rxn.degeneracy, 1)
def database( thermoLibraries = None, transportLibraries = None, reactionLibraries = None, frequenciesLibraries = None, kineticsFamilies = 'default', kineticsDepositories = 'default', kineticsEstimator = 'rate rules', ): if isinstance(thermoLibraries, str): thermoLibraries = [thermoLibraries] if isinstance(transportLibraries, str): transportLibraries = [transportLibraries] if isinstance(reactionLibraries, str): reactionLibraries = [reactionLibraries] if isinstance(frequenciesLibraries, str): frequenciesLibraries = [frequenciesLibraries] databaseDirectory = settings['database.directory'] thermoLibraries = thermoLibraries or [] transportLibraries = transportLibraries reactionLibraries = reactionLibraries or [] kineticsEstimator = kineticsEstimator if kineticsDepositories == 'default': kineticsDepositories = ['training'] elif kineticsDepositories == 'all': kineticsDepositories = None else: if not isinstance(kineticsDepositories,list): raise InputError("kineticsDepositories should be either 'default', 'all', or a list of names eg. ['training','PrIMe'].") kineticsDepositories = kineticsDepositories if kineticsFamilies in ('default', 'all', 'none'): kineticsFamilies = kineticsFamilies else: if not isinstance(kineticsFamilies,list): raise InputError("kineticsFamilies should be either 'default', 'all', 'none', or a list of names eg. ['H_Abstraction','R_Recombination'] or ['!Intra_Disproportionation'].") kineticsFamilies = kineticsFamilies database = getDB() or RMGDatabase() database.load( path = databaseDirectory, thermoLibraries = thermoLibraries, transportLibraries = transportLibraries, reactionLibraries = reactionLibraries, seedMechanisms = [], kineticsFamilies = kineticsFamilies, kineticsDepositories = kineticsDepositories, depository = False, # Don't bother loading the depository information, as we don't use it ) for family in database.kinetics.families.values(): #load training family.addKineticsRulesFromTrainingSet(thermoDatabase=database.thermo) for family in database.kinetics.families.values(): family.fillKineticsRulesByAveragingUp(verbose=True)
def reaction(label, reactants, products, transitionState=None, kinetics=None, tunneling=''): global reactionDict, speciesDict, transitionStateDict #label = 'reaction'+transitionState if label in reactionDict: label = label+transitionState if label in reactionDict: raise ValueError('Multiple occurrences of reaction with label {0!r}.'.format(label)) logging.info('Loading reaction {0}...'.format(label)) reactants = sorted([speciesDict[spec] for spec in reactants]) products = sorted([speciesDict[spec] for spec in products]) if transitionState: transitionState = transitionStateDict[transitionState] if tunneling.lower() == 'wigner': transitionState.tunneling = Wigner(frequency=None) elif tunneling.lower() == 'eckart': transitionState.tunneling = Eckart(frequency=None, E0_reac=None, E0_TS=None, E0_prod=None) elif transitionState and (tunneling == '' or tunneling is None): transitionState.tunneling = None elif transitionState and not isinstance(tunneling, TunnelingModel): raise ValueError('Unknown tunneling model {0!r}.'.format(tunneling)) rxn = Reaction(label=label, reactants=reactants, products=products, transitionState=transitionState, kinetics=kinetics) if rxn.transitionState is None and rxn.kinetics is None: logging.info('estimating rate of reaction {0} using RMG-database') if not all([m.molecule != [] for m in rxn.reactants+rxn.products]): raise ValueError('chemical structures of reactants and products not available for RMG estimation of reaction {0}'.format(label)) for spc in rxn.reactants+rxn.products: print spc.label print spc.molecule db = getDB('kinetics') rxns = db.generate_reactions_from_libraries(reactants=rxn.reactants,products=rxn.products) rxns = [r for r in rxns if r.elementary_high_p] if rxns != []: for r in rxns: if isinstance(rxn.kinetics, PDepKineticsModel): boo = rxn.generate_high_p_limit_kinetics() if boo: rxn = r break if rxns == [] or not boo: logging.info('No library reactions tagged with elementary_high_p found for reaction {0}, generating reactions from RMG families'.format(label)) rxn = list(db.generate_reactions_from_families(reactants=rxn.reactants,products=rxn.products)) model = CoreEdgeReactionModel() model.verboseComments = True for r in rxn: model.applyKineticsToReaction(r) if isinstance(rxn,Reaction): reactionDict[label] = rxn else: for i in xrange(len(rxn)): reactionDict[label+str(i)] = rxn[i] return rxn
def loadNecessaryDatabases(): """ loads transport and statmech databases """ from rmgpy.data.statmech import StatmechDatabase from rmgpy.data.transport import TransportDatabase #only load if they are not there already. try: getDB('transport') getDB('statmech') except DatabaseError: logging.info("Databases not found. Making databases") db = RMGDatabase() db.statmech = StatmechDatabase() db.statmech.load(os.path.join(settings['database.directory'],'statmech')) db.transport = TransportDatabase() db.transport.load(os.path.join(settings['database.directory'],'transport'))
def reaction(label, reactants, products, transitionState=None, kinetics=None, tunneling=''): global reactionDict, speciesDict, transitionStateDict if label in reactionDict: label = label + transitionState if label in reactionDict: raise ValueError('Multiple occurrences of reaction with label {0!r}.'.format(label)) logging.info('Loading reaction {0}...'.format(label)) reactants = sorted([speciesDict[spec] for spec in reactants]) products = sorted([speciesDict[spec] for spec in products]) if transitionState: transitionState = transitionStateDict[transitionState] if tunneling.lower() == 'wigner': transitionState.tunneling = Wigner(frequency=None) elif tunneling.lower() == 'eckart': transitionState.tunneling = Eckart(frequency=None, E0_reac=None, E0_TS=None, E0_prod=None) elif transitionState and (tunneling == '' or tunneling is None): transitionState.tunneling = None elif transitionState and not isinstance(tunneling, TunnelingModel): raise ValueError('Unknown tunneling model {0!r}.'.format(tunneling)) rxn = Reaction(label=label, reactants=reactants, products=products, transitionState=transitionState, kinetics=kinetics) if rxn.transitionState is None and rxn.kinetics is None: logging.info('estimating rate of reaction {0} using RMG-database') if not all([m.molecule != [] for m in rxn.reactants+rxn.products]): raise ValueError('chemical structures of reactants and products not available for RMG estimation of reaction {0}'.format(label)) for spc in rxn.reactants+rxn.products: print spc.label print spc.molecule db = getDB('kinetics') rxns = db.generate_reactions_from_libraries(reactants=rxn.reactants,products=rxn.products) rxns = [r for r in rxns if r.elementary_high_p] if rxns != []: for r in rxns: if isinstance(rxn.kinetics, PDepKineticsModel): boo = rxn.generate_high_p_limit_kinetics() if boo: rxn = r break if rxns == [] or not boo: logging.info('No library reactions tagged with elementary_high_p found for reaction {0}, generating reactions from RMG families'.format(label)) rxn = list(db.generate_reactions_from_families(reactants=rxn.reactants,products=rxn.products)) model = CoreEdgeReactionModel() model.verboseComments = True for r in rxn: model.applyKineticsToReaction(r) if isinstance(rxn,Reaction): reactionDict[label] = rxn else: for i in xrange(len(rxn)): reactionDict[label+str(i)] = rxn[i] return rxn
def react_species(species_tuple, only_families=None): """ Given a tuple of Species objects, generates all possible reactions from the loaded reaction families and combines degenerate reactions. """ species_tuple = tuple([spc.copy(deep=True) for spc in species_tuple]) reactions = getDB('kinetics').generate_reactions_from_families(species_tuple, only_families=only_families) return reactions
def test_add_atom_labels_for_reaction_3(self): """Test that addAtomLabelsForReaction can identify reactions with resonance and isotopes""" from rmgpy.data.rmg import getDB mr0 = Molecule().fromAdjacencyList( '1 C u0 p0 c0 i13 {3,D} {4,S} {5,S}\n2 *1 C u0 p0 c0 {3,D} {6,S} {7,S}\n3 C u0 p0 c0 {1,D} {2,D}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {2,S}\n7 *4 H u0 p0 c0 {2,S}\n' ) mr1a = Molecule().fromAdjacencyList( 'multiplicity 2\n1 C u0 p0 c0 i13 {2,D} {4,S} {5,S}\n2 C u0 p0 c0 {1,D} {3,D}\n3 *1 C u1 p0 c0 {2,D} {6,S}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {3,S}\n' ) mr1b = Molecule().fromAdjacencyList( 'multiplicity 2\n1 C u1 p0 c0 i13 {2,S} {4,S} {5,S}\n2 C u0 p0 c0 {1,S} {3,T}\n3 *1 C u0 p0 c0 {2,T} {6,S}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {3,S}\n' ) mp1a = Molecule().fromAdjacencyList( 'multiplicity 2\n1 C u0 p0 c0 {2,D} {4,S} {5,S}\n2 C u0 p0 c0 {1,D} {3,D}\n3 *1 C u1 p0 c0 i13 {2,D} {6,S}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {3,S}\n' ) mp1b = Molecule().fromAdjacencyList( 'multiplicity 2\n1 C u1 p0 c0 {2,S} {4,S} {5,S}\n2 C u0 p0 c0 {1,S} {3,T}\n3 *1 C u0 p0 c0 i13 {2,T} {6,S}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {3,S}\n' ) s1 = Species(molecule=[mr0]) s2 = Species(molecule=[mr1a, mr1b]) s3 = Species(molecule=[mp1a, mp1b]) reactants = [s1, s2] products = [s1, s3] reaction = TemplateReaction(reactants=reactants, products=products, family='H_Abstraction') family = getDB('kinetics').families['H_Abstraction'] print reaction.reactants print reaction.products family.addAtomLabelsForReaction(reaction, output_with_resonance=False) # test that the reaction has labels found_labels = [] for species in reaction.reactants: for atom in species.molecule[0].atoms: if atom.label != '': found_labels.append(atom.label) self.assertEqual( len(found_labels), 3, 'wrong number of labels found {0}'.format(found_labels)) self.assertIn('*1', found_labels) self.assertIn('*2', found_labels) self.assertIn('*3', found_labels) # test for the products too found_labels = [] for species in reaction.products: for atom in species.molecule[0].atoms: if atom.label != '': found_labels.append(atom.label) self.assertEqual(len(found_labels), 3) self.assertIn('*1', found_labels) self.assertIn('*2', found_labels) self.assertIn('*3', found_labels)
def generateTransportData(self): """ Generate the transportData parameters for the species. """ from rmgpy.data.rmg import getDB try: transportDB = getDB('transport') if not transportDB: raise Exception except Exception: logging.debug('Could not obtain the transport database. Not generating transport...') raise #count = sum([1 for atom in self.molecule[0].vertices if atom.isNonHydrogen()]) self.transportData = transportDB.getTransportProperties(self)[0]
def correctDegeneracyOfReverseReaction(reaction): """ This method corrects the degeneracy of reactions found when the backwards template is used. Given the following parameters: reaction - list of reactions with their degeneracies already counted This method modifies reaction in place and returns nothing This does not adjust for identical reactants, you need to use `reduceSameReactantDegeneracy` to adjust for that. """ family = getDB('kinetics').families[reaction.family] if not family.ownReverse: reaction.degeneracy = family.calculateDegeneracy(reaction)
def react_species(species_tuple, only_families=None): """ Given a tuple of Species objects, generates all possible reactions from the loaded reaction families and combines degenerate reactions. Args: species_tuple (tuple): tuple of 1-3 Species objects to react together only_families (list, optional): list of reaction families to consider Returns: list of generated reactions """ reactions = getDB('kinetics').generate_reactions_from_families( species_tuple, only_families=only_families) return reactions
def reactSpecies(speciesTuple): """ Given a tuple of Species objects, generates all possible reactions from the loaded reaction families and combines degenerate reactions. The generated reactions are deflated. """ speciesTuple = tuple([spc.copy(deep=True) for spc in speciesTuple]) reactions = getDB('kinetics').generate_reactions_from_families(speciesTuple) deflate(reactions, [spec for spec in speciesTuple], [spec.index for spec in speciesTuple]) return reactions
def reactSpecies(speciesTuple): """ Given a tuple of Species objects, generates all possible reactions from the loaded reaction families and combines degenerate reactions. The generated reactions are deflated. """ speciesTuple = tuple([spc.copy(deep=True) for spc in speciesTuple]) reactions = getDB('kinetics').generate_reactions_from_families( speciesTuple) deflate(reactions, [spec for spec in speciesTuple], [spec.index for spec in speciesTuple]) return reactions
def reactSpecies(speciesTuple): """ given one species tuple, will find the reactions and remove degeneracy from them. """ # Check if the reactants are the same sameReactants = False if len(speciesTuple) == 2 and speciesTuple[0].isIsomorphic( speciesTuple[1]): sameReactants = True speciesTuple = tuple([spc.copy(deep=True) for spc in speciesTuple]) _labelListOfSpecies(speciesTuple) combos = getMoleculeTuples(speciesTuple) reactions = map(reactMolecules, combos) reactions = list(itertools.chain.from_iterable(reactions)) # remove reverse reaction reactions = findDegeneracies(reactions, sameReactants) # add reverse attribute to families with ownReverse toDelete = [] for i, rxn in enumerate(reactions): family = getDB('kinetics').families[rxn.family] if family.ownReverse: successful = family.addReverseAttribute(rxn) if not successful: toDelete.append(i) # delete reactions which we could not find a reverse reaction for for i in reversed(toDelete): del reactions[i] # get a molecule list with species indexes zippedList = [] for spec in speciesTuple: for mol in spec.molecule: zippedList.append((mol, spec.index)) molecules, reactantIndices = zip(*zippedList) deflate(reactions, [spec for spec in speciesTuple], [spec.index for spec in speciesTuple]) return reactions
def generateThermoData(spc, thermoClass=NASA, solventName=''): """ Generates thermo data, first checking Libraries, then using either QM or Database. The database generates the thermo data for each structure (resonance isomer), picks that with lowest H298 value. It then calls :meth:`processThermoData`, to convert (via Wilhoit) to NASA and set the E0. Result stored in `spc.thermo` and returned. """ try: thermodb = getDB('thermo') if not thermodb: raise Exception except Exception, e: logging.debug('Could not obtain the thermo database. Not generating thermo...') return None
def test_add_atom_labels_for_reaction_3(self): """Test that addAtomLabelsForReaction can identify reactions with resonance and isotopes""" from rmgpy.data.rmg import getDB mr0 = Molecule().fromAdjacencyList('1 C u0 p0 c0 i13 {3,D} {4,S} {5,S}\n2 *1 C u0 p0 c0 {3,D} {6,S} {7,S}\n3 C u0 p0 c0 {1,D} {2,D}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {2,S}\n7 *4 H u0 p0 c0 {2,S}\n') mr1a = Molecule().fromAdjacencyList('multiplicity 2\n1 C u0 p0 c0 i13 {2,D} {4,S} {5,S}\n2 C u0 p0 c0 {1,D} {3,D}\n3 *1 C u1 p0 c0 {2,D} {6,S}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {3,S}\n') mr1b = Molecule().fromAdjacencyList('multiplicity 2\n1 C u1 p0 c0 i13 {2,S} {4,S} {5,S}\n2 C u0 p0 c0 {1,S} {3,T}\n3 *1 C u0 p0 c0 {2,T} {6,S}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {3,S}\n') mp1a = Molecule().fromAdjacencyList('multiplicity 2\n1 C u0 p0 c0 {2,D} {4,S} {5,S}\n2 C u0 p0 c0 {1,D} {3,D}\n3 *1 C u1 p0 c0 i13 {2,D} {6,S}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {3,S}\n') mp1b = Molecule().fromAdjacencyList('multiplicity 2\n1 C u1 p0 c0 {2,S} {4,S} {5,S}\n2 C u0 p0 c0 {1,S} {3,T}\n3 *1 C u0 p0 c0 i13 {2,T} {6,S}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {3,S}\n') s1 = Species(molecule = [mr0]) s2 = Species(molecule = [mr1a,mr1b]) s3 = Species(molecule = [mp1a,mp1b]) reactants = [s1,s2] products = [s1,s3] reaction = TemplateReaction(reactants =reactants, products = products, family = 'H_Abstraction') family = getDB('kinetics').families['H_Abstraction'] print reaction.reactants print reaction.products family.addAtomLabelsForReaction(reaction, output_with_resonance=False) # test that the reaction has labels found_labels = [] for species in reaction.reactants: for atom in species.molecule[0].atoms: if atom.label != '': found_labels.append(atom.label) self.assertEqual(len(found_labels), 3,'wrong number of labels found {0}'.format(found_labels)) self.assertIn('*1',found_labels) self.assertIn('*2',found_labels) self.assertIn('*3',found_labels) # test for the products too found_labels = [] for species in reaction.products: for atom in species.molecule[0].atoms: if atom.label != '': found_labels.append(atom.label) self.assertEqual(len(found_labels), 3) self.assertIn('*1',found_labels) self.assertIn('*2',found_labels) self.assertIn('*3',found_labels)
def generateThermoData(spc, thermoClass=NASA, solventName=''): """ Generates thermo data, first checking Libraries, then using either QM or Database. The database generates the thermo data for each structure (resonance isomer), picks that with lowest H298 value. It then calls :meth:`processThermoData`, to convert (via Wilhoit) to NASA and set the E0. Result stored in `spc.thermo` and returned. """ try: thermodb = getDB('thermo') if not thermodb: raise Exception except Exception: logging.debug( 'Could not obtain the thermo database. Not generating thermo...') return None thermo0 = thermodb.getThermoData(spc) # 1. maybe only submit cyclic core # 2. to help radical prediction, HBI should also # look up centrailThermoDB for its saturated version # currently it only looks up libraries or estimates via GAV from rmgpy.rmg.input import getInput try: thermoCentralDatabase = getInput('thermoCentralDatabase') except Exception: logging.debug('thermoCentralDatabase could not be found.') thermoCentralDatabase = None if thermoCentralDatabase and thermoCentralDatabase.client \ and thermoCentralDatabase.satisfyRegistrationRequirements(spc, thermo0, thermodb): thermoCentralDatabase.registerInCentralThermoDB(spc) return processThermoData(spc, thermo0, thermoClass, solventName)
def test_add_atom_labels_for_reaction(self): """Test that addAtomLabelsForReaction can identify reactions with resonance The molecule [CH]=C=C has resonance in this reaction""" from rmgpy.data.rmg import getDB reactants = [ Molecule().fromSMILES('C=C=C'), Molecule().fromSMILES('[CH]=C=C'), ] products = [ Molecule().fromSMILES('C#C[CH2]'), Molecule().fromSMILES('C#CC'), ] reaction = TemplateReaction(reactants=reactants, products=products, family='H_Abstraction') reaction.ensure_species(reactant_resonance=True, product_resonance=True) family = getDB('kinetics').families['H_Abstraction'] family.addAtomLabelsForReaction(reaction, output_with_resonance=False) # test that the reaction has labels found_labels = [] for species in reaction.reactants: for atom in species.molecule[0].atoms: if atom.label != '': found_labels.append(atom.label) self.assertEqual(len(found_labels), 3) self.assertIn('*1', found_labels) self.assertIn('*2', found_labels) self.assertIn('*3', found_labels) # test for the products too found_labels = [] for species in reaction.products: for atom in species.molecule[0].atoms: if atom.label != '': found_labels.append(atom.label) self.assertEqual(len(found_labels), 3) self.assertIn('*1', found_labels) self.assertIn('*2', found_labels) self.assertIn('*3', found_labels)
def test_add_atom_labels_for_reaction_2(self): """Test that addAtomLabelsForReaction can identify reactions with identical references The molecule [CH]=C=C has resonance in this reaction""" from rmgpy.data.rmg import getDB s1 = Species().fromSMILES('C=C=C') s2 = Species().fromSMILES('C=C=[CH]') s3 = Species().fromSMILES('C#CC') s2.generate_resonance_structures() reactants = [s1, s2] products = [s2, s3] reaction = TemplateReaction(reactants=reactants, products=products, family='H_Abstraction') family = getDB('kinetics').families['H_Abstraction'] print reaction.reactants print reaction.products family.addAtomLabelsForReaction(reaction, output_with_resonance=False) # test that the reaction has labels found_labels = [] for species in reaction.reactants: for atom in species.molecule[0].atoms: if atom.label != '': found_labels.append(atom.label) self.assertEqual( len(found_labels), 3, 'wrong number of labels found {0}'.format(found_labels)) self.assertIn('*1', found_labels) self.assertIn('*2', found_labels) self.assertIn('*3', found_labels) # test for the products too found_labels = [] for species in reaction.products: for atom in species.molecule[0].atoms: if atom.label != '': found_labels.append(atom.label) self.assertEqual(len(found_labels), 3) self.assertIn('*1', found_labels) self.assertIn('*2', found_labels) self.assertIn('*3', found_labels)
def generateThermoData(spc, thermoClass=NASA, solventName=''): """ Generates thermo data, first checking Libraries, then using either QM or Database. The database generates the thermo data for each structure (resonance isomer), picks that with lowest H298 value. It then calls :meth:`processThermoData`, to convert (via Wilhoit) to NASA and set the E0. Result stored in `spc.thermo` and returned. """ try: thermodb = getDB('thermo') if not thermodb: raise Exception except Exception: logging.debug('Could not obtain the thermo database. Not generating thermo...') return None thermo0 = thermodb.getThermoData(spc) # 1. maybe only submit cyclic core # 2. to help radical prediction, HBI should also # look up centrailThermoDB for its saturated version # currently it only looks up libraries or estimates via GAV from rmgpy.rmg.input import getInput try: thermoCentralDatabase = getInput('thermoCentralDatabase') except Exception: logging.debug('thermoCentralDatabase could not be found.') thermoCentralDatabase = None if thermoCentralDatabase and thermoCentralDatabase.client \ and thermoCentralDatabase.satisfyRegistrationRequirements(spc, thermo0, thermodb): thermoCentralDatabase.registerInCentralThermoDB(spc) return processThermoData(spc, thermo0, thermoClass, solventName)
def test_add_atom_labels_for_reaction(self): """Test that addAtomLabelsForReaction can identify reactions with resonance The molecule [CH]=C=C has resonance in this reaction""" from rmgpy.data.rmg import getDB reactants = [ Molecule().fromSMILES('C=C=C'), Molecule().fromSMILES('[CH]=C=C'), ] products = [ Molecule().fromSMILES('C#C[CH2]'), Molecule().fromSMILES('C#CC'), ] reaction = TemplateReaction(reactants =reactants, products = products, family = 'H_Abstraction') reaction.ensure_species(reactant_resonance=True, product_resonance=True) family = getDB('kinetics').families['H_Abstraction'] family.addAtomLabelsForReaction(reaction, output_with_resonance=False) # test that the reaction has labels found_labels = [] for species in reaction.reactants: for atom in species.molecule[0].atoms: if atom.label != '': found_labels.append(atom.label) self.assertEqual(len(found_labels), 3) self.assertIn('*1',found_labels) self.assertIn('*2',found_labels) self.assertIn('*3',found_labels) # test for the products too found_labels = [] for species in reaction.products: for atom in species.molecule[0].atoms: if atom.label != '': found_labels.append(atom.label) self.assertEqual(len(found_labels), 3) self.assertIn('*1',found_labels) self.assertIn('*2',found_labels) self.assertIn('*3',found_labels)
def test_add_atom_labels_for_reaction_2(self): """Test that addAtomLabelsForReaction can identify reactions with identical references The molecule [CH]=C=C has resonance in this reaction""" from rmgpy.data.rmg import getDB s1 = Species().fromSMILES('C=C=C') s2 = Species().fromSMILES('C=C=[CH]') s3 = Species().fromSMILES('C#CC') s2.generate_resonance_structures() reactants = [s1,s2] products = [s2,s3] reaction = TemplateReaction(reactants =reactants, products = products, family = 'H_Abstraction') family = getDB('kinetics').families['H_Abstraction'] print reaction.reactants print reaction.products family.addAtomLabelsForReaction(reaction, output_with_resonance=False) # test that the reaction has labels found_labels = [] for species in reaction.reactants: for atom in species.molecule[0].atoms: if atom.label != '': found_labels.append(atom.label) self.assertEqual(len(found_labels), 3,'wrong number of labels found {0}'.format(found_labels)) self.assertIn('*1',found_labels) self.assertIn('*2',found_labels) self.assertIn('*3',found_labels) # test for the products too found_labels = [] for species in reaction.products: for atom in species.molecule[0].atoms: if atom.label != '': found_labels.append(atom.label) self.assertEqual(len(found_labels), 3) self.assertIn('*1',found_labels) self.assertIn('*2',found_labels) self.assertIn('*3',found_labels)
def reactSpecies(speciesTuple): """ given one species tuple, will find the reactions and remove degeneracy from them. """ speciesTuple = tuple([spc.copy(deep=True) for spc in speciesTuple]) _labelListOfSpecies(speciesTuple) combos = getMoleculeTuples(speciesTuple) reactions = map(reactMolecules,combos) reactions = list(itertools.chain.from_iterable(reactions)) # remove reverse reaction reactions = findDegeneracies(reactions) # add reverse attribute to families with ownReverse for rxn in reactions: family = getDB('kinetics').families[rxn.family] if family.ownReverse: family.addReverseAttribute(rxn) # fix the degneracy of (not ownReverse) reactions found in the backwards # direction correctDegeneracyOfReverseReactions(reactions, list(speciesTuple)) reduceSameReactantDegeneracy(reactions) # get a molecule list with species indexes zippedList = [] for spec in speciesTuple: for mol in spec.molecule: zippedList.append((mol,spec.index)) molecules, reactantIndices = zip(*zippedList) deflate(reactions, [spec for spec in speciesTuple], [spec.index for spec in speciesTuple]) return reactions
def react_molecules_wrapper(reactants): return getDB('kinetics').react_molecules(reactants, only_families=None, prod_resonance=False)
def find_degenerate_reactions(rxn_list, same_reactants=None, template=None, kinetics_database=None, kinetics_family=None): """ Given a list of Reaction objects, this method combines degenerate reactions and increments the reaction degeneracy value. For multiple transition states, this method keeps them as duplicate reactions. If a template is specified, then the reaction list will be filtered to leave only reactions which match the specified template, then the degeneracy will be calculated as usual. A KineticsDatabase or KineticsFamily instance can also be provided to calculate the degeneracy for reactions generated in the reverse direction. If not provided, then it will be retrieved from the global database. This algorithm used to exist in family.__generateReactions, but was moved here so it could operate across reaction families. This method returns an updated list with degenerate reactions removed. Args: rxn_list (list): reactions to be analyzed same_reactants (bool, optional): indicate whether the reactants are identical template (list, optional): specify a specific template to filter by kinetics_database (KineticsDatabase, optional): provide a KineticsDatabase instance for calculating degeneracy kinetics_family (KineticsFamily, optional): provide a KineticsFamily instance for calculating degeneracy Returns: Reaction list with degenerate reactions combined with proper degeneracy values """ # If a specific reaction template is requested, filter by that template if template is not None: selected_rxns = [] template = frozenset(template) for rxn in rxn_list: if template == frozenset(rxn.template): selected_rxns.append(rxn) if not selected_rxns: # Only log a warning here. If a non-empty output is expected, then the caller should raise an exception logging.warning('No reactions matched the specified template, {0}'.format(template)) return [] else: selected_rxns = rxn_list # We want to sort all the reactions into sublists composed of isomorphic reactions # with degenerate transition states sorted_rxns = [] for rxn0 in selected_rxns: # find resonance structures for rxn0 rxn0.ensure_species() if len(sorted_rxns) == 0: # This is the first reaction, so create a new sublist sorted_rxns.append([rxn0]) else: # Loop through each sublist, which represents a unique reaction for sub_list in sorted_rxns: # Try to determine if the current rxn0 is identical or isomorphic to any reactions in the sublist isomorphic = False identical = False sameTemplate = True for rxn in sub_list: isomorphic = rxn0.isIsomorphic(rxn, checkIdentical=False, checkTemplateRxnProducts=True) if isomorphic: identical = rxn0.isIsomorphic(rxn, checkIdentical=True, checkTemplateRxnProducts=True) if identical: # An exact copy of rxn0 is already in our list, so we can move on break sameTemplate = frozenset(rxn.template) == frozenset(rxn0.template) else: # This sublist contains a different product break # Process the reaction depending on the results of the comparisons if identical: # This reaction does not contribute to degeneracy break elif isomorphic: if sameTemplate: # We found the right sublist, and there is no identical reaction # We should add rxn0 to the sublist as a degenerate rxn, and move on to the next rxn sub_list.append(rxn0) break else: # We found an isomorphic sublist, but the reaction templates are different # We need to mark this as a duplicate and continue searching the remaining sublists rxn0.duplicate = True sub_list[0].duplicate = True continue else: # This is not an isomorphic sublist, so we need to continue searching the remaining sublists # Note: This else statement is not technically necessary but is included for clarity continue else: # We did not break, which means that there was no isomorphic sublist, so create a new one sorted_rxns.append([rxn0]) rxn_list = [] for sub_list in sorted_rxns: # Collapse our sorted reaction list by taking one reaction from each sublist rxn = sub_list[0] # The degeneracy of each reaction is the number of reactions that were in the sublist rxn.degeneracy = sum([reaction0.degeneracy for reaction0 in sub_list]) rxn_list.append(rxn) for rxn in rxn_list: if rxn.is_forward: reduce_same_reactant_degeneracy(rxn, same_reactants) else: # fix the degeneracy of (not ownReverse) reactions found in the backwards direction try: family = kinetics_family or kinetics_database.families[rxn.family] except AttributeError: from rmgpy.data.rmg import getDB family = getDB('kinetics').families[rxn.family] if not family.ownReverse: rxn.degeneracy = family.calculateDegeneracy(rxn) return rxn_list
def find_degenerate_reactions(rxnList, same_reactants=None, kinetics_database=None, kinetics_family=None): """ given a list of Reaction object with Molecule objects, this method removes degenerate reactions and increments the degeneracy of the reaction object. For multiple transition states, this method adds them as separate duplicate reactions. This method modifies rxnList in place and does not return anything. This algorithm used to exist in family.__generateReactions, but was moved here because it didn't have any family dependence. """ # We want to sort all the reactions into sublists composed of isomorphic reactions # with degenerate transition states rxnSorted = [] for rxn0 in rxnList: # find resonance structures for rxn0 ensure_species_in_reaction(rxn0) if len(rxnSorted) == 0: # This is the first reaction, so create a new sublist rxnSorted.append([rxn0]) else: # Loop through each sublist, which represents a unique reaction for rxnList1 in rxnSorted: # Try to determine if the current rxn0 is identical or isomorphic to any reactions in the sublist isomorphic = False identical = False sameTemplate = False for rxn in rxnList1: isomorphic = rxn0.isIsomorphic( rxn, checkIdentical=False, checkTemplateRxnProducts=True) if not isomorphic: identical = False else: identical = rxn0.isIsomorphic( rxn, checkIdentical=True, checkTemplateRxnProducts=True) sameTemplate = frozenset(rxn.template) == frozenset( rxn0.template) if not isomorphic: # a different product was found, go to next list break elif not sameTemplate: # a different transition state was found, mark as duplicate and # go to the next sublist rxn.duplicate = True rxn0.duplicate = True break elif identical: # An exact copy of rxn0 is already in our list, so we can move on to the next rxn break else: # sameTemplate and isomorphic but not identical # This is the right sublist for rxn0, but continue to see if there is an identical rxn continue else: # We did not break, so this is the right sublist, but there is no identical reaction # This means that we should add rxn0 to the sublist as a degenerate rxn rxnList1.append(rxn0) if isomorphic and sameTemplate: # We already found the right sublist, so we can move on to the next rxn break else: # We did not break, which means that there was no isomorphic sublist, so create a new one rxnSorted.append([rxn0]) rxnList = [] for rxnList1 in rxnSorted: # Collapse our sorted reaction list by taking one reaction from each sublist rxn = rxnList1[0] # The degeneracy of each reaction is the number of reactions that were in the sublist rxn.degeneracy = sum([reaction0.degeneracy for reaction0 in rxnList1]) rxnList.append(rxn) for rxn in rxnList: if rxn.isForward: reduce_same_reactant_degeneracy(rxn, same_reactants) else: # fix the degeneracy of (not ownReverse) reactions found in the backwards direction try: family = kinetics_family or kinetics_database.families[ rxn.family] except AttributeError: from rmgpy.data.rmg import getDB family = getDB('kinetics').families[rxn.family] if not family.ownReverse: rxn.degeneracy = family.calculateDegeneracy(rxn) return rxnList
def testaddReverseAttribute(self): """ tests that the addReverseAttribute method gets the reverse degeneracy correct """ from rmgpy.data.rmg import getDB from rmgpy.data.kinetics.family import TemplateReaction adjlist = [''' multiplicity 2 1 H u0 p0 c0 {7,S} 2 H u0 p0 c0 {4,S} 3 C u1 p0 c0 {5,S} {7,S} {8,S} 4 C u0 p0 c0 {2,S} {6,S} {7,D} 5 H u0 p0 c0 {3,S} 6 H u0 p0 c0 {4,S} 7 C u0 p0 c0 {1,S} {3,S} {4,D} 8 H u0 p0 c0 {3,S} ''', ''' 1 C u0 p0 c0 {2,S} {4,S} {5,S} {6,S} 2 C u0 p0 c0 i13 {1,S} {3,D} {7,S} 3 C u0 p0 c0 {2,D} {8,S} {9,S} 4 H u0 p0 c0 {1,S} 5 H u0 p0 c0 {1,S} 6 H u0 p0 c0 {1,S} 7 H u0 p0 c0 {2,S} 8 H u0 p0 c0 {3,S} 9 H u0 p0 c0 {3,S} ''', ''' multiplicity 2 1 H u0 p0 c0 {7,S} 2 H u0 p0 c0 {4,S} 3 C u1 p0 c0 {5,S} {7,S} {8,S} 4 C u0 p0 c0 {2,S} {6,S} {7,D} 5 H u0 p0 c0 {3,S} 6 H u0 p0 c0 {4,S} 7 C u0 p0 c0 i13 {1,S} {3,S} {4,D} 8 H u0 p0 c0 {3,S} ''', ''' 1 C u0 p0 c0 {2,S} {4,S} {5,S} {6,S} 2 C u0 p0 c0 {1,S} {3,D} {7,S} 3 C u0 p0 c0 {2,D} {8,S} {9,S} 4 H u0 p0 c0 {1,S} 5 H u0 p0 c0 {1,S} 6 H u0 p0 c0 {1,S} 7 H u0 p0 c0 {2,S} 8 H u0 p0 c0 {3,S} 9 H u0 p0 c0 {3,S} ''' ] family = getDB('kinetics').families['H_Abstraction'] r1 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[0])]) r2 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[1])]) p1 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[2])]) p2 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[3])]) r1.generate_resonance_structures(keep_isomorphic=True) p1.generate_resonance_structures(keep_isomorphic=True) rxn = TemplateReaction(reactants=[r1, r2], products=[p1, p2]) rxn.degeneracy = family.calculateDegeneracy(rxn) self.assertEqual(rxn.degeneracy, 6) family.addReverseAttribute(rxn) self.assertEqual(rxn.reverse.degeneracy, 6)
def processThermoData(spc, thermo0, thermoClass=NASA, solventName = ''): """ Converts via Wilhoit into required `thermoClass` and sets `E0`. Resulting thermo is returned. """ # TODO moving this as a global import leads to circular imports. from rmgpy.rmg.model import Species thermo = None # Always convert to Wilhoit so we can compute E0 if isinstance(thermo0, Wilhoit): wilhoit = thermo0 elif isinstance(thermo0, ThermoData): wilhoit = thermo0.toWilhoit(B=1000.) else: wilhoit = thermo0.toWilhoit() # Add on solvation correction solvationdatabase = getDB('solvation') if not solventName or solvationdatabase is None: logging.debug('Solvent database or solventName not found. Solvent effect was not utilized') solventData = None else: solventData = solvationdatabase.getSolventData(solventName) if solventData and not "Liquid thermo library" in thermo0.comment: solvationdatabase = getDB('solvation') #logging.info("Making solvent correction for {0}".format(Species.solventName)) soluteData = solvationdatabase.getSoluteData(spc) solvation_correction = solvationdatabase.getSolvationCorrection(soluteData, solventData) # correction is added to the entropy and enthalpy wilhoit.S0.value_si = (wilhoit.S0.value_si + solvation_correction.entropy) wilhoit.H0.value_si = (wilhoit.H0.value_si + solvation_correction.enthalpy) # Compute E0 by extrapolation to 0 K if spc.conformer is None: spc.conformer = Conformer() spc.conformer.E0 = wilhoit.E0 # Convert to desired thermo class if thermoClass is Wilhoit: thermo = wilhoit elif thermoClass is NASA: if solventData: #if liquid phase simulation keep the nasa polynomial if it comes from a liquid phase thermoLibrary. Otherwise convert wilhoit to NASA if "Liquid thermo library" in thermo0.comment and isinstance(thermo0, NASA): thermo = thermo0 if thermo.E0 is None: thermo.E0 = wilhoit.E0 else: thermo = wilhoit.toNASA(Tmin=100.0, Tmax=5000.0, Tint=1000.0) else: #gas phase with species matching thermo library keep the NASA from library or convert if group additivity if "Thermo library" in thermo0.comment and isinstance(thermo0,NASA): thermo=thermo0 if thermo.E0 is None: thermo.E0 = wilhoit.E0 else: thermo = wilhoit.toNASA(Tmin=100.0, Tmax=5000.0, Tint=1000.0) else: raise Exception('thermoClass neither NASA nor Wilhoit. Cannot process thermo data.') if thermo.__class__ != thermo0.__class__: # Compute RMS error of overall transformation Tlist = numpy.array([300.0, 400.0, 500.0, 600.0, 800.0, 1000.0, 1500.0], numpy.float64) err = 0.0 for T in Tlist: err += (thermo.getHeatCapacity(T) - thermo0.getHeatCapacity(T))**2 err = math.sqrt(err/len(Tlist))/constants.R # logging.log(logging.WARNING if err > 0.2 else 0, 'Average RMS error in heat capacity fit to {0} = {1:g}*R'.format(spc, err)) return thermo
def species(label, *args, **kwargs): """Load a species from an input file""" global speciesDict, jobList if label in speciesDict: raise ValueError( 'Multiple occurrences of species with label {0!r}.'.format(label)) logging.info('Loading species {0}...'.format(label)) spec = Species(label=label) speciesDict[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)) jobList.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 = [] spinMultiplicity = 0 opticalIsomers = 1 molecularWeight = None collisionModel = None energyTransferModel = 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': spinMultiplicity = value elif key == 'opticalIsomers': opticalIsomers = value elif key == 'molecularWeight': molecularWeight = value elif key == 'collisionModel': collisionModel = value elif key == 'energyTransferModel': energyTransferModel = 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, spinMultiplicity=spinMultiplicity, opticalIsomers=opticalIsomers) if molecularWeight is not None: spec.molecularWeight = molecularWeight elif spec.molecularWeight is None and is_pdep(jobList): # If a structure was given, simply calling spec.molecularWeight 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.transportData = collisionModel spec.energyTransferModel = energyTransferModel 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 = getDB('thermo') if db is None: raise DatabaseError('Thermo database is None.') except DatabaseError: logging.warn( "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.getThermoData(spec) if spec.thermo.E0 is None: th = spec.thermo.toWilhoit() 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.hasStatMech( ) and structure is not None: # generate stat mech info if it wasn't provided before spec.generateStatMech() if not energyTransferModel: # default to RMG's method of generating energyTransferModel spec.generateEnergyTransferModel() return spec
def execute(self, outputFile, plot, format='pdf', print_summary=True, speciesList=None, thermoLibrary=None, kineticsLibrary=None): logging.info('Exploring network...') rmg = RMG() rmg.speciesConstraints = {'allowed' : ['input species', 'seed mechanisms', 'reaction libraries'], 'maximumRadicalElectrons' : self.maximumRadicalElectrons, 'explicitlyAllowedMolecules': []} rmgpy.rmg.input.rmg = rmg reaction_model = CoreEdgeReactionModel() reaction_model.pressureDependence = self.pdepjob reaction_model.pressureDependence.rmgmode = True if outputFile: reaction_model.pressureDependence.outputFile = os.path.dirname(outputFile) kineticsDatabase = getDB('kinetics') thermoDatabase = getDB('thermo') thermoDatabase.libraries['thermojobs'] = thermoLibrary thermoDatabase.libraryOrder.insert(0,'thermojobs') kineticsDatabase.libraries['kineticsjobs'] = kineticsLibrary kineticsDatabase.libraryOrder.insert(0,('kineticsjobs','Reaction Library')) jobRxns = [rxn for rxn in reaction_model.core.reactions] self.jobRxns = jobRxns if outputFile is not None: if not os.path.exists(os.path.join(reaction_model.pressureDependence.outputFile,'pdep')): os.mkdir(os.path.join(reaction_model.pressureDependence.outputFile,'pdep')) else: shutil.rmtree(os.path.join(reaction_model.pressureDependence.outputFile,'pdep')) os.mkdir(os.path.join(reaction_model.pressureDependence.outputFile,'pdep')) # get the molecular formula for the network mmol = None for spc in self.source: if mmol: mmol = mmol.merge(spc.molecule[0]) else: mmol = spc.molecule[0].copy(deep=True) form = mmol.getFormula() for spec in self.bathGas.keys()+self.source: nspec,isNew = reaction_model.makeNewSpecies(spec,reactive=False) flags = np.array([s.molecule[0].getFormula()==form for s in reaction_model.core.species]) reaction_model.enlarge(nspec,reactEdge=False,unimolecularReact=flags, bimolecularReact=np.zeros((len(reaction_model.core.species),len(reaction_model.core.species)))) reaction_model.addSeedMechanismToCore('kineticsjobs') for lib in kineticsDatabase.libraryOrder: if lib[0] != 'kineticsjobs': reaction_model.addReactionLibraryToEdge(lib[0]) for spc in reaction_model.core.species: for i,item in enumerate(self.source): if spc.isIsomorphic(item): self.source[i] = spc # react initial species if len(self.source) == 1: flags = np.array([s.molecule[0].getFormula()==form for s in reaction_model.core.species]) biflags = np.zeros((len(reaction_model.core.species),len(reaction_model.core.species))) elif len(self.source) == 2: flags = np.array([False for s in reaction_model.core.species]) biflags = np.array([[False for i in xrange(len(reaction_model.core.species))] for j in xrange(len(reaction_model.core.species))]) biflags[reaction_model.core.species.index(self.source[0]),reaction_model.core.species.index(self.source[1])] = True else: raise ValueError("Reactant channels with greater than 2 reactants not supported") reaction_model.enlarge(reactEdge=True,unimolecularReact=flags, bimolecularReact=biflags) # find the networks we're interested in networks = [] for nwk in reaction_model.networkList: if set(nwk.source) == set(self.source): self.source = nwk.source networks.append(nwk) if len(networks) == 0: raise ValueError('Did not generate a network with the requested source. This usually means no unimolecular' 'reactions were generated for the source. Note that library reactions that are not' ' properly flagged as elementary_high_p can replace RMG generated reactions that would' ' otherwise be part of networks.') for network in networks: network.bathGas = self.bathGas self.networks = networks # determine T and P combinations if self.pdepjob.Tlist: Tlist = self.pdepjob.Tlist.value_si else: Tlist = np.linspace(self.pdepjob.Tmin.value_si,self.pdepjob.Tmax.value_si,self.pdepjob.Tcount) if self.pdepjob.Plist: Plist = self.pdepjob.Plist.value_si else: Plist = np.linspace(self.pdepjob.Pmin.value_si,self.pdepjob.Pmax.value_si,self.pdepjob.Pcount) # generate the network forbiddenStructures = getDB('forbidden') incomplete = True while incomplete: incomplete = False for T in Tlist: for P in Plist: for network in self.networks: kchar = 0.0 #compute the characteristic rate coefficient by summing all rate coefficients from the reactant channel for rxn in network.netReactions:#reaction_model.core.reactions+reaction_model.edge.reactions: if set(rxn.reactants) == set(self.source) and rxn.products[0].molecule[0].getFormula() == form: kchar += rxn.kinetics.getRateCoefficient(T=T,P=P) elif set(rxn.products) == set(self.source) and rxn.reactants[0].molecule[0].getFormula() == form: kchar += rxn.generateReverseRateCoefficient(network_kinetics=True).getRateCoefficient(T=T,P=P) if network.getLeakCoefficient(T=T,P=P) > self.explore_tol*kchar: incomplete = True spc = network.getMaximumLeakSpecies(T=T,P=P) if forbiddenStructures.isMoleculeForbidden(spc.molecule[0]): reaction_model.removeSpeciesFromEdge(reaction_model.reactionSystems,spc) reaction_model.removeEmptyPdepNetworks() logging.error(spc.label) else: logging.info('adding new isomer {0} to network'.format(spc)) flags = np.array([s.molecule[0].getFormula()==form for s in reaction_model.core.species]) reaction_model.enlarge((network,spc),reactEdge=False,unimolecularReact=flags, bimolecularReact=np.zeros((len(reaction_model.core.species),len(reaction_model.core.species)))) flags = np.array([s.molecule[0].getFormula()==form for s in reaction_model.core.species]) reaction_model.enlarge(reactEdge=True,unimolecularReact=flags, bimolecularReact=np.zeros((len(reaction_model.core.species),len(reaction_model.core.species)))) for network in self.networks: rmRxns = [] for rxn in network.pathReactions: # remove reactions with forbidden species for r in rxn.reactants+rxn.products: if forbiddenStructures.isMoleculeForbidden(r.molecule[0]): rmRxns.append(rxn) for rxn in rmRxns: logging.info('Removing forbidden reaction: {0}'.format(rxn)) network.pathReactions.remove(rxn) # clean up output files if outputFile is not None: path = os.path.join(reaction_model.pressureDependence.outputFile,'pdep') for name in os.listdir(path): if name.endswith('.py') and '_' in name: if name.split('_')[-1].split('.')[0] != str(len(network.isomers)): os.remove(os.path.join(path,name)) else: os.rename(os.path.join(path,name),os.path.join(path,'network_full{}.py'.format(self.networks.index(network)))) warns = [] for rxn in jobRxns: if rxn not in network.pathReactions: warns.append('Reaction {0} in the input file was not explored during network expansion and was not included in the full network. This is likely because your explore_tol value is too high.'.format(rxn)) # reduction process for network in self.networks: if self.energy_tol != np.inf or self.flux_tol != 0.0: rxnSet = None for T in Tlist: if self.energy_tol != np.inf: rxns = network.get_energy_filtered_reactions(T,self.energy_tol) if rxnSet is not None: rxnSet &= set(rxns) else: rxnSet = set(rxns) for P in Plist: if self.flux_tol != 0.0: rxns = network.get_rate_filtered_reactions(T,P,self.flux_tol) if rxnSet is not None: rxnSet &= set(rxns) else: rxnSet = set(rxns) logging.info('removing reactions during reduction:') for rxn in rxnSet: logging.info(rxn) network.remove_reactions(reaction_model,list(rxnSet)) for rxn in jobRxns: if rxn not in network.pathReactions: warns.append('Reaction {0} in the input file was not included in the reduced model.'.format(rxn)) self.networks = networks for p,network in enumerate(self.networks): self.pdepjob.network = network if len(self.networks) > 1: s1,s2 = outputFile.split(".") ind = str(self.networks.index(network)) stot = s1+"{}.".format(ind)+s2 else: stot = outputFile self.pdepjob.execute(stot, plot, format='pdf', print_summary=True) if os.path.isfile('network.pdf'): os.rename('network.pdf','network'+str(p)+'.pdf') if warns != []: logging.info('\nOUTPUT WARNINGS:\n') for w in warns: logging.warning(w)
def find_degenerate_reactions(rxnList, same_reactants=None, kinetics_database=None, kinetics_family=None): """ given a list of Reaction object with Molecule objects, this method removes degenerate reactions and increments the degeneracy of the reaction object. For multiple transition states, this method adds them as separate duplicate reactions. This method modifies rxnList in place and does not return anything. This algorithm used to exist in family.__generateReactions, but was moved here because it didn't have any family dependence. """ # We want to sort all the reactions into sublists composed of isomorphic reactions # with degenerate transition states rxnSorted = [] for rxn0 in rxnList: # find resonance structures for rxn0 rxn0.ensure_species() if len(rxnSorted) == 0: # This is the first reaction, so create a new sublist rxnSorted.append([rxn0]) else: # Loop through each sublist, which represents a unique reaction for rxnList1 in rxnSorted: # Try to determine if the current rxn0 is identical or isomorphic to any reactions in the sublist isomorphic = False identical = False sameTemplate = False for rxn in rxnList1: isomorphic = rxn0.isIsomorphic(rxn, checkIdentical=False, checkTemplateRxnProducts=True) if not isomorphic: identical = False else: identical = rxn0.isIsomorphic(rxn, checkIdentical=True, checkTemplateRxnProducts=True) sameTemplate = frozenset(rxn.template) == frozenset(rxn0.template) if not isomorphic: # a different product was found, go to next list break elif not sameTemplate: # a different transition state was found, mark as duplicate and # go to the next sublist rxn.duplicate = True rxn0.duplicate = True break elif identical: # An exact copy of rxn0 is already in our list, so we can move on to the next rxn break else: # sameTemplate and isomorphic but not identical # This is the right sublist for rxn0, but continue to see if there is an identical rxn continue else: # We did not break, so this is the right sublist, but there is no identical reaction # This means that we should add rxn0 to the sublist as a degenerate rxn rxnList1.append(rxn0) if isomorphic and sameTemplate: # We already found the right sublist, so we can move on to the next rxn break else: # We did not break, which means that there was no isomorphic sublist, so create a new one rxnSorted.append([rxn0]) rxnList = [] for rxnList1 in rxnSorted: # Collapse our sorted reaction list by taking one reaction from each sublist rxn = rxnList1[0] # The degeneracy of each reaction is the number of reactions that were in the sublist rxn.degeneracy = sum([reaction0.degeneracy for reaction0 in rxnList1]) rxnList.append(rxn) for rxn in rxnList: if rxn.isForward: reduce_same_reactant_degeneracy(rxn, same_reactants) else: # fix the degeneracy of (not ownReverse) reactions found in the backwards direction try: family = kinetics_family or kinetics_database.families[rxn.family] except AttributeError: from rmgpy.data.rmg import getDB family = getDB('kinetics').families[rxn.family] if not family.ownReverse: rxn.degeneracy = family.calculateDegeneracy(rxn) return rxnList
def react_all(core_spc_list, numOldCoreSpecies, unimolecularReact, bimolecularReact, trimolecularReact=None, procnum=1): """ Reacts the core species list via uni-, bi-, and trimolecular reactions. For parallel processing, reaction families are split per task for improved load balancing. This is currently hard-coded using reaction family labels. Args: core_spc_list (list): list of all core species numOldCoreSpecies (int): current number of core species in the model unimolecularReact (np.ndarray): reaction filter flags indicating which species to react unimolecularly bimolecularReact (np.ndarray): reaction filter flags indicating which species to react bimolecularly trimolecularReact (np.ndarray, optional): reaction filter flags indicating which species to react trimolecularly procnum (int, optional): number of processors used for reaction generation Returns: a list of lists of reactions generated from each species tuple a list of species tuples corresponding to each list of reactions """ # Select reactive species that can undergo unimolecular reactions: spc_tuples = [(core_spc_list[i], ) for i in xrange(numOldCoreSpecies) if (unimolecularReact[i] and core_spc_list[i].reactive)] for i in xrange(numOldCoreSpecies): for j in xrange(i, numOldCoreSpecies): # Find reactions involving the species that are bimolecular. # This includes a species reacting with itself (if its own concentration is high enough). if bimolecularReact[i, j]: if core_spc_list[i].reactive and core_spc_list[j].reactive: spc_tuples.append((core_spc_list[i], core_spc_list[j])) if trimolecularReact is not None: for i in xrange(numOldCoreSpecies): for j in xrange(i, numOldCoreSpecies): for k in xrange(j, numOldCoreSpecies): # Find reactions involving the species that are trimolecular. if trimolecularReact[i, j, k]: if core_spc_list[i].reactive and core_spc_list[ j].reactive and core_spc_list[k].reactive: spc_tuples.append( (core_spc_list[i], core_spc_list[j], core_spc_list[k])) if procnum == 1: # React all families like normal (provide empty argument for only_families) spc_fam_tuples = zip(spc_tuples) else: # Identify and split families that are prone to generate many reactions into sublists. family_list = getDB('kinetics').families.keys() major_families = [ 'H_Abstraction', 'R_Recombination', 'Intra_Disproportionation', 'Intra_RH_Add_Endocyclic', 'Singlet_Carbene_Intra_Disproportionation', 'Intra_ene_reaction', 'Disproportionation', '1,4_Linear_birad_scission', 'R_Addition_MultipleBond', '2+2_cycloaddition_Cd', 'Diels_alder_addition', 'Intra_RH_Add_Exocyclic', 'Intra_Retro_Diels_alder_bicyclic', 'Intra_2+2_cycloaddition_Cd', 'Birad_recombination', 'Intra_Diels_alder_monocyclic', '1,4_Cyclic_birad_scission', '1,2_Insertion_carbene', ] split_list = [] leftovers = [] for fam in family_list: if fam in major_families: split_list.append([fam]) else: leftovers.append(fam) split_list.append(leftovers) # Only employ family splitting for reactants that have a larger number than min_atoms min_atoms = 10 spc_fam_tuples = [] for i, spc_tuple in enumerate(spc_tuples): if any( [len(spc.molecule[0].atoms) > min_atoms for spc in spc_tuple]): for item in split_list: spc_fam_tuples.append((spc_tuple, item)) else: spc_fam_tuples.append((spc_tuple, )) return react(spc_fam_tuples, procnum), [fam_tuple[0] for fam_tuple in spc_fam_tuples]
def generate_isotope_reactions(isotopeless_reactions, isotopes): """ Find the list of isotope reactions based on the reactions in the isotopeless reaction. uses the reactSpecies method to find reactions with proper degeneracies and then filters out those that don't match products. the proper reactions are given kinetics of the previous reaction modified for the degeneracy difference. """ # make sure all isotopeless reactions have templates and are TemplateReaction objects for rxn in isotopeless_reactions: if not isinstance(rxn, TemplateReaction): raise TypeError( 'reactions sent to generate_isotope_reactions must be a TemplateReaction object' ) if rxn.template is None: raise AttributeError( 'isotope reaction {0} does not have a template attribute. The object is:\n\n{1}' .format(str(rxn), repr(rxn))) found_reactions = [] rxn_index = 0 while rxn_index < len(isotopeless_reactions): rxn = isotopeless_reactions[rxn_index] # find all reactions involving same reactants rxns_w_same_reactants = [rxn] rxn_index2 = rxn_index + 1 while rxn_index2 < len(isotopeless_reactions): if isomorphic_species_lists( isotopeless_reactions[rxn_index].reactants, isotopeless_reactions[rxn_index2].reactants, ): rxns_w_same_reactants.append(isotopeless_reactions[rxn_index2]) del isotopeless_reactions[rxn_index2] else: rxn_index2 += 1 ##### find all pairs of reacting isotoper species ##### # find the lists of reactants that have identical isotopomers reactants = [] for reactant in rxn.reactants: for iso_index, isotopomers in enumerate(isotopes): if compare_isotopomers(reactant, isotopomers[0]): reactants.append(iso_index) break # find pairs of all reactants to react together reactant_pairs = [] if len(rxn.reactants) == 1: reactant_pairs = [[spec] for spec in isotopes[reactants[0]]] elif len(rxn.reactants) == 2: for spec1 in isotopes[reactants[0]]: for spec2 in isotopes[reactants[1]]: reactant_pairs.append([spec1, spec2]) else: raise ValueError('Cannot process reactions with over 2 reactants') # remove identical pairs rxn_index3 = 0 while rxn_index3 < len(reactant_pairs): rxn_index4 = rxn_index3 + 1 while rxn_index4 < len(reactant_pairs): if isomorphic_species_lists(reactant_pairs[rxn_index3], reactant_pairs[rxn_index4]): del reactant_pairs[rxn_index4] else: rxn_index4 += 1 rxn_index3 += 1 # make reaction objects for pair in reactant_pairs: # copy species so they don't get modified speciesTuple = tuple([spc.copy(deep=True) for spc in pair]) unfiltered_rxns = getDB( 'kinetics').generate_reactions_from_families( speciesTuple, only_families=[rxn.family]) # remove reactions whose products don't match the original reactions rxn_index5 = 0 while rxn_index5 < len(unfiltered_rxns): for isotopeless_reaction in rxns_w_same_reactants: isotopeless_kinetics = isotopeless_reaction.kinetics isotopeless_degeneracy = isotopeless_reaction.degeneracy if compare_isotopomers(isotopeless_reaction, unfiltered_rxns[rxn_index5],eitherDirection = False)\ and isotopeless_reaction.family == unfiltered_rxns[rxn_index5].family\ and frozenset(isotopeless_reaction.template) == \ frozenset(unfiltered_rxns[rxn_index5].template): # apply kinetics to new reaction & modify for degeneracy unfiltered_rxns[rxn_index5].kinetics = deepcopy( isotopeless_kinetics) unfiltered_rxns[rxn_index5].kinetics.changeRate( unfiltered_rxns[rxn_index5].degeneracy / isotopeless_degeneracy) rxn_index5 += 1 break else: # did not find same prodcuts del unfiltered_rxns[rxn_index5] found_reactions.extend(unfiltered_rxns) rxn_index += 1 return found_reactions
def processThermoData(spc, thermo0, thermoClass=NASA): """ Converts via Wilhoit into required `thermoClass` and sets `E0`. Resulting thermo is returned. """ # TODO moving this as a global import leads to circular imports. from rmgpy.rmg.model import Species thermo = None # Always convert to Wilhoit so we can compute E0 if isinstance(thermo0, Wilhoit): wilhoit = thermo0 elif isinstance(thermo0, ThermoData): wilhoit = thermo0.toWilhoit(B=1000.) else: wilhoit = thermo0.toWilhoit() # Add on solvation correction if Species.solventData and not "Liquid thermo library" in thermo0.comment: solvationdatabase = getDB('solvation') #logging.info("Making solvent correction for {0}".format(Species.solventName)) soluteData = solvationdatabase.getSoluteData(spc) solvation_correction = solvationdatabase.getSolvationCorrection( soluteData, Species.solventData) # correction is added to the entropy and enthalpy wilhoit.S0.value_si = (wilhoit.S0.value_si + solvation_correction.entropy) wilhoit.H0.value_si = (wilhoit.H0.value_si + solvation_correction.enthalpy) # Compute E0 by extrapolation to 0 K if spc.conformer is None: spc.conformer = Conformer() spc.conformer.E0 = wilhoit.E0 # Convert to desired thermo class if thermoClass is Wilhoit: thermo = wilhoit elif thermoClass is NASA: if Species.solventData: #if liquid phase simulation keep the nasa polynomial if it comes from a liquid phase thermoLibrary. Otherwise convert wilhoit to NASA if "Liquid thermo library" in thermo0.comment and isinstance( thermo0, NASA): thermo = thermo0 if thermo.E0 is None: thermo.E0 = wilhoit.E0 else: thermo = wilhoit.toNASA(Tmin=100.0, Tmax=5000.0, Tint=1000.0) else: #gas phase with species matching thermo library keep the NASA from library or convert if group additivity if "Thermo library" in thermo0.comment and isinstance( thermo0, NASA): thermo = thermo0 if thermo.E0 is None: thermo.E0 = wilhoit.E0 else: thermo = wilhoit.toNASA(Tmin=100.0, Tmax=5000.0, Tint=1000.0) else: raise Exception( 'thermoClass neither NASA nor Wilhoit. Cannot process thermo data.' ) if thermo.__class__ != thermo0.__class__: # Compute RMS error of overall transformation Tlist = numpy.array( [300.0, 400.0, 500.0, 600.0, 800.0, 1000.0, 1500.0], numpy.float64) err = 0.0 for T in Tlist: err += (thermo.getHeatCapacity(T) - thermo0.getHeatCapacity(T))**2 err = math.sqrt(err / len(Tlist)) / constants.R # logging.log(logging.WARNING if err > 0.2 else 0, 'Average RMS error in heat capacity fit to {0} = {1:g}*R'.format(spc, err)) return thermo
def testaddReverseAttribute(self): """ tests that the addReverseAttribute method gets the reverse degeneracy correct """ from rmgpy.data.rmg import getDB from rmgpy.data.kinetics.family import TemplateReaction adjlist = [ ''' multiplicity 2 1 H u0 p0 c0 {7,S} 2 H u0 p0 c0 {4,S} 3 C u1 p0 c0 {5,S} {7,S} {8,S} 4 C u0 p0 c0 {2,S} {6,S} {7,D} 5 H u0 p0 c0 {3,S} 6 H u0 p0 c0 {4,S} 7 C u0 p0 c0 {1,S} {3,S} {4,D} 8 H u0 p0 c0 {3,S} ''', ''' 1 C u0 p0 c0 {2,S} {4,S} {5,S} {6,S} 2 C u0 p0 c0 i13 {1,S} {3,D} {7,S} 3 C u0 p0 c0 {2,D} {8,S} {9,S} 4 H u0 p0 c0 {1,S} 5 H u0 p0 c0 {1,S} 6 H u0 p0 c0 {1,S} 7 H u0 p0 c0 {2,S} 8 H u0 p0 c0 {3,S} 9 H u0 p0 c0 {3,S} ''', ''' multiplicity 2 1 H u0 p0 c0 {7,S} 2 H u0 p0 c0 {4,S} 3 C u1 p0 c0 {5,S} {7,S} {8,S} 4 C u0 p0 c0 {2,S} {6,S} {7,D} 5 H u0 p0 c0 {3,S} 6 H u0 p0 c0 {4,S} 7 C u0 p0 c0 i13 {1,S} {3,S} {4,D} 8 H u0 p0 c0 {3,S} ''', ''' 1 C u0 p0 c0 {2,S} {4,S} {5,S} {6,S} 2 C u0 p0 c0 {1,S} {3,D} {7,S} 3 C u0 p0 c0 {2,D} {8,S} {9,S} 4 H u0 p0 c0 {1,S} 5 H u0 p0 c0 {1,S} 6 H u0 p0 c0 {1,S} 7 H u0 p0 c0 {2,S} 8 H u0 p0 c0 {3,S} 9 H u0 p0 c0 {3,S} ''' ] family = getDB('kinetics').families['H_Abstraction'] r1 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[0])]) r2 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[1])]) p1 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[2])]) p2 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[3])]) r1.generateResonanceIsomers(keepIsomorphic=True) p1.generateResonanceIsomers(keepIsomorphic=True) rxn = TemplateReaction(reactants=[r1, r2], products=[p1, p2]) rxn.degeneracy = family.calculateDegeneracy(rxn) self.assertEqual(rxn.degeneracy, 6) family.addReverseAttribute(rxn) self.assertEqual(rxn.reverse.degeneracy, 6)
def generate_isotope_reactions(isotopeless_reactions, isotopes): """ Find the list of isotope reactions based on the reactions in the isotopeless reaction. uses the reactSpecies method to find reactions with proper degeneracies and then filters out those that don't match products. the proper reactions are given kinetics of the previous reaction modified for the degeneracy difference. """ # make sure all isotopeless reactions have templates and are TemplateReaction objects for rxn in isotopeless_reactions: if not isinstance(rxn,TemplateReaction): raise TypeError('reactions sent to generate_isotope_reactions must be a TemplateReaction object') if rxn.template is None: raise AttributeError('isotope reaction {0} does not have a template attribute. The object is:\n\n{1}'.format(str(rxn),repr(rxn))) found_reactions = [] rxn_index = 0 while rxn_index < len(isotopeless_reactions): rxn = isotopeless_reactions[rxn_index] # find all reactions involving same reactants rxns_w_same_reactants = [rxn] rxn_index2 = rxn_index + 1 while rxn_index2 < len(isotopeless_reactions): if isomorphic_species_lists(isotopeless_reactions[rxn_index].reactants, isotopeless_reactions[rxn_index2].reactants, ): rxns_w_same_reactants.append(isotopeless_reactions[rxn_index2]) del isotopeless_reactions[rxn_index2] else: rxn_index2 += 1 ##### find all pairs of reacting isotoper species ##### # find the lists of reactants that have identical isotopomers reactants = [] for reactant in rxn.reactants: for iso_index, isotopomers in enumerate(isotopes): if compare_isotopomers(reactant,isotopomers[0]): reactants.append(iso_index) break # find pairs of all reactants to react together reactant_pairs = [] if len(rxn.reactants) == 1: reactant_pairs = [[spec] for spec in isotopes[reactants[0]]] elif len(rxn.reactants) == 2: for spec1 in isotopes[reactants[0]]: for spec2 in isotopes[reactants[1]]: reactant_pairs.append([spec1, spec2]) else: raise ValueError('Cannot process reactions with over 2 reactants') # remove identical pairs rxn_index3 = 0 while rxn_index3 < len(reactant_pairs): rxn_index4 = rxn_index3 + 1 while rxn_index4 < len(reactant_pairs): if isomorphic_species_lists(reactant_pairs[rxn_index3], reactant_pairs[rxn_index4]): del reactant_pairs[rxn_index4] else: rxn_index4 += 1 rxn_index3 += 1 # make reaction objects for pair in reactant_pairs: # copy species so they don't get modified speciesTuple = tuple([spc.copy(deep=True) for spc in pair]) unfiltered_rxns = getDB('kinetics').generate_reactions_from_families(speciesTuple,only_families=[rxn.family]) # remove reactions whose products don't match the original reactions rxn_index5 = 0 while rxn_index5 < len(unfiltered_rxns): for isotopeless_reaction in rxns_w_same_reactants: isotopeless_kinetics = isotopeless_reaction.kinetics isotopeless_degeneracy = isotopeless_reaction.degeneracy if compare_isotopomers(isotopeless_reaction, unfiltered_rxns[rxn_index5],eitherDirection = False)\ and isotopeless_reaction.family == unfiltered_rxns[rxn_index5].family\ and frozenset(isotopeless_reaction.template) == \ frozenset(unfiltered_rxns[rxn_index5].template): # apply kinetics to new reaction & modify for degeneracy unfiltered_rxns[rxn_index5].kinetics = deepcopy(isotopeless_kinetics) unfiltered_rxns[rxn_index5].kinetics.changeRate(unfiltered_rxns[rxn_index5].degeneracy / isotopeless_degeneracy) rxn_index5 += 1 break else: # did not find same prodcuts del unfiltered_rxns[rxn_index5] found_reactions.extend(unfiltered_rxns) rxn_index += 1 return found_reactions
def execute(self, outputFile, plot, format='pdf', print_summary=True, speciesList=None, thermoLibrary=None, kineticsLibrary=None): logging.info('Exploring network...') rmg = RMG() rmg.speciesConstraints = { 'allowed': ['input species', 'seed mechanisms', 'reaction libraries'], 'maximumRadicalElectrons': self.maximumRadicalElectrons, 'explicitlyAllowedMolecules': [] } rmgpy.rmg.input.rmg = rmg reaction_model = CoreEdgeReactionModel() reaction_model.pressureDependence = self.pdepjob reaction_model.pressureDependence.rmgmode = True if outputFile: reaction_model.pressureDependence.outputFile = os.path.dirname( outputFile) kineticsDatabase = getDB('kinetics') thermoDatabase = getDB('thermo') thermoDatabase.libraries['thermojobs'] = thermoLibrary thermoDatabase.libraryOrder.insert(0, 'thermojobs') kineticsDatabase.libraries['kineticsjobs'] = kineticsLibrary kineticsDatabase.libraryOrder.insert( 0, ('kineticsjobs', 'Reaction Library')) jobRxns = [rxn for rxn in reaction_model.core.reactions] self.jobRxns = jobRxns if outputFile is not None: if not os.path.exists( os.path.join(reaction_model.pressureDependence.outputFile, 'pdep')): os.mkdir( os.path.join(reaction_model.pressureDependence.outputFile, 'pdep')) else: shutil.rmtree( os.path.join(reaction_model.pressureDependence.outputFile, 'pdep')) os.mkdir( os.path.join(reaction_model.pressureDependence.outputFile, 'pdep')) # get the molecular formula for the network mmol = None for spc in self.source: if mmol: mmol.merge(spc.molecule[0]) else: mmol = spc.molecule[0] form = mmol.getFormula() for spec in self.bathGas.keys() + self.source: nspec, isNew = reaction_model.makeNewSpecies(spec, reactive=False) flags = np.array([ s.molecule[0].getFormula() == form for s in reaction_model.core.species ]) reaction_model.enlarge(nspec, reactEdge=False, unimolecularReact=flags, bimolecularReact=np.zeros( (len(reaction_model.core.species), len(reaction_model.core.species)))) reaction_model.addSeedMechanismToCore('kineticsjobs') for lib in kineticsDatabase.libraryOrder: if lib[0] != 'kineticsjobs': reaction_model.addReactionLibraryToEdge(lib[0]) for spc in reaction_model.core.species: for i, item in enumerate(self.source): if spc.isIsomorphic(item): self.source[i] = spc # react initial species flags = np.array([ s.molecule[0].getFormula() == form for s in reaction_model.core.species ]) reaction_model.enlarge(reactEdge=True, unimolecularReact=flags, bimolecularReact=np.zeros( (len(reaction_model.core.species), len(reaction_model.core.species)))) # find the network we're interested in for nwk in reaction_model.networkList: if set(nwk.source) == set(self.source): self.source = nwk.source network = nwk break else: raise ValueError( 'Did not generate a network with the requested source. This usually means no unimolecular' 'reactions were generated for the source. Note that library reactions that are not' ' properly flagged as elementary_high_p can replace RMG generated reactions that would' ' otherwise be part of networks.') network.bathGas = self.bathGas self.network = network # determine T and P combinations if self.pdepjob.Tlist: Tlist = self.pdepjob.Tlist.value_si else: Tlist = np.linspace(self.pdepjob.Tmin.value_si, self.pdepjob.Tmax.value_si, self.pdepjob.Tcount) if self.pdepjob.Plist: Plist = self.pdepjob.Plist.value_si else: Plist = np.linspace(self.pdepjob.Pmin.value_si, self.pdepjob.Pmax.value_si, self.pdepjob.Pcount) # generate the network forbiddenStructures = getDB('forbidden') incomplete = True while incomplete: incomplete = False for T in Tlist: for P in Plist: if network.getLeakCoefficient(T=T, P=P) > self.explore_tol: incomplete = True spc = network.getMaximumLeakSpecies(T=T, P=P) if forbiddenStructures.isMoleculeForbidden( spc.molecule[0]): reaction_model.removeSpeciesFromEdge( reaction_model.reactionSystems, spc) reaction_model.removeEmptyPdepNetworks() logging.error(spc.label) else: logging.info( 'adding new isomer {0} to network'.format(spc)) flags = np.array([ s.molecule[0].getFormula() == form for s in reaction_model.core.species ]) reaction_model.enlarge( (network, spc), reactEdge=False, unimolecularReact=flags, bimolecularReact=np.zeros( (len(reaction_model.core.species), len(reaction_model.core.species)))) flags = np.array([ s.molecule[0].getFormula() == form for s in reaction_model.core.species ]) reaction_model.enlarge( reactEdge=True, unimolecularReact=flags, bimolecularReact=np.zeros( (len(reaction_model.core.species), len(reaction_model.core.species)))) rmRxns = [] for rxn in network.pathReactions: # remove reactions with forbidden species for r in rxn.reactants + rxn.products: if forbiddenStructures.isMoleculeForbidden(r.molecule[0]): rmRxns.append(rxn) for rxn in rmRxns: logging.info('Removing forbidden reaction: {0}'.format(rxn)) network.pathReactions.remove(rxn) # clean up output files if outputFile is not None: path = os.path.join(reaction_model.pressureDependence.outputFile, 'pdep') for name in os.listdir(path): if name.endswith('.py') and '_' in name: if name.split('_')[-1].split('.')[0] != str( len(network.isomers)): os.remove(os.path.join(path, name)) else: os.rename(os.path.join(path, name), os.path.join(path, 'network_full.py')) warns = [] for rxn in jobRxns: if rxn not in network.pathReactions: warns.append( 'Reaction {0} in the input file was not explored during network expansion and was not included in the full network. This is likely because your explore_tol value is too high.' .format(rxn)) # reduction process if self.energy_tol != np.inf or self.flux_tol != 0.0: rxnSet = None for T in Tlist: if self.energy_tol != np.inf: rxns = network.get_energy_filtered_reactions( T, self.energy_tol) if rxnSet is not None: rxnSet &= set(rxns) else: rxnSet = set(rxns) for P in Plist: if self.flux_tol != 0.0: rxns = network.get_rate_filtered_reactions( T, P, self.flux_tol) if rxnSet is not None: rxnSet &= set(rxns) else: rxnSet = set(rxns) logging.info('removing reactions during reduction:') for rxn in rxnSet: logging.info(rxn) network.remove_reactions(reaction_model, list(rxnSet)) for rxn in jobRxns: if rxn not in network.pathReactions: warns.append( 'Reaction {0} in the input file was not included in the reduced model.' .format(rxn)) self.network = network self.pdepjob.network = network self.pdepjob.execute(outputFile, plot, format='pdf', print_summary=True) if warns != []: logging.info('\nOUTPUT WARNINGS:\n') for w in warns: logging.warning(w)
def species(label, *args, **kwargs): global speciesDict, jobList if label in speciesDict: raise ValueError('Multiple occurrences of species with label {0!r}.'.format(label)) logging.info('Loading species {0}...'.format(label)) spec = Species(label=label) speciesDict[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)) jobList.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 = [] spinMultiplicity = 0 opticalIsomers = 1 molecularWeight = None collisionModel = None energyTransferModel = 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': spinMultiplicity = value elif key == 'opticalIsomers': opticalIsomers = value elif key == 'molecularWeight': molecularWeight = value elif key == 'collisionModel': collisionModel = value elif key == 'energyTransferModel': energyTransferModel = 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, spinMultiplicity=spinMultiplicity, opticalIsomers=opticalIsomers) if molecularWeight is not None: spec.molecularWeight = molecularWeight elif spec.molecularWeight is None and is_pdep(jobList): # If a structure was given, simply calling spec.molecularWeight 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.transportData = collisionModel spec.energyTransferModel = energyTransferModel 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 = getDB('thermo') if db is None: raise DatabaseError('Thermo database is None.') except DatabaseError: logging.warn("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.getThermoData(spec) if spec.thermo.E0 is None: th = spec.thermo.toWilhoit() 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.hasStatMech() and structure is not None: # generate stat mech info if it wasn't provided before spec.generateStatMech() if not energyTransferModel: # default to RMG's method of generating energyTransferModel spec.generateEnergyTransferModel() return spec