def fitInterpolationModels(self): configurations = [] configurations.extend(self.network.isomers) configurations.extend(self.network.reactants) configurations.extend(self.network.products) self.network.netReactions = [] Nreac = self.network.Nisom + self.network.Nreac Nprod = Nreac + self.network.Nprod Tmin = self.Tmin.value_si Tmax = self.Tmax.value_si Tdata = self.Tlist.value_si Pmin = self.Pmin.value_si Pmax = self.Pmax.value_si Pdata = self.Plist.value_si for prod in range(Nprod): for reac in range(Nreac): if reac == prod: continue reaction = Reaction( reactants = configurations[reac].species, products = configurations[prod].species, ) kdata = self.K[:,:,prod,reac].copy() order = len(reaction.reactants) kdata *= 1e6 ** (order-1) kunits = {1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)'}[order] reaction.kinetics = self.fitInterpolationModel(Tdata, Pdata, kdata, kunits) self.network.netReactions.append(reaction)
def fit_interpolation_models(self): """Fit all pressure dependent rates with interpolation models""" configurations = [] configurations.extend(self.network.isomers) configurations.extend(self.network.reactants) configurations.extend(self.network.products) self.network.net_reactions = [] n_reac = self.network.n_isom + self.network.n_reac n_prod = n_reac + self.network.n_prod Tdata, Pdata = self.Tlist.value_si, self.Plist.value_si for prod in range(n_prod): for reac in range(n_reac): if reac == prod: continue reaction = Reaction(reactants=configurations[reac].species, products=configurations[prod].species) kdata = self.K[:, :, prod, reac].copy() order = len(reaction.reactants) kdata *= 1e6 ** (order - 1) k_units = {1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)'}[order] logging.debug('Fitting master eqn data to kinetics for reaction {}.'.format(reaction)) reaction.kinetics = self.fit_interpolation_model(Tdata, Pdata, kdata, k_units) self.network.net_reactions.append(reaction)
def fitInterpolationModels(self): configurations = [] configurations.extend(self.network.isomers) configurations.extend(self.network.reactants) configurations.extend(self.network.products) self.network.netReactions = [] Nreac = self.network.Nisom + self.network.Nreac Nprod = Nreac + self.network.Nprod Tmin = self.Tmin.value_si Tmax = self.Tmax.value_si Tdata = self.Tlist.value_si Pmin = self.Pmin.value_si Pmax = self.Pmax.value_si Pdata = self.Plist.value_si for prod in range(Nprod): for reac in range(Nreac): if reac == prod: continue reaction = Reaction( reactants = configurations[reac].species, products = configurations[prod].species, ) kdata = self.K[:,:,prod,reac].copy() order = len(reaction.reactants) kdata *= 1e6 ** (order-1) kunits = {1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)'}[order] reaction.kinetics = self.fitInterpolationModel(Tdata, Pdata, kdata, kunits) self.network.netReactions.append(reaction)
def compute(self): """ Compute the pressure-dependent rate coefficients :math:`k(T,P)` for the loaded MEASURE calculation. """ # Only proceed if the input network is valid if self.network is None or self.network.errorString != '': raise PDepError('Attempted to run MEASURE calculation with invalid input.') Nisom = len(self.network.isomers) Nreac = len(self.network.reactants) Nprod = len(self.network.products) network = self.network Tmin = self.Tmin.value_si Tmax = self.Tmax.value_si Tlist = self.Tlist.value_si Pmin = self.Pmin.value_si Pmax = self.Pmax.value_si Plist = self.Plist.value_si method = self.method model = self.model # Calculate the rate coefficients K = network.calculateRateCoefficients(Tlist, Plist, method, grainCount=self.grainCount, grainSize=self.grainSize.value_si) # Fit interpolation model from rmgpy.reaction import Reaction from rmgpy.measure.reaction import fitInterpolationModel if model[0] != '': logging.info('Fitting {0} interpolation models...'.format(model[0])) configurations = [] configurations.extend([[isom] for isom in network.isomers]) configurations.extend([reactants for reactants in network.reactants]) configurations.extend([products for products in network.products]) for i in range(Nisom+Nreac+Nprod): for j in range(Nisom+Nreac): if i != j: # Check that we have nonzero k(T,P) values if (numpy.any(K[:,:,i,j]) and not numpy.all(K[:,:,i,j])): raise NetworkError('Zero rate coefficient encountered while updating network {0}.'.format(network)) # Make a new net reaction netReaction = Reaction( reactants=configurations[j], products=configurations[i], kinetics=None, reversible=(i<Nisom+Nreac), ) network.netReactions.append(netReaction) # Set/update the net reaction kinetics using interpolation model netReaction.kinetics = fitInterpolationModel(netReaction, Tlist, Plist, K[:,:,i,j], model, Tmin, Tmax, Pmin, Pmax, errorCheck=True) logging.info('')
def compute(self): """ Compute the pressure-dependent rate coefficients :math:`k(T,P)` for the loaded MEASURE calculation. """ # Only proceed if the input network is valid if self.network is None or self.network.errorString != '': raise PDepError('Attempted to run MEASURE calculation with invalid input.') Nisom = len(self.network.isomers) Nreac = len(self.network.reactants) Nprod = len(self.network.products) network = self.network Tmin = self.Tmin.value Tmax = self.Tmax.value Tlist = self.Tlist.values Pmin = self.Pmin.value Pmax = self.Pmax.value Plist = self.Plist.values method = self.method model = self.model # Calculate the rate coefficients K = network.calculateRateCoefficients(Tlist, Plist, method, grainCount=self.grainCount, grainSize=self.grainSize.value) # Fit interpolation model from rmgpy.reaction import Reaction from rmgpy.measure.reaction import fitInterpolationModel if model[0] != '': logging.info('Fitting {0} interpolation models...'.format(model[0])) configurations = [] configurations.extend([[isom] for isom in network.isomers]) configurations.extend([reactants for reactants in network.reactants]) configurations.extend([products for products in network.products]) for i in range(Nisom+Nreac+Nprod): for j in range(Nisom+Nreac): if i != j: # Check that we have nonzero k(T,P) values if (numpy.any(K[:,:,i,j]) and not numpy.all(K[:,:,i,j])): raise NetworkError('Zero rate coefficient encountered while updating network {0}.'.format(network)) # Make a new net reaction netReaction = Reaction( reactants=configurations[j], products=configurations[i], kinetics=None, reversible=(i<Nisom+Nreac), ) network.netReactions.append(netReaction) # Set/update the net reaction kinetics using interpolation model netReaction.kinetics = fitInterpolationModel(netReaction, Tlist, Plist, K[:,:,i,j], model, Tmin, Tmax, Pmin, Pmax, errorCheck=True) logging.info('')
def getKineticsDepository(FullDatabase, family, depositoryLabel): """ Retrieve dictionaries of exact kinetics from NIST depository and approximated kinetics for those same reactions from RMG. Note: does NOT average up the database or create any rate rules from training data. If that is desired it must be done prior to entering this function. """ depository = None for tempDepository in family.depositories: if re.search(re.escape(depositoryLabel), tempDepository.label): depository=tempDepository break else: print 'Depository {} not found in {} family.'.format(depositoryLabel, family.label) return exactKinetics={} approxKinetics={} for key, entry in depository.entries.iteritems(): try: reaction=entry.item template=family.getReactionTemplate(reaction) exactKinetics[key]=entry.data approxKinetics[key]=family.rules.estimateKinetics(template)[0] except UndeterminableKineticsError: # See if the reaction was written in the reverse direction reaction = Reaction(reactants = copy.deepcopy(entry.item.products), products = copy.deepcopy(entry.item.reactants), kinetics = copy.deepcopy(entry.data) ) template=family.getReactionTemplate(reaction) # Getting thermo data erases the atomLabels, so do this after finding the template # But we need it for setting the reverse kinetics for spec in reaction.reactants + reaction.products: spec.getThermoData() reverseKinetics = reaction.generateReverseRateCoefficient() reaction.kinetics = reverseKinetics exactKinetics[key]=reaction.kinetics approxKinetics[key]=family.rules.estimateKinetics(template)[0] return exactKinetics, approxKinetics
def getKineticsDepository(FullDatabase, family, depositoryLabel): """ Retrieve dictionaries of exact kinetics from NIST depository and approximated kinetics for those same reactions from RMG. Note: does NOT average up the database or create any rate rules from training data. If that is desired it must be done prior to entering this function. """ depository = None for tempDepository in family.depositories: if re.search(re.escape(depositoryLabel), tempDepository.label): depository = tempDepository break else: print 'Depository {} not found in {} family.'.format( depositoryLabel, family.label) return exactKinetics = {} approxKinetics = {} for key, entry in depository.entries.iteritems(): try: reaction = entry.item template = family.getReactionTemplate(reaction) exactKinetics[key] = entry.data approxKinetics[key] = family.rules.estimateKinetics(template)[0] except UndeterminableKineticsError: # See if the reaction was written in the reverse direction reaction = Reaction(reactants=copy.deepcopy(entry.item.products), products=copy.deepcopy(entry.item.reactants), kinetics=copy.deepcopy(entry.data)) template = family.getReactionTemplate(reaction) # Getting thermo data erases the atomLabels, so do this after finding the template # But we need it for setting the reverse kinetics for spec in reaction.reactants + reaction.products: spec.getThermoData() reverseKinetics = reaction.generateReverseRateCoefficient() reaction.kinetics = reverseKinetics exactKinetics[key] = reaction.kinetics approxKinetics[key] = family.rules.estimateKinetics(template)[0] return exactKinetics, approxKinetics
def get_reaction_for_entry(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 reactant in entry.item.reactants: reactant.generate_resonance_structures() reactant.thermo = generate_thermo_data(reactant, database) reaction.reactants.append(reactant) for product in entry.item.products: product.generate_resonance_structures() product.thermo = generate_thermo_data(product, database) reaction.products.append(product) reaction.kinetics = entry.data reaction.degeneracy = entry.item.degeneracy return reaction
def kinetics(label, kinetics): reactants, products = label.split('_') reactants = reactants.split('+') products = products.split('+') reaction = Reaction(reactants=[], products=[], reversible=True) for in_list, out_list in [(reactants, reaction.reactants), (products, reaction.products)]: for i, smiles in enumerate(in_list): if smiles not in species_dict: species = Species().fromSMILES(smiles) species_dict[smiles] = species species.label = '{0}({1})'.format(species.toChemkin(), len(species_dict)) out_list.append(species_dict[smiles]) reaction.kinetics = kinetics print repr(reaction) display(reaction) addReactionToKineticsLibrary(reaction)
def get_rmg_reaction(self): """ Convert PrIMeReaction to an RMG reaction. """ if not all((self.reactants, self.products, self.kinetics)): raise ConversionError( 'Missing reactants, products, or kinetics for reaction {}.'. format(self.prime_id)) if not self._direction_matched: self.match_direction() reaction = Reaction() if self.rmg_reactants is None or self.rmg_products is None: reaction.reactants = [s.get_rmg_species() for s in self.reactants] reaction.products = [s.get_rmg_species() for s in self.products] else: reaction.reactants = self.rmg_reactants reaction.products = self.rmg_products reaction.kinetics = self.kinetics.expression return reaction
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.generate_resonance_structures() 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.generate_resonance_structures() product.thermo = generateThermoData(product, database) reaction.products.append(product) reaction.kinetics = entry.data reaction.degeneracy = entry.item.degeneracy return reaction
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 loadFAMEInput(path, moleculeDict=None): """ Load the contents of a FAME input file into the MEASURE object. FAME is an early version of MEASURE written in Fortran and used by RMG-Java. This script enables importing FAME input files into MEASURE so we can use the additional functionality that MEASURE provides. Note that it is mostly designed to load the FAME input files generated automatically by RMG-Java, and may not load hand-crafted FAME input files. If you specify a `moleculeDict`, then this script will use it to associate the species with their structures. """ def readMeaningfulLine(f): line = f.readline() while line != '': line = line.strip() if len(line) > 0 and line[0] != '#': return line else: line = f.readline() return '' moleculeDict = moleculeDict or {} logging.info('Loading file "{0}"...'.format(path)) f = open(path) job = PressureDependenceJob(network=None) # Read method method = readMeaningfulLine(f).lower() if method == 'modifiedstrongcollision': job.method = 'modified strong collision' elif method == 'reservoirstate': job.method = 'reservoir state' # Read temperatures Tcount, Tunits, Tmin, Tmax = readMeaningfulLine(f).split() job.Tmin = Quantity(float(Tmin), Tunits) job.Tmax = Quantity(float(Tmax), Tunits) job.Tcount = int(Tcount) Tlist = [] for i in range(int(Tcount)): Tlist.append(float(readMeaningfulLine(f))) job.Tlist = Quantity(Tlist, Tunits) # Read pressures Pcount, Punits, Pmin, Pmax = readMeaningfulLine(f).split() job.Pmin = Quantity(float(Pmin), Punits) job.Pmax = Quantity(float(Pmax), Punits) job.Pcount = int(Pcount) Plist = [] for i in range(int(Pcount)): Plist.append(float(readMeaningfulLine(f))) job.Plist = Quantity(Plist, Punits) # Read interpolation model model = readMeaningfulLine(f).split() if model[0].lower() == 'chebyshev': job.interpolationModel = ('chebyshev', int(model[1]), int(model[2])) elif model[0].lower() == 'pdeparrhenius': job.interpolationModel = ('pdeparrhenius',) # Read grain size or number of grains job.minimumGrainCount = 0 job.maximumGrainSize = None for i in range(2): data = readMeaningfulLine(f).split() if data[0].lower() == 'numgrains': job.minimumGrainCount = int(data[1]) elif data[0].lower() == 'grainsize': job.maximumGrainSize = (float(data[2]), data[1]) # A FAME file is almost certainly created during an RMG job, so use RMG mode job.rmgmode = True # Create the Network job.network = Network() # Read collision model data = readMeaningfulLine(f) assert data.lower() == 'singleexpdown' alpha0units, alpha0 = readMeaningfulLine(f).split() T0units, T0 = readMeaningfulLine(f).split() n = readMeaningfulLine(f) energyTransferModel = SingleExponentialDown( alpha0 = Quantity(float(alpha0), alpha0units), T0 = Quantity(float(T0), T0units), n = float(n), ) speciesDict = {} # Read bath gas parameters bathGas = Species(label='bath_gas', energyTransferModel=energyTransferModel) molWtunits, molWt = readMeaningfulLine(f).split() if molWtunits == 'u': molWtunits = 'amu' bathGas.molecularWeight = Quantity(float(molWt), molWtunits) sigmaLJunits, sigmaLJ = readMeaningfulLine(f).split() epsilonLJunits, epsilonLJ = readMeaningfulLine(f).split() assert epsilonLJunits == 'J' bathGas.transportData = TransportData( sigma = Quantity(float(sigmaLJ), sigmaLJunits), epsilon = Quantity(float(epsilonLJ) / constants.kB, 'K'), ) job.network.bathGas = {bathGas: 1.0} # Read species data Nspec = int(readMeaningfulLine(f)) for i in range(Nspec): species = Species() species.conformer = Conformer() species.energyTransferModel = energyTransferModel # Read species label species.label = readMeaningfulLine(f) speciesDict[species.label] = species if species.label in moleculeDict: species.molecule = [moleculeDict[species.label]] # Read species E0 E0units, E0 = readMeaningfulLine(f).split() species.conformer.E0 = Quantity(float(E0), E0units) species.conformer.E0.units = 'kJ/mol' # Read species thermo data H298units, H298 = readMeaningfulLine(f).split() S298units, S298 = readMeaningfulLine(f).split() Cpcount, Cpunits = readMeaningfulLine(f).split() Cpdata = [] for i in range(int(Cpcount)): Cpdata.append(float(readMeaningfulLine(f))) if S298units == 'J/mol*K': S298units = 'J/(mol*K)' if Cpunits == 'J/mol*K': Cpunits = 'J/(mol*K)' species.thermo = ThermoData( H298 = Quantity(float(H298), H298units), S298 = Quantity(float(S298), S298units), Tdata = Quantity([300,400,500,600,800,1000,1500], "K"), Cpdata = Quantity(Cpdata, Cpunits), Cp0 = (Cpdata[0], Cpunits), CpInf = (Cpdata[-1], Cpunits), ) # Read species collision parameters molWtunits, molWt = readMeaningfulLine(f).split() if molWtunits == 'u': molWtunits = 'amu' species.molecularWeight = Quantity(float(molWt), molWtunits) sigmaLJunits, sigmaLJ = readMeaningfulLine(f).split() epsilonLJunits, epsilonLJ = readMeaningfulLine(f).split() assert epsilonLJunits == 'J' species.transportData = TransportData( sigma = Quantity(float(sigmaLJ), sigmaLJunits), epsilon = Quantity(float(epsilonLJ) / constants.kB, 'K'), ) # Read species vibrational frequencies freqCount, freqUnits = readMeaningfulLine(f).split() frequencies = [] for j in range(int(freqCount)): frequencies.append(float(readMeaningfulLine(f))) species.conformer.modes.append(HarmonicOscillator( frequencies = Quantity(frequencies, freqUnits), )) # Read species external rotors rotCount, rotUnits = readMeaningfulLine(f).split() if int(rotCount) > 0: raise NotImplementedError('Cannot handle external rotational modes in FAME input.') # Read species internal rotors freqCount, freqUnits = readMeaningfulLine(f).split() frequencies = [] for j in range(int(freqCount)): frequencies.append(float(readMeaningfulLine(f))) barrCount, barrUnits = readMeaningfulLine(f).split() barriers = [] for j in range(int(barrCount)): barriers.append(float(readMeaningfulLine(f))) if barrUnits == 'cm^-1': barrUnits = 'J/mol' barriers = [barr * constants.h * constants.c * constants.Na * 100. for barr in barriers] elif barrUnits in ['Hz', 's^-1']: barrUnits = 'J/mol' barriers = [barr * constants.h * constants.Na for barr in barriers] elif barrUnits != 'J/mol': raise Exception('Unexpected units "{0}" for hindered rotor barrier height.'.format(barrUnits)) inertia = [V0 / 2.0 / (nu * constants.c * 100.)**2 / constants.Na for nu, V0 in zip(frequencies, barriers)] for I, V0 in zip(inertia, barriers): species.conformer.modes.append(HinderedRotor( inertia = Quantity(I,"kg*m^2"), barrier = Quantity(V0,barrUnits), symmetry = 1, semiclassical = False, )) # Read overall symmetry number species.conformer.spinMultiplicity = int(readMeaningfulLine(f)) # Read isomer, reactant channel, and product channel data Nisom = int(readMeaningfulLine(f)) Nreac = int(readMeaningfulLine(f)) Nprod = int(readMeaningfulLine(f)) for i in range(Nisom): data = readMeaningfulLine(f).split() assert data[0] == '1' job.network.isomers.append(speciesDict[data[1]]) for i in range(Nreac): data = readMeaningfulLine(f).split() assert data[0] == '2' job.network.reactants.append([speciesDict[data[1]], speciesDict[data[2]]]) for i in range(Nprod): data = readMeaningfulLine(f).split() if data[0] == '1': job.network.products.append([speciesDict[data[1]]]) elif data[0] == '2': job.network.products.append([speciesDict[data[1]], speciesDict[data[2]]]) # Read path reactions Nrxn = int(readMeaningfulLine(f)) for i in range(Nrxn): # Read and ignore reaction equation equation = readMeaningfulLine(f) reaction = Reaction(transitionState=TransitionState(), reversible=True) job.network.pathReactions.append(reaction) reaction.transitionState.conformer = Conformer() # Read reactant and product indices data = readMeaningfulLine(f).split() reac = int(data[0]) - 1 prod = int(data[1]) - 1 if reac < Nisom: reaction.reactants = [job.network.isomers[reac]] elif reac < Nisom+Nreac: reaction.reactants = job.network.reactants[reac-Nisom] else: reaction.reactants = job.network.products[reac-Nisom-Nreac] if prod < Nisom: reaction.products = [job.network.isomers[prod]] elif prod < Nisom+Nreac: reaction.products = job.network.reactants[prod-Nisom] else: reaction.products = job.network.products[prod-Nisom-Nreac] # Read reaction E0 E0units, E0 = readMeaningfulLine(f).split() reaction.transitionState.conformer.E0 = Quantity(float(E0), E0units) reaction.transitionState.conformer.E0.units = 'kJ/mol' # Read high-pressure limit kinetics data = readMeaningfulLine(f) assert data.lower() == 'arrhenius' Aunits, A = readMeaningfulLine(f).split() if '/' in Aunits: index = Aunits.find('/') Aunits = '{0}/({1})'.format(Aunits[0:index], Aunits[index+1:]) Eaunits, Ea = readMeaningfulLine(f).split() n = readMeaningfulLine(f) reaction.kinetics = Arrhenius( A = Quantity(float(A), Aunits), Ea = Quantity(float(Ea), Eaunits), n = Quantity(float(n)), ) reaction.kinetics.Ea.units = 'kJ/mol' f.close() job.network.isomers = [Configuration(isomer) for isomer in job.network.isomers] job.network.reactants = [Configuration(*reactants) for reactants in job.network.reactants] job.network.products = [Configuration(*products) for products in job.network.products] return job
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
def loadFAMEInput(path, moleculeDict=None): """ Load the contents of a FAME input file into the MEASURE object. FAME is an early version of MEASURE written in Fortran and used by RMG-Java. This script enables importing FAME input files into MEASURE so we can use the additional functionality that MEASURE provides. Note that it is mostly designed to load the FAME input files generated automatically by RMG-Java, and may not load hand-crafted FAME input files. If you specify a `moleculeDict`, then this script will use it to associate the species with their structures. """ def readMeaningfulLine(f): line = f.readline() while line != '': line = line.strip() if len(line) > 0 and line[0] != '#': return line else: line = f.readline() return '' moleculeDict = moleculeDict or {} logging.info('Loading file "{0}"...'.format(path)) f = open(path) job = PressureDependenceJob(network=None) # Read method method = readMeaningfulLine(f).lower() if method == 'modifiedstrongcollision': job.method = 'modified strong collision' elif method == 'reservoirstate': job.method = 'reservoir state' # Read temperatures Tcount, Tunits, Tmin, Tmax = readMeaningfulLine(f).split() job.Tmin = Quantity(float(Tmin), Tunits) job.Tmax = Quantity(float(Tmax), Tunits) job.Tcount = int(Tcount) Tlist = [] for i in range(int(Tcount)): Tlist.append(float(readMeaningfulLine(f))) job.Tlist = Quantity(Tlist, Tunits) # Read pressures Pcount, Punits, Pmin, Pmax = readMeaningfulLine(f).split() job.Pmin = Quantity(float(Pmin), Punits) job.Pmax = Quantity(float(Pmax), Punits) job.Pcount = int(Pcount) Plist = [] for i in range(int(Pcount)): Plist.append(float(readMeaningfulLine(f))) job.Plist = Quantity(Plist, Punits) # Read interpolation model model = readMeaningfulLine(f).split() if model[0].lower() == 'chebyshev': job.model = ['chebyshev', int(model[1]), int(model[2])] elif model[0].lower() == 'pdeparrhenius': job.model = ['pdeparrhenius'] # Read grain size or number of grains job.grainCount = 0 job.grainSize = Quantity(0.0, "J/mol") for i in range(2): data = readMeaningfulLine(f).split() if data[0].lower() == 'numgrains': job.grainCount = int(data[1]) elif data[0].lower() == 'grainsize': job.grainSize = Quantity(float(data[2]), data[1]) # Create the Network job.network = Network() # Read collision model data = readMeaningfulLine(f) assert data.lower() == 'singleexpdown' alpha0units, alpha0 = readMeaningfulLine(f).split() T0units, T0 = readMeaningfulLine(f).split() n = readMeaningfulLine(f) energyTransferModel = SingleExponentialDown( alpha0 = Quantity(float(alpha0), alpha0units), T0 = Quantity(float(T0), T0units), n = float(n), ) speciesDict = {} # Read bath gas parameters bathGas = Species(label='bath_gas', energyTransferModel=energyTransferModel) molWtunits, molWt = readMeaningfulLine(f).split() if molWtunits == 'u': molWtunits = 'amu' bathGas.molecularWeight = Quantity(float(molWt), molWtunits) sigmaLJunits, sigmaLJ = readMeaningfulLine(f).split() epsilonLJunits, epsilonLJ = readMeaningfulLine(f).split() assert epsilonLJunits == 'J' bathGas.lennardJones = LennardJones( sigma = Quantity(float(sigmaLJ), sigmaLJunits), epsilon = Quantity(float(epsilonLJ) / constants.kB, 'K'), ) job.network.bathGas = {bathGas: 1.0} # Read species data Nspec = int(readMeaningfulLine(f)) for i in range(Nspec): species = Species() species.conformer = Conformer() # Read species label species.label = readMeaningfulLine(f) speciesDict[species.label] = species if species.label in moleculeDict: species.molecule = [moleculeDict[species.label]] # Read species E0 E0units, E0 = readMeaningfulLine(f).split() species.conformer.E0 = Quantity(float(E0), E0units) species.conformer.E0.units = 'kJ/mol' # Read species thermo data H298units, H298 = readMeaningfulLine(f).split() S298units, S298 = readMeaningfulLine(f).split() Cpcount, Cpunits = readMeaningfulLine(f).split() Cpdata = [] for i in range(int(Cpcount)): Cpdata.append(float(readMeaningfulLine(f))) if S298units == 'J/mol*K': S298units = 'J/(mol*K)' if Cpunits == 'J/mol*K': Cpunits = 'J/(mol*K)' species.thermo = ThermoData( H298 = Quantity(float(H298), H298units), S298 = Quantity(float(S298), S298units), Tdata = Quantity([300,400,500,600,800,1000,1500], "K"), Cpdata = Quantity(Cpdata, Cpunits), ) # Read species collision parameters molWtunits, molWt = readMeaningfulLine(f).split() if molWtunits == 'u': molWtunits = 'amu' species.molecularWeight = Quantity(float(molWt), molWtunits) sigmaLJunits, sigmaLJ = readMeaningfulLine(f).split() epsilonLJunits, epsilonLJ = readMeaningfulLine(f).split() assert epsilonLJunits == 'J' species.lennardJones = LennardJones( sigma = Quantity(float(sigmaLJ), sigmaLJunits), epsilon = Quantity(float(epsilonLJ) / constants.kB, 'K'), ) # Read species vibrational frequencies freqCount, freqUnits = readMeaningfulLine(f).split() frequencies = [] for j in range(int(freqCount)): frequencies.append(float(readMeaningfulLine(f))) species.conformer.modes.append(HarmonicOscillator( frequencies = Quantity(frequencies, freqUnits), )) # Read species external rotors rotCount, rotUnits = readMeaningfulLine(f).split() if int(rotCount) > 0: raise NotImplementedError('Cannot handle external rotational modes in FAME input.') # Read species internal rotors freqCount, freqUnits = readMeaningfulLine(f).split() frequencies = [] for j in range(int(freqCount)): frequencies.append(float(readMeaningfulLine(f))) barrCount, barrUnits = readMeaningfulLine(f).split() barriers = [] for j in range(int(barrCount)): barriers.append(float(readMeaningfulLine(f))) if barrUnits == 'cm^-1': barrUnits = 'J/mol' barriers = [barr * constants.h * constants.c * constants.Na * 100. for barr in barriers] elif barrUnits in ['Hz', 's^-1']: barrUnits = 'J/mol' barriers = [barr * constants.h * constants.Na for barr in barriers] elif barrUnits != 'J/mol': raise Exception('Unexpected units "{0}" for hindered rotor barrier height.'.format(barrUnits)) inertia = [V0 / 2.0 / (nu * constants.c * 100.)**2 / constants.Na for nu, V0 in zip(frequencies, barriers)] for I, V0 in zip(inertia, barriers): species.conformer.modes.append(HinderedRotor( inertia = Quantity(I,"kg*m^2"), barrier = Quantity(V0,barrUnits), symmetry = 1, )) # Read overall symmetry number species.conformer.spinMultiplicity = int(readMeaningfulLine(f)) # Read isomer, reactant channel, and product channel data Nisom = int(readMeaningfulLine(f)) Nreac = int(readMeaningfulLine(f)) Nprod = int(readMeaningfulLine(f)) for i in range(Nisom): data = readMeaningfulLine(f).split() assert data[0] == '1' job.network.isomers.append(speciesDict[data[1]]) for i in range(Nreac): data = readMeaningfulLine(f).split() assert data[0] == '2' job.network.reactants.append([speciesDict[data[1]], speciesDict[data[2]]]) for i in range(Nprod): data = readMeaningfulLine(f).split() if data[0] == '1': job.network.products.append([speciesDict[data[1]]]) elif data[0] == '2': job.network.products.append([speciesDict[data[1]], speciesDict[data[2]]]) # Read path reactions Nrxn = int(readMeaningfulLine(f)) for i in range(Nrxn): # Read and ignore reaction equation equation = readMeaningfulLine(f) reaction = Reaction(transitionState=TransitionState(), reversible=True) job.network.pathReactions.append(reaction) reaction.transitionState.conformer = Conformer() # Read reactant and product indices data = readMeaningfulLine(f).split() reac = int(data[0]) - 1 prod = int(data[1]) - 1 if reac < Nisom: reaction.reactants = [job.network.isomers[reac]] elif reac < Nisom+Nreac: reaction.reactants = job.network.reactants[reac-Nisom] else: reaction.reactants = job.network.products[reac-Nisom-Nreac] if prod < Nisom: reaction.products = [job.network.isomers[prod]] elif prod < Nisom+Nreac: reaction.products = job.network.reactants[prod-Nisom] else: reaction.products = job.network.products[prod-Nisom-Nreac] # Read reaction E0 E0units, E0 = readMeaningfulLine(f).split() reaction.transitionState.conformer.E0 = Quantity(float(E0), E0units) reaction.transitionState.conformer.E0.units = 'kJ/mol' # Read high-pressure limit kinetics data = readMeaningfulLine(f) assert data.lower() == 'arrhenius' Aunits, A = readMeaningfulLine(f).split() if '/' in Aunits: index = Aunits.find('/') Aunits = '{0}/({1})'.format(Aunits[0:index], Aunits[index+1:]) Eaunits, Ea = readMeaningfulLine(f).split() n = readMeaningfulLine(f) reaction.kinetics = Arrhenius( A = Quantity(float(A), Aunits), Ea = Quantity(float(Ea), Eaunits), n = Quantity(float(n)), ) reaction.kinetics.Ea.units = 'kJ/mol' f.close() job.network.isomers = [Configuration(isomer) for isomer in job.network.isomers] job.network.reactants = [Configuration(*reactants) for reactants in job.network.reactants] job.network.products = [Configuration(*products) for products in job.network.products] return job
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
def get_forward_reaction_for_family_entry(self, entry, family, thermo_database): """ 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 `thermo_database` to use to generate the thermo data. """ 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=[], specific_collider=entry.item.specific_collider, 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.generate_resonance_structures() reactant.thermo = thermo_database.get_thermo_data(reactant) reaction.reactants.append(reactant) for molecule in entry.item.products: product = Species(molecule=[molecule]) product.generate_resonance_structures() product.thermo = thermo_database.get_thermo_data(product) reaction.products.append(product) # Generate all possible reactions involving the reactant species generated_reactions = self.generate_reactions_from_families( [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 generated_reactions: if (same_species_lists(reaction.reactants, rxn.reactants) and same_species_lists(reaction.products, rxn.products)): forward.append(rxn) if (same_species_lists(reaction.reactants, rxn.products) and same_species_lists(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 T_data = 1000.0 / np.arange(0.5, 3.301, 0.1, np.float64) k_data = np.zeros_like(T_data) for i in range(T_data.shape[0]): k_data[i] = entry.data.get_rate_coefficient( T_data[i]) / reaction.get_equilibrium_constant( T_data[i]) try: k_units = ('s^-1', 'm^3/(mol*s)', 'm^6/(mol^2*s)')[len(reverse[0].reactants) - 1] except IndexError: raise NotImplementedError( 'Cannot reverse reactions with {} products'.format( len(reverse[0].reactants))) kinetics = Arrhenius().fit_to_data(T_data, k_data, k_units, 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