def loadEntry(self, index, label, solvent, molecule=None, reference=None, referenceType='', shortDesc='', longDesc='', ): spc = molecule if molecule is not None: try: spc = Species().fromSMILES(molecule) except: logging.debug("Solvent '{0}' does not have a valid SMILES '{1}'" .format(label, molecule)) try: spc = Species().fromAdjacencyList(molecule) except: logging.error("Can't understand '{0}' in solute library '{1}'".format(molecule, self.name)) raise spc.generateResonanceIsomers() self.entries[label] = Entry( index = index, label = label, item = spc, data = solvent, reference = reference, referenceType = referenceType, shortDesc = shortDesc, longDesc = longDesc.strip(), )
def getRMGSpeciesFromSMILES(smilesList, speciesList, names=False): """ Args: smilesList: list of SMIlES for species of interest speciesList: a list of RMG species objects names: set to `True` if species names are desired to be returned instead of objects Returns: A dict containing the smiles as keys and RMG species objects as their values If the species is not found, the value will be returned as None """ # Not strictly necesssary, but its likely that people will forget to put the brackets around the bath gasses bathGases={"Ar": "[Ar]", "He": "[He]", "Ne": "[Ne]"} mapping = {} for smiles in smilesList: if smiles in bathGases: spec = Species().fromSMILES(bathGases[smiles]) else: spec=Species().fromSMILES(smiles) spec.generateResonanceIsomers() for rmgSpecies in speciesList: if spec.isIsomorphic(rmgSpecies): if smiles in mapping: raise KeyError("The SMILES {0} has appeared twice in the species list!".format(smiles)) mapping[smiles] = rmgSpecies break else: mapping[smiles] = None return mapping
def testOldThermoGeneration(self): """ Test that the old ThermoDatabase generates relatively accurate thermo data. """ for smiles, symm, H298, S298, Cp300, Cp400, Cp500, Cp600, Cp800, Cp1000, Cp1500 in self.testCases: Cplist = [Cp300, Cp400, Cp500, Cp600, Cp800, Cp1000, Cp1500] species = Species(molecule=[Molecule(SMILES=smiles)]) species.generateResonanceIsomers() thermoData = self.oldDatabase.getThermoData(Species(molecule=[species.molecule[0]])) molecule = species.molecule[0] for mol in species.molecule[1:]: thermoData0 = self.oldDatabase.getAllThermoData(Species(molecule=[mol]))[0][0] for data in self.oldDatabase.getAllThermoData(Species(molecule=[mol]))[1:]: if data.getEnthalpy(298) < thermoData0.getEnthalpy(298): thermoData0 = data if thermoData0.getEnthalpy(298) < thermoData.getEnthalpy(298): thermoData = thermoData0 molecule = mol self.assertEqual(molecule.calculateSymmetryNumber(), symm) self.assertTrue(1 - thermoData.getEnthalpy(298) / 4184 / H298 < 0.01) self.assertTrue(1 - thermoData.getEntropy(298) / 4.184 / S298 < 0.01) for T, Cp in zip(self.Tlist, Cplist): self.assertTrue(1 - thermoData.getHeatCapacity(T) / 4.184 / Cp < 0.1)
def testNewThermoGeneration(self): """ Test that the new ThermoDatabase generates appropriate thermo data. """ for smiles, symm, H298, S298, Cp300, Cp400, Cp500, Cp600, Cp800, Cp1000, Cp1500 in self.testCases: Cplist = [Cp300, Cp400, Cp500, Cp600, Cp800, Cp1000, Cp1500] species = Species(molecule=[Molecule(SMILES=smiles)]) species.generateResonanceIsomers() species.molecule[0] thermoData = self.database.getThermoDataFromGroups(Species(molecule=[species.molecule[0]]))[0] molecule = species.molecule[0] for mol in species.molecule[1:]: thermoData0 = self.database.getAllThermoData(Species(molecule=[mol]))[0][0] for data in self.database.getAllThermoData(Species(molecule=[mol]))[1:]: if data.getEnthalpy(298) < thermoData0.getEnthalpy(298): thermoData0 = data if thermoData0.getEnthalpy(298) < thermoData.getEnthalpy(298): thermoData = thermoData0 molecule = mol self.assertEqual(molecule.calculateSymmetryNumber(), symm) self.assertAlmostEqual(H298, thermoData.getEnthalpy(298) / 4184, places=1) self.assertAlmostEqual(S298, thermoData.getEntropy(298) / 4.184, places=1) for T, Cp in zip(self.Tlist, Cplist): self.assertAlmostEqual(Cp, thermoData.getHeatCapacity(T) / 4.184, places=1)
def test_singlet_vs_closed_shell(self): adjlist_singlet = """ 1 C u0 p0 c0 {2,D} {3,S} {4,S} 2 C u0 p0 c0 {1,D} {3,S} {5,S} 3 C u0 p1 c0 {1,S} {2,S} 4 H u0 p0 c0 {1,S} 5 H u0 p0 c0 {2,S} """ adjlist_closed_shell = """ 1 C u0 p0 c0 {2,D} {3,S} {4,S} 2 C u0 p0 c0 {1,D} {3,D} 3 C u0 p0 c0 {1,S} {2,D} {5,S} 4 H u0 p0 c0 {1,S} 5 H u0 p0 c0 {3,S} """ singlet = Species(molecule=[Molecule().fromAdjacencyList(adjlist_singlet)]) singlet.generateResonanceIsomers() closed_shell = Species(molecule=[Molecule().fromAdjacencyList(adjlist_closed_shell)]) closed_shell.generateResonanceIsomers() singlet_aug_inchi = singlet.getAugmentedInChI() closed_shell_aug_inchi = closed_shell.getAugmentedInChI() self.assertTrue(singlet_aug_inchi != closed_shell_aug_inchi)
def testResonaceIsomersRepresented(self): "Test that both resonance forms of 1-penten-3-yl are printed by __repr__" spec = Species().fromSMILES('C=C[CH]CC') spec.generateResonanceIsomers() exec('spec2 = {0!r}'.format(spec)) self.assertEqual(len(spec.molecule), len(spec2.molecule)) for i, j in zip(spec.molecule, spec2.molecule): self.assertTrue(i.isIsomorphic(j))
def compare(self, adjlist, aug_inchi): spc = Species(molecule=[Molecule().fromAdjacencyList(adjlist)]) spc.generateResonanceIsomers() ignore_prefix = r"(InChI=1+)(S*)/" exp = re.split(ignore_prefix, aug_inchi)[-1] comp = re.split(ignore_prefix, spc.getAugmentedInChI())[-1] self.assertEquals(exp, comp)
def testTotalSymmetryNumber14Dimethylbenzene(self): """ Test the Species.getSymmetryNumber() (total symmetry) on Cc1ccc(C)cc1 """ molecule = Molecule().fromSMILES("Cc1ccc(C)cc1") species = Species(molecule=[molecule]) species.generateResonanceIsomers() symmetryNumber = species.getSymmetryNumber() self.assertEqual(symmetryNumber, 36)
def testTotalSymmetryNumberToluene(self): """ Test the Species.getSymmetryNumber() (total symmetry) on c1ccccc1C """ molecule = Molecule().fromSMILES("c1ccccc1C") species = Species(molecule=[molecule]) species.generateResonanceIsomers() symmetryNumber = species.getSymmetryNumber() self.assertEqual(symmetryNumber, 3)
def filterReactions(reactants, products, reactionList): """ Remove any reactions from the given `reactionList` whose reactants do not involve all the given `reactants` or whose products do not involve all the given `products`. This method checks both forward and reverse directions, and only filters out reactions that don't match either. """ # Convert from molecules to species and generate resonance isomers. reactant_species = [] for mol in reactants: s = Species(molecule=[mol]) s.generateResonanceIsomers() reactant_species.append(s) reactants = reactant_species product_species = [] for mol in products: s = Species(molecule=[mol]) s.generateResonanceIsomers() product_species.append(s) products = product_species reactions = reactionList[:] for reaction in reactionList: # Forward direction reactants0 = [r for r in reaction.reactants] for reactant in reactants: for reactant0 in reactants0: if reactant.isIsomorphic(reactant0): reactants0.remove(reactant0) break products0 = [p for p in reaction.products] for product in products: for product0 in products0: if product.isIsomorphic(product0): products0.remove(product0) break forward = not (len(reactants0) != 0 or len(products0) != 0) # Reverse direction reactants0 = [r for r in reaction.products] for reactant in reactants: for reactant0 in reactants0: if reactant.isIsomorphic(reactant0): reactants0.remove(reactant0) break products0 = [p for p in reaction.reactants] for product in products: for product0 in products0: if product.isIsomorphic(product0): products0.remove(product0) break reverse = not (len(reactants0) != 0 or len(products0) != 0) if not forward and not reverse: reactions.remove(reaction) return reactions
def test_CCCO_triplet(self): adjlist = """ multiplicity 3 1 C u0 p0 c0 {2,D} {5,S} {6,S} 2 C u0 p0 c0 {1,D} {3,S} {7,S} 3 C u1 p0 c0 {2,S} {4,S} {8,S} 4 O u1 p2 c0 {3,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} """ mol = Molecule().fromAdjacencyList(adjlist) spc = Species(molecule=[mol]) spc.generateResonanceIsomers() aug_inchi = spc.getAugmentedInChI() self.assertEqual(Species(molecule=[Molecule().fromAugmentedInChI(aug_inchi)]).isIsomorphic(spc), True)
def compare(self, inchi, u_indices=[], p_indices = []): u_layer = U_LAYER_PREFIX + U_LAYER_SEPARATOR.join(map(str, u_indices)) if u_indices else None p_layer = P_LAYER_PREFIX + P_LAYER_SEPARATOR.join(map(str, p_indices)) if p_indices else None aug_inchi = compose_aug_inchi(inchi, u_layer, p_layer) mol = fromAugmentedInChI(Molecule(), aug_inchi) ConsistencyChecker.check_multiplicity(mol.getRadicalCount(), mol.multiplicity) for at in mol.atoms: ConsistencyChecker.check_partial_charge(at) spc = Species(molecule=[mol]) spc.generateResonanceIsomers() ignore_prefix = r"(InChI=1+)(S*)/" aug_inchi_expected = re.split(ignore_prefix, aug_inchi)[-1] aug_inchi_computed = re.split(ignore_prefix, spc.getAugmentedInChI())[-1] self.assertEquals(aug_inchi_expected, aug_inchi_computed) return mol
def testSymmetryNumberGeneration(self): """ Test we generate symmetry numbers correctly. This uses the new thermo database to generate the H298, used to select the stablest resonance isomer. """ for smiles, symm, H298, S298, Cp300, Cp400, Cp500, Cp600, Cp800, Cp1000, Cp1500 in self.testCases: species = Species(molecule=[Molecule(SMILES=smiles)]) species.generateResonanceIsomers() thermoData = self.database.getThermoDataFromGroups(Species(molecule=[species.molecule[0]])) # pick the molecule with lowest H298 molecule = species.molecule[0] for mol in species.molecule[1:]: thermoData0 = self.database.getAllThermoData(Species(molecule=[mol]))[0][0] for data in self.database.getAllThermoData(Species(molecule=[mol]))[1:]: if data.getEnthalpy(298) < thermoData0.getEnthalpy(298): thermoData0 = data if thermoData0.getEnthalpy(298) < thermoData.getEnthalpy(298): thermoData = thermoData0 molecule = mol self.assertEqual(molecule.calculateSymmetryNumber(), symm, msg="Symmetry number error for {0}".format(smiles))
def loadEntry( self, index, label, solvent, molecule=None, reference=None, referenceType='', shortDesc='', longDesc='', ): spc = molecule if molecule is not None: try: spc = Species().fromSMILES(molecule) except: logging.debug( "Solvent '{0}' does not have a valid SMILES '{1}'".format( label, molecule)) try: spc = Species().fromAdjacencyList(molecule) except: logging.error( "Can't understand '{0}' in solute library '{1}'". format(molecule, self.name)) raise spc.generateResonanceIsomers() self.entries[label] = Entry( index=index, label=label, item=spc, data=solvent, reference=reference, referenceType=referenceType, shortDesc=shortDesc, longDesc=longDesc.strip(), )
def getReactionForEntry(entry, database): """ Return a Reaction object for a given entry that uses Species instead of Molecules (so that we can compute the reaction thermo). """ reaction = Reaction(reactants=[], products=[]) for molecule in entry.item.reactants: molecule.makeHydrogensExplicit() reactant = Species(molecule=[molecule], label=molecule.toSMILES()) reactant.generateResonanceIsomers() reactant.thermo = generateThermoData(reactant, database) reaction.reactants.append(reactant) for molecule in entry.item.products: molecule.makeHydrogensExplicit() product = Species(molecule=[molecule], label=molecule.toSMILES()) product.generateResonanceIsomers() product.thermo = generateThermoData(product, database) reaction.products.append(product) reaction.kinetics = entry.data reaction.degeneracy = entry.item.degeneracy return reaction
def testSymmetryNumberGeneration(self): """ Test we generate symmetry numbers correctly. This uses the new thermo database to generate the H298, used to select the stablest resonance isomer. """ for smiles, symm, H298, S298, Cp300, Cp400, Cp500, Cp600, Cp800, Cp1000, Cp1500 in self.testCases: molecule=Molecule(SMILES=smiles) species = Species(molecule=molecule) species.generateResonanceIsomers() thermoData = self.database.getThermoDataFromGroups(Species(molecule=[species.molecule[0]])) # pick the molecule with lowest H298 molecule = species.molecule[0] for mol in species.molecule[1:]: thermoData0 = self.database.getAllThermoData(Species(molecule=[mol]))[0][0] for data in self.database.getAllThermoData(Species(molecule=[mol]))[1:]: if data.getEnthalpy(298) < thermoData0.getEnthalpy(298): thermoData0 = data if thermoData0.getEnthalpy(298) < thermoData.getEnthalpy(298): thermoData = thermoData0 molecule = mol self.assertEqual(molecule.calculateSymmetryNumber(), symm, msg="Symmetry number error for {0}".format(smiles))
def testOldThermoGeneration(self): """ Test that the old ThermoDatabase generates relatively accurate thermo data. """ for smiles, symm, H298, S298, Cp300, Cp400, Cp500, Cp600, Cp800, Cp1000, Cp1500 in self.testCases: Cplist = [Cp300, Cp400, Cp500, Cp600, Cp800, Cp1000, Cp1500] species = Species(molecule=[Molecule(SMILES=smiles)]) species.generateResonanceIsomers() thermoData = self.oldDatabase.getThermoData(Species(molecule=[species.molecule[0]])) molecule = species.molecule[0] for mol in species.molecule[1:]: thermoData0 = self.oldDatabase.getAllThermoData(Species(molecule=[mol]))[0][0] for data in self.oldDatabase.getAllThermoData(Species(molecule=[mol]))[1:]: if data.getEnthalpy(298) < thermoData0.getEnthalpy(298): thermoData0 = data if thermoData0.getEnthalpy(298) < thermoData.getEnthalpy(298): thermoData = thermoData0 molecule = mol self.assertAlmostEqual(H298, thermoData.getEnthalpy(298) / 4184, places=1, msg="H298 error for {0}".format(smiles)) self.assertAlmostEqual(S298, thermoData.getEntropy(298) / 4.184, places=1, msg="S298 error for {0}".format(smiles)) for T, Cp in zip(self.Tlist, Cplist): self.assertAlmostEqual(Cp, thermoData.getHeatCapacity(T) / 4.184, places=1, msg="Cp{1} error for {0}".format(smiles, T))
def convertToSpeciesObjects(reaction): """ modifies a reaction holding Molecule objects to a reaction holding Species objects, with generated resonance isomers. """ # if already species' objects, return none if isinstance(reaction.reactants[0], Species): return None # obtain species with all resonance isomers for i, mol in enumerate(reaction.reactants): spec = Species(molecule=[mol]) spec.generateResonanceIsomers(keepIsomorphic=True) reaction.reactants[i] = spec for i, mol in enumerate(reaction.products): spec = Species(molecule=[mol]) spec.generateResonanceIsomers(keepIsomorphic=True) reaction.products[i] = spec # convert reaction.pairs object to species newPairs = [] for reactant, product in reaction.pairs: newPair = [] for reactant0 in reaction.reactants: if reactant0.isIsomorphic(reactant): newPair.append(reactant0) break for product0 in reaction.products: if product0.isIsomorphic(product): newPair.append(product0) break newPairs.append(newPair) reaction.pairs = newPairs try: convertToSpeciesObjects(reaction.reverse) except AttributeError: pass
def testNewThermoGeneration(self): """ Test that the new ThermoDatabase generates appropriate thermo data. """ for smiles, symm, H298, S298, Cp300, Cp400, Cp500, Cp600, Cp800, Cp1000, Cp1500 in self.testCases: Cplist = [Cp300, Cp400, Cp500, Cp600, Cp800, Cp1000, Cp1500] species = Species(molecule=[Molecule(SMILES=smiles)]) species.generateResonanceIsomers() species.molecule[0] thermoData = self.database.getThermoDataFromGroups( Species(molecule=[species.molecule[0]])) molecule = species.molecule[0] for mol in species.molecule[1:]: thermoData0 = self.database.getAllThermoData( Species(molecule=[mol]))[0][0] for data in self.database.getAllThermoData( Species(molecule=[mol]))[1:]: if data.getEnthalpy(298) < thermoData0.getEnthalpy(298): thermoData0 = data if thermoData0.getEnthalpy(298) < thermoData.getEnthalpy(298): thermoData = thermoData0 molecule = mol self.assertAlmostEqual(H298, thermoData.getEnthalpy(298) / 4184, places=1, msg="H298 error for {0}".format(smiles)) self.assertAlmostEqual(S298, thermoData.getEntropy(298) / 4.184, places=1, msg="S298 error for {0}".format(smiles)) for T, Cp in zip(self.Tlist, Cplist): self.assertAlmostEqual(Cp, thermoData.getHeatCapacity(T) / 4.184, places=1, msg="Cp{1} error for {0}".format( smiles, T))
def getForwardReactionForFamilyEntry(self, entry, family, groups, rxnFamily): """ For a given `entry` for a reaction of the given reaction `family` (the string label of the family), return the reaction with transition state distances for the "forward" direction as defined by the reaction family. For families that are their own reverse, the direction the kinetics is given in will be preserved. If the entry contains functional groups for the reactants, assume that it is given in the forward direction and do nothing. Returns the reaction in the direction consistent with the reaction family template, and the matching template. """ def matchSpeciesToMolecules(species, molecules): if len(species) == len(molecules) == 1: return species[0].isIsomorphic(molecules[0]) elif len(species) == len(molecules) == 2: if species[0].isIsomorphic( molecules[0]) and species[1].isIsomorphic( molecules[1]): return True elif species[0].isIsomorphic( molecules[1]) and species[1].isIsomorphic( molecules[0]): return True return False reaction = None template = None # Get the indicated reaction family if groups is None: raise ValueError( 'Invalid value "{0}" for family parameter.'.format(family)) if all([(isinstance(reactant, Group) or isinstance(reactant, LogicNode)) for reactant in entry.item.reactants]): # The entry is a rate rule, containing functional groups only # By convention, these are always given in the forward direction and # have kinetics defined on a per-site basis reaction = Reaction( reactants=entry.item.reactants[:], products=[], kinetics=entry.data, degeneracy=1, ) template = [ groups.entries[label] for label in entry.label.split(';') ] elif (all([ isinstance(reactant, (RMGMolecule, RMGSpecies)) for reactant in entry.item.reactants ]) and all([ isinstance(product, (RMGMolecule, RMGSpecies)) for product in entry.item.products ])): # The entry is a real reaction, containing molecules # These could be defined for either the forward or reverse direction # and could have a reaction-path degeneracy reaction = Reaction(reactants=[], products=[]) for molecule in entry.item.reactants: if isinstance(molecule, RMGMolecule): reactant = RMGSpecies(molecule=[molecule]) else: reactant = molecule reactant.generateResonanceIsomers() reaction.reactants.append(reactant) for molecule in entry.item.products: if isinstance(molecule, RMGMolecule): product = RMGSpecies(molecule=[molecule]) else: product = molecule product.generateResonanceIsomers() reaction.products.append(product) # Generate all possible reactions involving the reactant species generatedReactions = self.generateReactionsFromFamilies( [reactant.molecule[0] for reactant in reaction.reactants], [], only_families=[family], families=rxnFamily) # Remove from that set any reactions that don't produce the desired # reactants and products forward = [] reverse = [] for rxn in generatedReactions: if matchSpeciesToMolecules( reaction.reactants, rxn.reactants) and matchSpeciesToMolecules( reaction.products, rxn.products): forward.append(rxn) if matchSpeciesToMolecules( reaction.reactants, rxn.products) and matchSpeciesToMolecules( reaction.products, rxn.reactants): reverse.append(rxn) # We should now know whether the reaction is given in the forward or # reverse direction if len(forward) == 1 and len(reverse) == 0: # The reaction is in the forward direction, so use as-is reaction = forward[0] template = reaction.template # Don't forget to overwrite the estimated distances from the # database with the distances for this entry reaction.distances = entry.data elif len(reverse) == 1 and len(forward) == 0: # The reaction is in the reverse direction # The reaction is in the forward direction, so use as-is reaction = forward[0] template = reaction.template # The distances to the H atom are reversed reaction.distances = entry.data reaction.distances['d12'] = entry.data['d23'] reaction.distances['d23'] = entry.data['d12'] elif len(reverse) > 0 and len(forward) > 0: print('FAIL: Multiple reactions found for {0!r}.'.format( entry.label)) elif len(reverse) == 0 and len(forward) == 0: print('FAIL: No reactions found for "%s".' % (entry.label)) else: print('FAIL: Unable to estimate distances for {0!r}.'.format( entry.label)) assert reaction is not None assert template is not None return reaction, template
def getForwardReactionForFamilyEntry(self, entry, family, thermoDatabase): """ For a given `entry` for a reaction of the given reaction `family` (the string label of the family), return the reaction with kinetics and degeneracy for the "forward" direction as defined by the reaction family. For families that are their own reverse, the direction the kinetics is given in will be preserved. If the entry contains functional groups for the reactants, assume that it is given in the forward direction and do nothing. Returns the reaction in the direction consistent with the reaction family template, and the matching template. Note that the returned reaction will have its kinetics and degeneracy set appropriately. In order to reverse the reactions that are given in the reverse of the direction the family is defined, we need to compute the thermodynamics of the reactants and products. For this reason you must also pass the `thermoDatabase` to use to generate the thermo data. """ def generateThermoData(species, thermoDatabase): thermoData = [thermoDatabase.getThermoData(species)] thermoData.sort(key=lambda x: x.getEnthalpy(298)) return thermoData[0] def matchSpeciesToMolecules(species, molecules): if len(species) == len(molecules) == 1: return species[0].isIsomorphic(molecules[0]) elif len(species) == len(molecules) == 2: if species[0].isIsomorphic( molecules[0]) and species[1].isIsomorphic( molecules[1]): return True elif species[0].isIsomorphic( molecules[1]) and species[1].isIsomorphic( molecules[0]): return True return False reaction = None template = None # Get the indicated reaction family try: groups = self.families[family].groups except KeyError: raise ValueError( 'Invalid value "{0}" for family parameter.'.format(family)) if all([(isinstance(reactant, Group) or isinstance(reactant, LogicNode)) for reactant in entry.item.reactants]): # The entry is a rate rule, containing functional groups only # By convention, these are always given in the forward direction and # have kinetics defined on a per-site basis reaction = Reaction( reactants=entry.item.reactants[:], products=[], kinetics=entry.data, degeneracy=1, ) template = [ groups.entries[label] for label in entry.label.split(';') ] elif (all([ isinstance(reactant, Molecule) for reactant in entry.item.reactants ]) and all( [isinstance(product, Molecule) for product in entry.item.products])): # The entry is a real reaction, containing molecules # These could be defined for either the forward or reverse direction # and could have a reaction-path degeneracy reaction = Reaction(reactants=[], products=[]) for molecule in entry.item.reactants: reactant = Species(molecule=[molecule]) reactant.generateResonanceIsomers() reactant.thermo = generateThermoData(reactant, thermoDatabase) reaction.reactants.append(reactant) for molecule in entry.item.products: product = Species(molecule=[molecule]) product.generateResonanceIsomers() product.thermo = generateThermoData(product, thermoDatabase) reaction.products.append(product) # Generate all possible reactions involving the reactant species generatedReactions = self.generateReactionsFromFamilies( [reactant.molecule for reactant in reaction.reactants], [], only_families=[family]) # Remove from that set any reactions that don't produce the desired reactants and products forward = [] reverse = [] for rxn in generatedReactions: if matchSpeciesToMolecules( reaction.reactants, rxn.reactants) and matchSpeciesToMolecules( reaction.products, rxn.products): forward.append(rxn) if matchSpeciesToMolecules( reaction.reactants, rxn.products) and matchSpeciesToMolecules( reaction.products, rxn.reactants): reverse.append(rxn) # We should now know whether the reaction is given in the forward or # reverse direction if len(forward) == 1 and len(reverse) == 0: # The reaction is in the forward direction, so use as-is reaction = forward[0] template = reaction.template # Don't forget to overwrite the estimated kinetics from the database with the kinetics for this entry reaction.kinetics = entry.data elif len(reverse) == 1 and len(forward) == 0: # The reaction is in the reverse direction # First fit Arrhenius kinetics in that direction Tdata = 1000.0 / numpy.arange(0.5, 3.301, 0.1, numpy.float64) kdata = numpy.zeros_like(Tdata) for i in range(Tdata.shape[0]): kdata[i] = entry.data.getRateCoefficient( Tdata[i]) / reaction.getEquilibriumConstant(Tdata[i]) kunits = 'm^3/(mol*s)' if len( reverse[0].reactants) == 2 else 's^-1' kinetics = Arrhenius().fitToData(Tdata, kdata, kunits, T0=1.0) kinetics.Tmin = entry.data.Tmin kinetics.Tmax = entry.data.Tmax kinetics.Pmin = entry.data.Pmin kinetics.Pmax = entry.data.Pmax # Now flip the direction reaction = reverse[0] reaction.kinetics = kinetics template = reaction.template elif len(reverse) > 0 and len(forward) > 0: print 'FAIL: Multiple reactions found for {0!r}.'.format( entry.label) elif len(reverse) == 0 and len(forward) == 0: print 'FAIL: No reactions found for "%s".' % (entry.label) else: print 'FAIL: Unable to estimate kinetics for {0!r}.'.format( entry.label) assert reaction is not None assert template is not None return reaction, template
break return rmg_mol else: return Molecule().fromSMILES(mol_string) rxnString = os.getcwd().split('/')[-2] r1 = fixLonePairMolecule(rxnString.split('_')[0].split('+')[0]) r2 = fixLonePairMolecule(rxnString.split('_')[0].split('+')[1]) p1 = fixLonePairMolecule(rxnString.split('_')[1]) #.split('+')[0]) #p2 = fixLonePairMolecule(rxnString.split('_')[1].split('+')[1]) rSpecies1 = Species(molecule=[r1]) rSpecies2 = Species(molecule=[r2]) pSpecies1 = Species(molecule=[p1]) #pSpecies2 = Species(molecule=[p2]) rSpecies1.generateResonanceIsomers() rSpecies2.generateResonanceIsomers() pSpecies1.generateResonanceIsomers() #pSpecies2.generateResonanceIsomers() testReaction = Reaction(reactants=[rSpecies1, rSpecies2], products=[pSpecies1], reversible=True) reactionList = [] for moleculeA in rSpecies1.molecule: for moleculeB in rSpecies2.molecule: tempList = database.kinetics.generateReactionsFromFamilies( [moleculeA, moleculeB], [], only_families=['Silylene_Insertion']) for rxn0 in tempList: reactionList.append(rxn0)
def getForwardReactionForFamilyEntry(self, entry, family, thermoDatabase): """ For a given `entry` for a reaction of the given reaction `family` (the string label of the family), return the reaction with kinetics and degeneracy for the "forward" direction as defined by the reaction family. For families that are their own reverse, the direction the kinetics is given in will be preserved. If the entry contains functional groups for the reactants, assume that it is given in the forward direction and do nothing. Returns the reaction in the direction consistent with the reaction family template, and the matching template. Note that the returned reaction will have its kinetics and degeneracy set appropriately. In order to reverse the reactions that are given in the reverse of the direction the family is defined, we need to compute the thermodynamics of the reactants and products. For this reason you must also pass the `thermoDatabase` to use to generate the thermo data. """ def generateThermoData(species, thermoDatabase): thermoData = [thermoDatabase.getThermoData(species)] thermoData.sort(key=lambda x: x.getEnthalpy(298)) return thermoData[0] def matchSpeciesToMolecules(species, molecules): if len(species) == len(molecules) == 1: return species[0].isIsomorphic(molecules[0]) elif len(species) == len(molecules) == 2: if species[0].isIsomorphic(molecules[0]) and species[1].isIsomorphic(molecules[1]): return True elif species[0].isIsomorphic(molecules[1]) and species[1].isIsomorphic(molecules[0]): return True return False reaction = None; template = None # Get the indicated reaction family try: groups = self.families[family].groups except KeyError: raise ValueError('Invalid value "{0}" for family parameter.'.format(family)) if all([(isinstance(reactant, Group) or isinstance(reactant, LogicNode)) for reactant in entry.item.reactants]): # The entry is a rate rule, containing functional groups only # By convention, these are always given in the forward direction and # have kinetics defined on a per-site basis reaction = Reaction( reactants = entry.item.reactants[:], products = [], kinetics = entry.data, degeneracy = 1, ) template = [groups.entries[label] for label in entry.label.split(';')] elif (all([isinstance(reactant, Molecule) for reactant in entry.item.reactants]) and all([isinstance(product, Molecule) for product in entry.item.products])): # The entry is a real reaction, containing molecules # These could be defined for either the forward or reverse direction # and could have a reaction-path degeneracy reaction = Reaction(reactants=[], products=[]) for molecule in entry.item.reactants: reactant = Species(molecule=[molecule]) reactant.generateResonanceIsomers() reactant.thermo = generateThermoData(reactant, thermoDatabase) reaction.reactants.append(reactant) for molecule in entry.item.products: product = Species(molecule=[molecule]) product.generateResonanceIsomers() product.thermo = generateThermoData(product, thermoDatabase) reaction.products.append(product) # Generate all possible reactions involving the reactant species generatedReactions = self.generateReactionsFromFamilies([reactant.molecule for reactant in reaction.reactants], [], only_families=[family]) # Remove from that set any reactions that don't produce the desired reactants and products forward = []; reverse = [] for rxn in generatedReactions: if matchSpeciesToMolecules(reaction.reactants, rxn.reactants) and matchSpeciesToMolecules(reaction.products, rxn.products): forward.append(rxn) if matchSpeciesToMolecules(reaction.reactants, rxn.products) and matchSpeciesToMolecules(reaction.products, rxn.reactants): reverse.append(rxn) # We should now know whether the reaction is given in the forward or # reverse direction if len(forward) == 1 and len(reverse) == 0: # The reaction is in the forward direction, so use as-is reaction = forward[0] template = reaction.template # Don't forget to overwrite the estimated kinetics from the database with the kinetics for this entry reaction.kinetics = entry.data elif len(reverse) == 1 and len(forward) == 0: # The reaction is in the reverse direction # First fit Arrhenius kinetics in that direction Tdata = 1000.0 / numpy.arange(0.5, 3.301, 0.1, numpy.float64) kdata = numpy.zeros_like(Tdata) for i in range(Tdata.shape[0]): kdata[i] = entry.data.getRateCoefficient(Tdata[i]) / reaction.getEquilibriumConstant(Tdata[i]) kunits = 'm^3/(mol*s)' if len(reverse[0].reactants) == 2 else 's^-1' kinetics = Arrhenius().fitToData(Tdata, kdata, kunits, T0=1.0) kinetics.Tmin = entry.data.Tmin kinetics.Tmax = entry.data.Tmax kinetics.Pmin = entry.data.Pmin kinetics.Pmax = entry.data.Pmax # Now flip the direction reaction = reverse[0] reaction.kinetics = kinetics template = reaction.template elif len(reverse) > 0 and len(forward) > 0: print 'FAIL: Multiple reactions found for {0!r}.'.format(entry.label) elif len(reverse) == 0 and len(forward) == 0: print 'FAIL: No reactions found for "%s".' % (entry.label) else: print 'FAIL: Unable to estimate kinetics for {0!r}.'.format(entry.label) assert reaction is not None assert template is not None return reaction, template
rmgDatabase = RMGDatabase() rmgDatabase.load(os.path.abspath(os.path.join(os.getenv('RMGpy'), '..', 'RMG-database', 'input')), kineticsFamilies='default') print 'Finished loading RMG Database ...' reactionTuple = rxnList[i-1] rxnFamily, reactionLine = reactionTuple rxnFormula, A, n, Ea = reactionLine.split() reactants, products = rxnFormula.split('=') if rxnFamily in ['H_Abstraction', 'Disproportionation']: reactant1, reactant2 = [moleculeDict[j] for j in reactants.split('+')] product1, product2 = [moleculeDict[j] for j in products.split('+')] rSpecies1 = Species(molecule=[reactant1]) rSpecies2 = Species(molecule=[reactant2]) pSpecies1 = Species(molecule=[product1]) pSpecies2 = Species(molecule=[product2]) rSpecies1.generateResonanceIsomers() rSpecies2.generateResonanceIsomers() pSpecies1.generateResonanceIsomers() pSpecies2.generateResonanceIsomers() testReaction = Reaction(reactants=[rSpecies1, rSpecies2], products=[pSpecies1, pSpecies2], reversible=True) reactionList = [] for moleculeA in rSpecies1.molecule: for moleculeB in rSpecies2.molecule: tempList = rmgDatabase.kinetics.generateReactionsFromFamilies([moleculeA, moleculeB], [], only_families=[rxnFamily]) for rxn0 in tempList: reactionList.append(rxn0) elif rxnFamily in ['intra_H_migration']: reactant = moleculeDict[reactants] product = moleculeDict[products] rSpecies = Species(molecule=[reactant]) pSpecies = Species(molecule=[product])
def filterReactions(reactants, products, reactionList): """ Remove any reactions from the given `reactionList` whose reactants do not involve all the given `reactants` or whose products do not involve all the given `products`. This method checks both forward and reverse directions, and only filters out reactions that don't match either. reactants and products can be either molecule or species objects """ # Convert from molecules to species and generate resonance isomers. reactant_species = [] for mol in reactants: if isinstance(mol,Species): s = mol else: s = Species(molecule=[mol]) s.generateResonanceIsomers() reactant_species.append(s) reactants = reactant_species product_species = [] for mol in products: if isinstance(mol,Species): s = mol else: s = Species(molecule=[mol]) s.generateResonanceIsomers() product_species.append(s) products = product_species reactions = reactionList[:] for reaction in reactionList: # Forward direction reactants0 = [r for r in reaction.reactants] for reactant in reactants: for reactant0 in reactants0: if reactant.isIsomorphic(reactant0): reactants0.remove(reactant0) break products0 = [p for p in reaction.products] for product in products: for product0 in products0: if product.isIsomorphic(product0): products0.remove(product0) break forward = not (len(reactants0) != 0 or len(products0) != 0) # Reverse direction reactants0 = [r for r in reaction.products] for reactant in reactants: for reactant0 in reactants0: if reactant.isIsomorphic(reactant0): reactants0.remove(reactant0) break products0 = [p for p in reaction.reactants] for product in products: for product0 in products0: if product.isIsomorphic(product0): products0.remove(product0) break reverse = not (len(reactants0) != 0 or len(products0) != 0) if not forward and not reverse: reactions.remove(reaction) return reactions
def testResonanceIsomersGenerated(self): "Test that 1-penten-3-yl makes 2-penten-1-yl resonance isomer" spec = Species().fromSMILES('C=C[CH]CC') spec.generateResonanceIsomers() self.assertEquals(len(spec.molecule), 2) self.assertEquals(spec.molecule[1].toSMILES(), "[CH2]C=CCC")
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)