def testGenerateReverseRateCoefficientPDepArrhenius(self): """ Test the Reaction.generateReverseRateCoefficient() method works for the PDepArrhenius format. """ from rmgpy.kinetics import PDepArrhenius arrhenius0 = Arrhenius( A = (1.0e6,"s^-1"), n = 1.0, Ea = (10.0,"kJ/mol"), T0 = (300.0,"K"), Tmin = (300.0,"K"), Tmax = (2000.0,"K"), comment = """This data is completely made up""", ) arrhenius1 = Arrhenius( A = (1.0e12,"s^-1"), n = 1.0, Ea = (20.0,"kJ/mol"), T0 = (300.0,"K"), Tmin = (300.0,"K"), Tmax = (2000.0,"K"), comment = """This data is completely made up""", ) pressures = numpy.array([0.1, 10.0]) arrhenius = [arrhenius0, arrhenius1] Tmin = 300.0 Tmax = 2000.0 Pmin = 0.1 Pmax = 10.0 comment = """This data is completely made up""" original_kinetics = PDepArrhenius( pressures = (pressures,"bar"), arrhenius = arrhenius, Tmin = (Tmin,"K"), Tmax = (Tmax,"K"), Pmin = (Pmin,"bar"), Pmax = (Pmax,"bar"), comment = comment, ) self.reaction2.kinetics = original_kinetics reverseKinetics = self.reaction2.generateReverseRateCoefficient() self.reaction2.kinetics = reverseKinetics # reverse reactants, products to ensure Keq is correctly computed self.reaction2.reactants, self.reaction2.products = self.reaction2.products, self.reaction2.reactants reversereverseKinetics = self.reaction2.generateReverseRateCoefficient() # check that reverting the reverse yields the original Tlist = numpy.arange(Tmin, Tmax, 200.0, numpy.float64) P = 1e5 for T in Tlist: korig = original_kinetics.getRateCoefficient(T, P) krevrev = reversereverseKinetics.getRateCoefficient(T, P) self.assertAlmostEqual(korig / krevrev, 1.0, 0)
def extractKinetics(reactionline): """ Takes a reaction line from RMG and creates Arrhenius object from the kinetic data, as well as extracts names of reactants, products and comments. Units from RMG-Java are in cm3, mol, s. Reference Temperature T0 = 1 K. """ lines = reactionline.split("\t") reaction_string = lines[0] reactants, products = reaction_string.split(" --> ") reactants = reactants.split(" + ") products = products.split(" + ") if len(reactants) == 1: Aunits = "s^-1" elif len(reactants) == 2: Aunits = "cm**3/mol/s" else: # 3 reactants? Aunits = "cm**6/(mol^2*s)" kinetics = Arrhenius( A=(float(lines[1]), Aunits), n=float(lines[2]), Ea=(float(lines[3]), "kcal/mol"), T0=(1, "K"), ) comments = "\t".join(lines[4:]) kinetics.comment = "Estimated by RMG-Java:\n" + comments entry = Entry(long_desc=comments) return reactants, products, kinetics, entry
def testGenerateReverseRateCoefficientArrhenius(self): """ Test the Reaction.generateReverseRateCoefficient() method works for the Arrhenius format. """ original_kinetics = Arrhenius( A = (2.65e12, 'cm^3/(mol*s)'), n = 0.0, Ea = (0.0, 'kJ/mol'), T0 = (1, 'K'), Tmin = (300, 'K'), Tmax = (2000, 'K'), ) self.reaction2.kinetics = original_kinetics reverseKinetics = self.reaction2.generateReverseRateCoefficient() self.reaction2.kinetics = reverseKinetics # reverse reactants, products to ensure Keq is correctly computed self.reaction2.reactants, self.reaction2.products = self.reaction2.products, self.reaction2.reactants reversereverseKinetics = self.reaction2.generateReverseRateCoefficient() # check that reverting the reverse yields the original Tlist = numpy.arange(original_kinetics.Tmin.value_si, original_kinetics.Tmax.value_si, 200.0, numpy.float64) P = 1e5 for T in Tlist: korig = original_kinetics.getRateCoefficient(T, P) krevrev = reversereverseKinetics.getRateCoefficient(T, P) self.assertAlmostEqual(korig / krevrev, 1.0, 0)
def extractKinetics(reactionline): """ Takes a reaction line from RMG and creates Arrhenius object from the kinetic data, as well as extracts names of reactants, products and comments. Units from RMG-Java are in cm3, mol, s. Reference Temperature T0 = 1 K. """ lines = reactionline.split("\t") reaction_string = lines[0] reactants, products = reaction_string.split(" --> ") reactants = reactants.split(" + ") products = products.split(" + ") if len(reactants) == 1: Aunits = "s^-1" elif len(reactants) == 2: Aunits = "cm**3/mol/s" else: # 3 reactants? Aunits = "cm**6/(mol^2*s)" kinetics = Arrhenius( A = (float(lines[1]), Aunits), n = float(lines[2]), Ea = (float(lines[3]),"kcal/mol"), T0 = (1,"K"), ) comments = "\t".join(lines[4:]) kinetics.comment = "Estimated by RMG-Java:\n"+comments entry = Entry(longDesc=comments) return reactants, products, kinetics, entry
def generateKineticsModel(reaction, tunneling='', plot=False): logging.info('Calculating rate coefficient for {0}...'.format(reaction)) if len(reaction.reactants) == 1: kunits = 's^-1' elif len(reaction.reactants) == 2: kunits = 'm^3/(mol*s)' elif len(reaction.reactants) == 3: kunits = 'm^6/(mol^2*s)' else: kunits = '' Tlist = 1000.0/numpy.arange(0.4, 3.35, 0.05) klist = reaction.calculateTSTRateCoefficients(Tlist, tunneling) arrhenius = Arrhenius().fitToData(Tlist, klist, kunits) klist2 = arrhenius.getRateCoefficients(Tlist) reaction.kinetics = arrhenius if plot: logging.info('Plotting kinetics model for {0}...'.format(reaction)) import pylab pylab.semilogy(1000.0 / Tlist, klist * reaction.degeneracy, 'ok') pylab.semilogy(1000.0 / Tlist, klist2 * reaction.degeneracy, '-k') pylab.xlabel('1000 / Temperature (1000/K)') pylab.ylabel('Rate coefficient (SI units)') pylab.show()
def testGenerateReverseRateCoefficientMultiArrhenius(self): """ Test the Reaction.generateReverseRateCoefficient() method works for the MultiArrhenius format. """ from rmgpy.kinetics import MultiArrhenius pressures = numpy.array([0.1, 10.0]) Tmin = 300.0 Tmax = 2000.0 Pmin = 0.1 Pmax = 10.0 comment = """This data is completely made up""" arrhenius = [ Arrhenius( A = (9.3e-14,"cm^3/(molecule*s)"), n = 0.0, Ea = (4740*constants.R*0.001,"kJ/mol"), T0 = (1,"K"), Tmin = (Tmin,"K"), Tmax = (Tmax,"K"), comment = comment, ), Arrhenius( A = (1.4e-9,"cm^3/(molecule*s)"), n = 0.0, Ea = (11200*constants.R*0.001,"kJ/mol"), T0 = (1,"K"), Tmin = (Tmin,"K"), Tmax = (Tmax,"K"), comment = comment, ), ] original_kinetics = MultiArrhenius( arrhenius = arrhenius, Tmin = (Tmin,"K"), Tmax = (Tmax,"K"), comment = comment, ) self.reaction2.kinetics = original_kinetics reverseKinetics = self.reaction2.generateReverseRateCoefficient() self.reaction2.kinetics = reverseKinetics # reverse reactants, products to ensure Keq is correctly computed self.reaction2.reactants, self.reaction2.products = self.reaction2.products, self.reaction2.reactants reversereverseKinetics = self.reaction2.generateReverseRateCoefficient() # check that reverting the reverse yields the original Tlist = numpy.arange(Tmin, Tmax, 200.0, numpy.float64) P = 1e5 for T in Tlist: korig = original_kinetics.getRateCoefficient(T, P) krevrev = reversereverseKinetics.getRateCoefficient(T, P) self.assertAlmostEqual(korig / krevrev, 1.0, 0)
def testGenerateReverseRateCoefficientLindemann(self): """ Test the Reaction.generateReverseRateCoefficient() method works for the Lindemann format. """ from rmgpy.kinetics import Lindemann arrheniusHigh = Arrhenius( A = (1.39e+16,"cm^3/(mol*s)"), n = -0.534, Ea = (2.243,"kJ/mol"), T0 = (1,"K"), ) arrheniusLow = Arrhenius( A = (2.62e+33,"cm^6/(mol^2*s)"), n = -4.76, Ea = (10.21,"kJ/mol"), T0 = (1,"K"), ) efficiencies = {"C": 3, "C(=O)=O": 2, "CC": 3, "O": 6, "[Ar]": 0.7, "[C]=O": 1.5, "[H][H]": 2} Tmin = 300. Tmax = 2000. Pmin = 0.01 Pmax = 100. comment = """H + CH3 -> CH4""" lindemann = Lindemann( arrheniusHigh = arrheniusHigh, arrheniusLow = arrheniusLow, Tmin = (Tmin,"K"), Tmax = (Tmax,"K"), Pmin = (Pmin,"bar"), Pmax = (Pmax,"bar"), efficiencies = efficiencies, comment = comment, ) original_kinetics = lindemann self.reaction2.kinetics = original_kinetics reverseKinetics = self.reaction2.generateReverseRateCoefficient() self.reaction2.kinetics = reverseKinetics # reverse reactants, products to ensure Keq is correctly computed self.reaction2.reactants, self.reaction2.products = self.reaction2.products, self.reaction2.reactants reversereverseKinetics = self.reaction2.generateReverseRateCoefficient() # check that reverting the reverse yields the original Tlist = numpy.arange(Tmin, Tmax, 200.0, numpy.float64) P = 1e5 for T in Tlist: korig = original_kinetics.getRateCoefficient(T, P) krevrev = reversereverseKinetics.getRateCoefficient(T, P) self.assertAlmostEqual(korig / krevrev, 1.0, 0)
def generate_high_p_limit_kinetics(self): """ If the LibraryReactions represented by `self` has pressure dependent kinetics, try extracting the high pressure limit rate from it. Used for incorporating library reactions with pressure-dependent kinetics in PDep networks. Only reactions flagged as `elementary_high_p=True` should be processed here. If the kinetics is a :class:Lindemann or a :class:Troe, simply get the high pressure limit rate. If the kinetics is a :class:PDepArrhenius or a :class:Chebyshev, generate a :class:Arrhenius kinetics entry that represents the high pressure limit if Pmax >= 90 bar . This high pressure limit Arrhenius kinetics is assigned to the reaction network_kinetics attribute. If this method successfully generated the high pressure limit kinetics, return ``True``, otherwise ``False``. """ logging.debug("Generating high pressure limit kinetics for {0}...".format(self)) if not self.is_unimolecular(): return False if isinstance(self.kinetics, Arrhenius): return self.elementary_high_p if self.network_kinetics is not None: return True if self.elementary_high_p: if isinstance(self.kinetics, (Lindemann, Troe)): self.network_kinetics = self.kinetics.arrheniusHigh self.network_kinetics.comment = self.kinetics.comment self.network_kinetics.comment = "Kinetics taken from the arrheniusHigh attribute of a" \ " Troe/Lindemann exprssion. Originally from reaction library {0}".format(self.library) return True if isinstance(self.kinetics, PDepArrhenius): if self.kinetics.pressures.value_si[-1] >= 9000000: # Pa units if isinstance(self.kinetics.arrhenius[-1], Arrhenius): self.network_kinetics = self.kinetics.arrhenius[-1] return True else: # This is probably MultiArrhenius entries inside a PDepArrhenius kinetics entry. Don't process return False if isinstance(self.kinetics, Chebyshev): if self.kinetics.Pmax.value_si >= 9000000: # Pa units if len(self.reactants) == 1: kunits = 's^-1' elif len(self.reactants) == 2: kunits = 'm^3/(mol*s)' elif len(self.reactants) == 3: kunits = 'm^6/(mol^2*s)' else: kunits = '' t_step = (self.kinetics.Tmax.value_si - self.kinetics.Tmin.value_si) / 20 t_list = np.arange(int(self.kinetics.Tmin.value_si), int(self.kinetics.Tmax.value_si), int(t_step)) if t_list[-1] < int(self.kinetics.Tmax.value_si): t_list = np.insert(t_list, -1, [int(self.kinetics.Tmax.value_si)]) k_list = [] for t in t_list: k_list.append(self.kinetics.get_rate_coefficient(t, self.kinetics.Pmax.value_si)) k_list = np.array(k_list) self.network_kinetics = Arrhenius().fit_to_data(Tlist=t_list, klist=k_list, kunits=kunits) return True logging.info("NOT processing reaction {0} in a pressure-dependent reaction network.\n" "Although it is marked with the `elementary_high_p=True` flag," " it doesn't answer either of the following criteria:\n1. Has a Lindemann or Troe" " kinetics type; 2. Has a PDepArrhenius or Chebyshev kinetics type and has valid" " kinetics at P >= 100 bar.\n".format(self)) return False
def __multiplyKineticsData(self, kinetics1, kinetics2): """ Multiply two kinetics objects `kinetics1` and `kinetics2` of the same class together, returning their product as a new kinetics object of that class. Currently this only works for :class:`KineticsData` or :class:`Arrhenius` objects. """ if isinstance(kinetics1, KineticsData) and isinstance(kinetics2, KineticsData): if len(kinetics1.Tdata.value_si) != len(kinetics2.Tdata.value_si) or any([T1 != T2 for T1, T2 in zip(kinetics1.Tdata.value_si, kinetics2.Tdata.value_si)]): raise KineticsError('Cannot add these KineticsData objects due to their having different temperature points.') kinetics = KineticsData( Tdata = (kinetics1.Tdata.value, kinetics2.Tdata.units), kdata = (kinetics1.kdata.value * kinetics2.kdata.value, kinetics1.kdata.units), ) elif isinstance(kinetics1, Arrhenius) and isinstance(kinetics2, Arrhenius): assert kinetics1.A.units == kinetics2.A.units assert kinetics1.Ea.units == kinetics2.Ea.units assert kinetics1.T0.units == kinetics2.T0.units assert kinetics1.T0.value == kinetics2.T0.value kinetics = Arrhenius( A = (kinetics1.A.value * kinetics2.A.value, kinetics1.A.units), n = (kinetics1.n.value + kinetics2.n.value, kinetics1.n.units), Ea = (kinetics1.Ea.value + kinetics2.Ea.value, kinetics1.Ea.units), T0 = (kinetics1.T0.value, kinetics1.T0.units), ) else: raise KineticsError('Unable to multiply kinetics types "{0}" and "{1}".'.format(kinetics1.__class__, kinetics2.__class__)) if kinetics1.Tmin is not None and kinetics2.Tmin is not None: kinetics.Tmin = kinetics1.Tmin if kinetics1.Tmin.value_si > kinetics2.Tmin.value_si else kinetics2.Tmin elif kinetics1.Tmin is not None and kinetics2.Tmin is None: kinetics.Tmin = kinetics1.Tmin elif kinetics1.Tmin is None and kinetics2.Tmin is not None: kinetics.Tmin = kinetics2.Tmin if kinetics1.Tmax is not None and kinetics2.Tmax is not None: kinetics.Tmax = kinetics1.Tmax if kinetics1.Tmax.value_si < kinetics2.Tmax.value_si else kinetics2.Tmax elif kinetics1.Tmax is not None and kinetics2.Tmax is None: kinetics.Tmax = kinetics1.Tmax elif kinetics1.Tmax is None and kinetics2.Tmax is not None: kinetics.Tmax = kinetics2.Tmax if kinetics1.Pmin is not None and kinetics2.Pmin is not None: kinetics.Pmin = kinetics1.Pmin if kinetics1.Pmin.value_si > kinetics2.Pmin.value_si else kinetics2.Pmin elif kinetics1.Pmin is not None and kinetics2.Pmin is None: kinetics.Pmin = kinetics1.Pmin elif kinetics1.Pmin is None and kinetics2.Pmin is not None: kinetics.Pmin = kinetics2.Pmin if kinetics1.Pmax is not None and kinetics2.Pmax is not None: kinetics.Pmax = kinetics1.Pmax if kinetics1.Pmax.value_si < kinetics2.Pmax.value_si else kinetics2.Pmax elif kinetics1.Pmax is not None and kinetics2.Pmax is None: kinetics.Pmax = kinetics1.Pmax elif kinetics1.Pmax is None and kinetics2.Pmax is not None: kinetics.Pmax = kinetics2.Pmax if kinetics1.comment == '': kinetics.comment = kinetics2.comment elif kinetics2.comment == '': kinetics.comment = kinetics1.comment else: kinetics.comment = kinetics1.comment + ' + ' + kinetics2.comment return kinetics
def test_compute_flux(self): """ Test the liquid batch reactor with a simple kinetic model. """ rxn1 = Reaction(reactants=[self.C2H6, self.CH3], products=[self.C2H5, self.CH4], kinetics=Arrhenius(A=(686.375 * 6, 'm^3/(mol*s)'), n=4.40721, Ea=(7.82799, 'kcal/mol'), T0=(298.15, 'K'))) core_species = [self.CH4, self.CH3, self.C2H6, self.C2H5] edge_species = [] core_reactions = [rxn1] edge_reactions = [] c0 = {self.C2H5: 0.1, self.CH3: 0.1, self.CH4: 0.4, self.C2H6: 0.4} rxn_system = LiquidReactor(self.T, c0, 1, termination=[]) rxn_system.initialize_model(core_species, core_reactions, edge_species, edge_reactions) tlist = np.array([10**(i / 10.0) for i in range(-130, -49)], np.float64) # Integrate to get the solution at each time point t, y, reaction_rates, species_rates = [], [], [], [] for t1 in tlist: rxn_system.advance(t1) t.append(rxn_system.t) # You must make a copy of y because it is overwritten by DASSL at # each call to advance() y.append(rxn_system.y.copy()) reaction_rates.append(rxn_system.core_reaction_rates.copy()) species_rates.append(rxn_system.core_species_rates.copy()) # Convert the solution vectors to np arrays t = np.array(t, np.float64) reaction_rates = np.array(reaction_rates, np.float64) species_rates = np.array(species_rates, np.float64) # Check that we're computing the species fluxes correctly for i in range(t.shape[0]): self.assertAlmostEqual(reaction_rates[i, 0], species_rates[i, 0], delta=1e-6 * reaction_rates[i, 0]) self.assertAlmostEqual(reaction_rates[i, 0], -species_rates[i, 1], delta=1e-6 * reaction_rates[i, 0]) self.assertAlmostEqual(reaction_rates[i, 0], -species_rates[i, 2], delta=1e-6 * reaction_rates[i, 0]) self.assertAlmostEqual(reaction_rates[i, 0], species_rates[i, 3], delta=1e-6 * reaction_rates[i, 0]) # Check that we've reached equilibrium self.assertAlmostEqual(reaction_rates[-1, 0], 0.0, delta=1e-2)
def test_kinetics_io(self): self.io.reaction.get_rmg_reaction() self.io.reaction.rmg_reaction.kinetics = Arrhenius() self.assertTrue(self.io.save_kinetics()) self.assertIsInstance(self.io.read_kinetics_file(), dict)
def load_pseudo_fragment_reactions(fragments_dict): """ Currently only returns a pseudo reaction. It can be extended to generate multiple ractions in the future. """ pseudo_fragrxts = ['RC*C__C', 'RCCCCR'] pseudo_fragprds = ['RCCCCC__CC*'] pseudo_frag_pairs = [('RC*C__C', 'RCCCCC__CC*'), ('RCCCCR', 'RCCCCC__CC*')] fragrxts = [fragments_dict[label] for label in pseudo_fragrxts] fragprds = [fragments_dict[label] for label in pseudo_fragprds] fragpairs = [(fragments_dict[rxt_label], fragments_dict[prod_label]) for rxt_label, prod_label in pseudo_frag_pairs] pseudo_kinetics = Arrhenius(A=(2.000e+05, 'cm^3/(mol*s)'), n=0.0, Ea=(0.0, 'kcal/mol'), T0=(1, 'K')) pseudo_fragrxn = FragmentReaction(index=-1, reactants=fragrxts, products=fragprds, kinetics=pseudo_kinetics, reversible=False, pairs=fragpairs, family='pseudo_rxn') return [pseudo_fragrxn]
def testTSTCalculation(self): """ A test of the transition state theory k(T) calculation function, using the reaction H + C2H4 -> C2H5. """ Tlist = 1000.0/numpy.arange(0.4, 3.35, 0.01) klist = numpy.array([self.reaction.calculateTSTRateCoefficient(T) for T in Tlist]) arrhenius = Arrhenius().fitToData(Tlist, klist, kunits='m^3/(mol*s)') klist2 = numpy.array([arrhenius.getRateCoefficient(T) for T in Tlist]) # Check that the correct Arrhenius parameters are returned self.assertAlmostEqual(arrhenius.A.value_si, 2265.2488, delta=1e-2) self.assertAlmostEqual(arrhenius.n.value_si, 1.45419, delta=1e-4) self.assertAlmostEqual(arrhenius.Ea.value_si, 6645.24, delta=1e-2) # Check that the fit is satisfactory (defined here as always within 5%) for i in range(len(Tlist)): self.assertAlmostEqual(klist[i], klist2[i], delta=5e-2 * klist[i])
def testTSTCalculation(self): """ A test of the transition state theory k(T) calculation function, using the reaction H + C2H4 -> C2H5. """ Tlist = 1000.0/numpy.arange(0.4, 3.35, 0.05) klist = self.reaction.calculateTSTRateCoefficients(Tlist, tunneling='') arrhenius = Arrhenius().fitToData(Tlist, klist, kunits='') klist2 = arrhenius.getRateCoefficients(Tlist) # Check that the correct Arrhenius parameters are returned self.assertAlmostEqual(arrhenius.A.value/1.07506e+07, 1.0, 3) self.assertAlmostEqual(arrhenius.n.value/1.47803, 1.0, 3) self.assertAlmostEqual(arrhenius.Ea.value/10194., 1.0, 3) # Check that the fit is satisfactory for i in range(len(Tlist)): self.assertTrue(abs(1 - klist2[i] / klist[i]) < 0.01)
def test_corespecies_rate(self): """ Test if a specific core species rate is equal to 0 over time. """ c0 = {self.C2H5: 0.1, self.CH3: 0.1, self.CH4: 0.4, self.C2H6: 0.4} rxn1 = Reaction(reactants=[self.C2H6, self.CH3], products=[self.C2H5, self.CH4], kinetics=Arrhenius(A=(686.375 * 6, 'm^3/(mol*s)'), n=4.40721, Ea=(7.82799, 'kcal/mol'), T0=(298.15, 'K'))) core_species = [self.CH4, self.CH3, self.C2H6, self.C2H5] edge_species = [] core_reactions = [rxn1] edge_reactions = [] sensitivity = [] termination_conversion = [] sensitivity_threshold = 0.001 const_species = ["CH4"] sens_conds = { self.C2H5: 0.1, self.CH3: 0.1, self.CH4: 0.4, self.C2H6: 0.4, 'T': self.T } rxn_system = LiquidReactor(self.T, c0, 1, termination_conversion, sensitivity, sensitivity_threshold, const_spc_names=const_species, sens_conditions=sens_conds) # The test regarding the writing of constantSPCindices from input file is check with the previous test. rxn_system.const_spc_indices = [0] rxn_system.initialize_model(core_species, core_reactions, edge_species, edge_reactions) tlist = np.array([10**(i / 10.0) for i in range(-130, -49)], np.float64) # Integrate to get the solution at each time point t, y, reaction_rates, species_rates = [], [], [], [] for t1 in tlist: rxn_system.advance(t1) t.append(rxn_system.t) self.assertEqual( rxn_system.core_species_rates[0], 0, "Core species rate has to be equal to 0 for species hold constant. " "Here it is equal to {0}".format( rxn_system.core_species_rates[0]))
def setUp(self): """ A function run before each unit test in this class. """ octyl_pri = Species(label="", thermo=NASA(polynomials=[ NASAPolynomial(coeffs=[-0.772759,0.093255,-5.84447e-05,1.8557e-08,-2.37127e-12,-3926.9,37.6131], Tmin=(298,'K'), Tmax=(1390,'K')), NASAPolynomial(coeffs=[25.051,0.036948,-1.25765e-05,1.94628e-09,-1.12669e-13,-13330.1,-102.557], Tmin=(1390,'K'), Tmax=(5000,'K')) ], Tmin=(298,'K'), Tmax=(5000,'K'), Cp0=(33.2579,'J/(mol*K)'), CpInf=(577.856,'J/(mol*K)'), comment="""Thermo library: JetSurF0.2"""), molecule=[Molecule(SMILES="[CH2]CCCCCCC")]) octyl_sec = Species(label="", thermo=NASA(polynomials=[ NASAPolynomial(coeffs=[-0.304233,0.0880077,-4.90743e-05,1.21858e-08,-8.87773e-13,-5237.93,36.6583], Tmin=(298,'K'), Tmax=(1383,'K')), NASAPolynomial(coeffs=[24.9044,0.0366394,-1.2385e-05,1.90835e-09,-1.10161e-13,-14713.5,-101.345], Tmin=(1383,'K'), Tmax=(5000,'K')) ], Tmin=(298,'K'), Tmax=(5000,'K'), Cp0=(33.2579,'J/(mol*K)'), CpInf=(577.856,'J/(mol*K)'), comment="""Thermo library: JetSurF0.2"""), molecule=[Molecule(SMILES="CC[CH]CCCCC")]) ethane = Species(label="", thermo=ThermoData( Tdata=([300,400,500,600,800,1000,1500],'K'), Cpdata=([10.294,12.643,14.933,16.932,20.033,22.438,26.281],'cal/(mol*K)'), H298=(12.549,'kcal/mol'), S298=(52.379,'cal/(mol*K)'), Cp0=(33.2579,'J/(mol*K)'), CpInf=(133.032,'J/(mol*K)'), comment="""Thermo library: CH"""), molecule=[Molecule(SMILES="C=C")]) decyl = Species(label="", thermo=NASA(polynomials=[ NASAPolynomial(coeffs=[-1.31358,0.117973,-7.51843e-05,2.43331e-08,-3.17523e-12,-9689.68,43.501], Tmin=(298,'K'), Tmax=(1390,'K')), NASAPolynomial(coeffs=[31.5697,0.0455818,-1.54995e-05,2.39711e-09,-1.3871e-13,-21573.8,-134.709], Tmin=(1390,'K'), Tmax=(5000,'K')) ], Tmin=(298,'K'), Tmax=(5000,'K'), Cp0=(33.2579,'J/(mol*K)'), CpInf=(719.202,'J/(mol*K)'), comment="""Thermo library: JetSurF0.2"""), molecule=[Molecule(SMILES="[CH2]CCCCCCCCC")]) self.database = SolvationDatabase() self.database.load(os.path.join(settings['database.directory'], 'solvation')) self.solvent = 'octane' diffusionLimiter.enable(self.database.getSolventData(self.solvent), self.database) self.T = 298 self.uni_reaction = Reaction(reactants=[octyl_pri], products=[octyl_sec]) self.uni_reaction.kinetics = Arrhenius(A=(2.0, '1/s'), n=0, Ea=(0,'kJ/mol')) self.bi_uni_reaction = Reaction(reactants=[octyl_pri, ethane], products=[decyl]) self.bi_uni_reaction.kinetics = Arrhenius(A=(1.0E-22, 'cm^3/molecule/s'), n=0, Ea=(0,'kJ/mol')) self.intrinsic_rates = { self.uni_reaction: self.uni_reaction.kinetics.getRateCoefficient(self.T, P=100e5), self.bi_uni_reaction: self.bi_uni_reaction.kinetics.getRateCoefficient(self.T, P=100e5), }
def testGenerateReverseRateCoefficientArrhenius(self): """ Test the Reaction.generateReverseRateCoefficient() method works for the Arrhenius format. """ original_kinetics = Arrhenius( A=(2.65e12, "cm^3/(mol*s)"), n=0.0, Ea=(0.0, "kJ/mol"), T0=(1, "K"), Tmin=(300, "K"), Tmax=(2000, "K") ) self.reaction2.kinetics = original_kinetics reverseKinetics = self.reaction2.generateReverseRateCoefficient() self.reaction2.kinetics = reverseKinetics # reverse reactants, products to ensure Keq is correctly computed self.reaction2.reactants, self.reaction2.products = self.reaction2.products, self.reaction2.reactants reversereverseKinetics = self.reaction2.generateReverseRateCoefficient() # check that reverting the reverse yields the original Tlist = numpy.arange(original_kinetics.Tmin.value_si, original_kinetics.Tmax.value_si, 200.0, numpy.float64) P = 1e5 for T in Tlist: korig = original_kinetics.getRateCoefficient(T, P) krevrev = reversereverseKinetics.getRateCoefficient(T, P) self.assertAlmostEqual(korig / krevrev, 1.0, 0)
def generate_group_additivity_values(self, training_set, kunits, method='Arrhenius'): """ Generate the group additivity values using the given `training_set`, a list of 2-tuples of the form ``(template, kinetics)``. You must also specify the `kunits` for the family and the `method` to use when generating the group values. Returns ``True`` if the group values have changed significantly since the last time they were fitted, or ``False`` otherwise. """ warnings.warn("Group additivity is no longer supported and may be" " removed in version 2.3.", DeprecationWarning) # keep track of previous values so we can detect if they change old_entries = dict() for label, entry in self.entries.items(): if entry.data is not None: old_entries[label] = entry.data # Determine a complete list of the entries in the database, sorted as in the tree group_entries = self.top[:] for entry in self.top: group_entries.extend(self.descendants(entry)) # Determine a unique list of the groups we will be able to fit parameters for group_list = [] for template, kinetics in training_set: for group in template: if group not in self.top: group_list.append(group) group_list.extend(self.ancestors(group)[:-1]) group_list = list(set(group_list)) group_list.sort(key=lambda x: x.index) if method == 'KineticsData': # Fit a discrete set of k(T) data points by training against k(T) data Tdata = np.array([300, 400, 500, 600, 800, 1000, 1500, 2000]) # Initialize dictionaries of fitted group values and uncertainties group_values = {} group_uncertainties = {} group_counts = {} group_comments = {} for entry in group_entries: group_values[entry] = [] group_uncertainties[entry] = [] group_counts[entry] = [] group_comments[entry] = set() # Generate least-squares matrix and vector A = [] b = [] kdata = [] for template, kinetics in training_set: if isinstance(kinetics, (Arrhenius, KineticsData)): kd = [kinetics.get_rate_coefficient(T) for T in Tdata] elif isinstance(kinetics, ArrheniusEP): kd = [kinetics.get_rate_coefficient(T, 0) for T in Tdata] else: raise TypeError('Unexpected kinetics model of type {0} for template ' '{1}.'.format(kinetics.__class__, template)) kdata.append(kd) # Create every combination of each group and its ancestors with each other combinations = [] for group in template: groups = [group] groups.extend(self.ancestors(group)) combinations.append(groups) combinations = get_all_combinations(combinations) # Add a row to the matrix for each combination for groups in combinations: Arow = [1 if group in groups else 0 for group in group_list] Arow.append(1) brow = [math.log10(k) for k in kd] A.append(Arow) b.append(brow) for group in groups: group_comments[group].add("{0!s}".format(template)) if len(A) == 0: logging.warning('Unable to fit kinetics groups for family "{0}"; ' 'no valid data found.'.format(self.label)) return A = np.array(A) b = np.array(b) kdata = np.array(kdata) x, residues, rank, s = np.linalg.lstsq(A, b, rcond=RCOND) for t, T in enumerate(Tdata): # Determine error in each group (on log scale) stdev = np.zeros(len(group_list) + 1, np.float64) count = np.zeros(len(group_list) + 1, np.int) for index in range(len(training_set)): template, kinetics = training_set[index] kd = math.log10(kdata[index, t]) km = x[-1, t] + sum([x[group_list.index(group), t] for group in template if group in group_list]) variance = (km - kd) ** 2 for group in template: groups = [group] groups.extend(self.ancestors(group)) for g in groups: if g not in self.top: ind = group_list.index(g) stdev[ind] += variance count[ind] += 1 stdev[-1] += variance count[-1] += 1 stdev = np.sqrt(stdev / (count - 1)) import scipy.stats ci = scipy.stats.t.ppf(0.975, count - 1) * stdev # Update dictionaries of fitted group values and uncertainties for entry in group_entries: if entry == self.top[0]: group_values[entry].append(10 ** x[-1, t]) group_uncertainties[entry].append(10 ** ci[-1]) group_counts[entry].append(count[-1]) elif entry in group_list: index = group_list.index(entry) group_values[entry].append(10 ** x[index, t]) group_uncertainties[entry].append(10 ** ci[index]) group_counts[entry].append(count[index]) else: group_values[entry] = None group_uncertainties[entry] = None group_counts[entry] = None # Store the fitted group values and uncertainties on the associated entries for entry in group_entries: if group_values[entry] is not None: entry.data = KineticsData(Tdata=(Tdata, "K"), kdata=(group_values[entry], kunits)) if not any(np.isnan(np.array(group_uncertainties[entry]))): entry.data.kdata.uncertainties = np.array(group_uncertainties[entry]) entry.data.kdata.uncertainty_type = '*|/' entry.short_desc = "Group additive kinetics." entry.long_desc = "Fitted to {0} rates.\n".format(group_counts[entry]) entry.long_desc += "\n".join(group_comments[entry]) else: entry.data = None elif method == 'Arrhenius': # Fit Arrhenius parameters (A, n, Ea) by training against k(T) data Tdata = np.array([300, 400, 500, 600, 800, 1000, 1500, 2000]) logTdata = np.log(Tdata) Tinvdata = 1000. / (constants.R * Tdata) A = [] b = [] kdata = [] for template, kinetics in training_set: if isinstance(kinetics, (Arrhenius, KineticsData)): kd = [kinetics.get_rate_coefficient(T) for T in Tdata] elif isinstance(kinetics, ArrheniusEP): kd = [kinetics.get_rate_coefficient(T, 0) for T in Tdata] else: raise TypeError('Unexpected kinetics model of type {0} for template ' '{1}.'.format(kinetics.__class__, template)) kdata.append(kd) # Create every combination of each group and its ancestors with each other combinations = [] for group in template: groups = [group] groups.extend(self.ancestors(group)) combinations.append(groups) combinations = get_all_combinations(combinations) # Add a row to the matrix for each combination at each temperature for t, T in enumerate(Tdata): logT = logTdata[t] Tinv = Tinvdata[t] for groups in combinations: Arow = [] for group in group_list: if group in groups: Arow.extend([1, logT, -Tinv]) else: Arow.extend([0, 0, 0]) Arow.extend([1, logT, -Tinv]) brow = math.log(kd[t]) A.append(Arow) b.append(brow) if len(A) == 0: logging.warning('Unable to fit kinetics groups for family "{0}"; ' 'no valid data found.'.format(self.label)) return A = np.array(A) b = np.array(b) kdata = np.array(kdata) x, residues, rank, s = np.linalg.lstsq(A, b, rcond=RCOND) # Store the results self.top[0].data = Arrhenius( A=(math.exp(x[-3]), kunits), n=x[-2], Ea=(x[-1], "kJ/mol"), T0=(1, "K"), ) for i, group in enumerate(group_list): group.data = Arrhenius( A=(math.exp(x[3 * i]), kunits), n=x[3 * i + 1], Ea=(x[3 * i + 2], "kJ/mol"), T0=(1, "K"), ) elif method == 'Arrhenius2': # Fit Arrhenius parameters (A, n, Ea) by training against (A, n, Ea) values A = [] b = [] for template, kinetics in training_set: # Create every combination of each group and its ancestors with each other combinations = [] for group in template: groups = [group] groups.extend(self.ancestors(group)) combinations.append(groups) combinations = get_all_combinations(combinations) # Add a row to the matrix for each parameter if (isinstance(kinetics, Arrhenius) or (isinstance(kinetics, ArrheniusEP) and kinetics.alpha.value_si == 0)): for groups in combinations: Arow = [] for group in group_list: if group in groups: Arow.append(1) else: Arow.append(0) Arow.append(1) Ea = kinetics.E0.value_si if isinstance(kinetics, ArrheniusEP) else kinetics.Ea.value_si brow = [math.log(kinetics.A.value_si), kinetics.n.value_si, Ea / 1000.] A.append(Arow) b.append(brow) if len(A) == 0: logging.warning('Unable to fit kinetics groups for family "{0}"; ' 'no valid data found.'.format(self.label)) return A = np.array(A) b = np.array(b) x, residues, rank, s = np.linalg.lstsq(A, b, rcond=RCOND) # Store the results self.top[0].data = Arrhenius( A=(math.exp(x[-1, 0]), kunits), n=x[-1, 1], Ea=(x[-1, 2], "kJ/mol"), T0=(1, "K"), ) for i, group in enumerate(group_list): group.data = Arrhenius( A=(math.exp(x[i, 0]), kunits), n=x[i, 1], Ea=(x[i, 2], "kJ/mol"), T0=(1, "K"), ) # Add a note to the history of each changed item indicating that we've generated new group values changed = False for label, entry in self.entries.items(): if entry.data is not None and label in old_entries: if (isinstance(entry.data, KineticsData) and isinstance(old_entries[label], KineticsData) and len(entry.data.kdata.value_si) == len(old_entries[label].kdata.value_si) and all(abs(entry.data.kdata.value_si / old_entries[label].kdata.value_si - 1) < 0.01)): # New group values within 1% of old pass elif (isinstance(entry.data, Arrhenius) and isinstance(old_entries[label], Arrhenius) and abs(entry.data.A.value_si / old_entries[label].A.value_si - 1) < 0.01 and abs(entry.data.n.value_si / old_entries[label].n.value_si - 1) < 0.01 and abs(entry.data.Ea.value_si / old_entries[label].Ea.value_si - 1) < 0.01 and abs(entry.data.T0.value_si / old_entries[label].T0.value_si - 1) < 0.01): # New group values within 1% of old pass else: changed = True break else: changed = True break return changed
def setUp(self): """ A function run before each unit test in this class. """ octyl_pri = Species( label="", thermo=NASA(polynomials=[ NASAPolynomial(coeffs=[ -0.772759, 0.093255, -5.84447e-05, 1.8557e-08, -2.37127e-12, -3926.9, 37.6131 ], Tmin=(298, 'K'), Tmax=(1390, 'K')), NASAPolynomial(coeffs=[ 25.051, 0.036948, -1.25765e-05, 1.94628e-09, -1.12669e-13, -13330.1, -102.557 ], Tmin=(1390, 'K'), Tmax=(5000, 'K')) ], Tmin=(298, 'K'), Tmax=(5000, 'K'), Cp0=(33.2579, 'J/(mol*K)'), CpInf=(577.856, 'J/(mol*K)'), comment="""Thermo library: JetSurF0.2"""), molecule=[Molecule(SMILES="[CH2]CCCCCCC")]) octyl_sec = Species( label="", thermo=NASA(polynomials=[ NASAPolynomial(coeffs=[ -0.304233, 0.0880077, -4.90743e-05, 1.21858e-08, -8.87773e-13, -5237.93, 36.6583 ], Tmin=(298, 'K'), Tmax=(1383, 'K')), NASAPolynomial(coeffs=[ 24.9044, 0.0366394, -1.2385e-05, 1.90835e-09, -1.10161e-13, -14713.5, -101.345 ], Tmin=(1383, 'K'), Tmax=(5000, 'K')) ], Tmin=(298, 'K'), Tmax=(5000, 'K'), Cp0=(33.2579, 'J/(mol*K)'), CpInf=(577.856, 'J/(mol*K)'), comment="""Thermo library: JetSurF0.2"""), molecule=[Molecule(SMILES="CC[CH]CCCCC")]) ethane = Species(label="", thermo=ThermoData( Tdata=([300, 400, 500, 600, 800, 1000, 1500], 'K'), Cpdata=([ 10.294, 12.643, 14.933, 16.932, 20.033, 22.438, 26.281 ], 'cal/(mol*K)'), H298=(12.549, 'kcal/mol'), S298=(52.379, 'cal/(mol*K)'), Cp0=(33.2579, 'J/(mol*K)'), CpInf=(133.032, 'J/(mol*K)'), comment="""Thermo library: CH"""), molecule=[Molecule(SMILES="C=C")]) decyl = Species(label="", thermo=NASA(polynomials=[ NASAPolynomial(coeffs=[ -1.31358, 0.117973, -7.51843e-05, 2.43331e-08, -3.17523e-12, -9689.68, 43.501 ], Tmin=(298, 'K'), Tmax=(1390, 'K')), NASAPolynomial(coeffs=[ 31.5697, 0.0455818, -1.54995e-05, 2.39711e-09, -1.3871e-13, -21573.8, -134.709 ], Tmin=(1390, 'K'), Tmax=(5000, 'K')) ], Tmin=(298, 'K'), Tmax=(5000, 'K'), Cp0=(33.2579, 'J/(mol*K)'), CpInf=(719.202, 'J/(mol*K)'), comment="""Thermo library: JetSurF0.2"""), molecule=[Molecule(SMILES="[CH2]CCCCCCCCC")]) acetone = Species(label="", thermo=NASA(polynomials=[ NASAPolynomial(coeffs=[ 3.75568, 0.0264934, -6.55661e-05, 1.94971e-07, -1.82059e-10, -27905.3, 9.0162 ], Tmin=(10, 'K'), Tmax=(422.477, 'K')), NASAPolynomial(coeffs=[ 0.701289, 0.0344988, -1.9736e-05, 5.48052e-09, -5.92612e-13, -27460.6, 23.329 ], Tmin=(422.477, 'K'), Tmax=(3000, 'K')) ], Tmin=(10, 'K'), Tmax=(3000, 'K'), E0=(-232.025, 'kJ/mol'), Cp0=(33.2579, 'J/(mol*K)'), CpInf=(232.805, 'J/(mol*K)')), molecule=[Molecule(SMILES="CC(=O)C")]) peracetic_acid = Species(label="", thermo=NASA(polynomials=[ NASAPolynomial(coeffs=[ 3.81786, 0.016419, 3.32204e-05, -8.98403e-08, 6.63474e-11, -42057.8, 9.65245 ], Tmin=(10, 'K'), Tmax=(354.579, 'K')), NASAPolynomial(coeffs=[ 2.75993, 0.0283534, -1.72659e-05, 5.08158e-09, -5.77773e-13, -41982.8, 13.6595 ], Tmin=(354.579, 'K'), Tmax=(3000, 'K')) ], Tmin=(10, 'K'), Tmax=(3000, 'K'), E0=(-349.698, 'kJ/mol'), Cp0=(33.2579, 'J/(mol*K)'), CpInf=(199.547, 'J/(mol*K)')), molecule=[Molecule(SMILES="CC(=O)OO")]) acetic_acid = Species(label="", thermo=NASA(polynomials=[ NASAPolynomial(coeffs=[ 3.97665, 0.00159915, 8.5542e-05, -1.76486e-07, 1.20201e-10, -53911.5, 8.99309 ], Tmin=(10, 'K'), Tmax=(375.616, 'K')), NASAPolynomial(coeffs=[ 1.57088, 0.0272146, -1.67357e-05, 5.01453e-09, -5.82273e-13, -53730.7, 18.2442 ], Tmin=(375.616, 'K'), Tmax=(3000, 'K')) ], Tmin=(10, 'K'), Tmax=(3000, 'K'), E0=(-448.245, 'kJ/mol'), Cp0=(33.2579, 'J/(mol*K)'), CpInf=(182.918, 'J/(mol*K)')), molecule=[Molecule(SMILES="CC(=O)O")]) criegee = Species(label="", thermo=NASA(polynomials=[ NASAPolynomial(coeffs=[ 3.23876, 0.0679583, -3.35611e-05, 7.91519e-10, 3.13038e-12, -77986, 13.6438 ], Tmin=(10, 'K'), Tmax=(1053.46, 'K')), NASAPolynomial(coeffs=[ 9.84525, 0.0536795, -2.86165e-05, 7.39945e-09, -7.48482e-13, -79977.6, -21.4187 ], Tmin=(1053.46, 'K'), Tmax=(3000, 'K')) ], Tmin=(10, 'K'), Tmax=(3000, 'K'), E0=(-648.47, 'kJ/mol'), Cp0=(33.2579, 'J/(mol*K)'), CpInf=(457.296, 'J/(mol*K)')), molecule=[Molecule(SMILES="CC(=O)OOC(C)(O)C")]) self.database = SolvationDatabase() self.database.load( os.path.join(settings['database.directory'], 'solvation')) self.solvent = 'octane' diffusionLimiter.enable(self.database.getSolventData(self.solvent), self.database) self.T = 298 self.uni_reaction = Reaction(reactants=[octyl_pri], products=[octyl_sec]) self.uni_reaction.kinetics = Arrhenius(A=(2.0, '1/s'), n=0, Ea=(0, 'kJ/mol')) self.bi_uni_reaction = Reaction(reactants=[octyl_pri, ethane], products=[decyl]) self.bi_uni_reaction.kinetics = Arrhenius(A=(1.0E-22, 'cm^3/molecule/s'), n=0, Ea=(0, 'kJ/mol')) self.tri_bi_reaction = Reaction( reactants=[acetone, peracetic_acid, acetic_acid], products=[criegee, acetic_acid]) self.tri_bi_reaction.kinetics = Arrhenius(A=(1.07543e-11, 'cm^6/(mol^2*s)'), n=5.47295, Ea=(-38.5379, 'kJ/mol')) self.intrinsic_rates = { self.uni_reaction: self.uni_reaction.kinetics.getRateCoefficient(self.T, P=100e5), self.bi_uni_reaction: self.bi_uni_reaction.kinetics.getRateCoefficient(self.T, P=100e5), self.tri_bi_reaction: self.tri_bi_reaction.kinetics.getRateCoefficient(self.T, P=100e5), }
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 testSolve(self): """ Test the simple batch reactor with a simple kinetic model. Here we choose a kinetic model consisting of the hydrogen abstraction reaction CH4 + C2H5 <=> CH3 + C2H6. """ CH4 = Species( molecule=[Molecule().fromSMILES("C")], thermo=ThermoData( Tdata=([300, 400, 500, 600, 800, 1000, 1500], "K"), Cpdata=([8.615, 9.687, 10.963, 12.301, 14.841, 16.976, 20.528], "cal/(mol*K)"), H298=(-17.714, "kcal/mol"), S298=(44.472, "cal/(mol*K)"))) CH3 = Species(molecule=[Molecule().fromSMILES("[CH3]")], thermo=ThermoData( Tdata=([300, 400, 500, 600, 800, 1000, 1500], "K"), Cpdata=([ 9.397, 10.123, 10.856, 11.571, 12.899, 14.055, 16.195 ], "cal/(mol*K)"), H298=(9.357, "kcal/mol"), S298=(45.174, "cal/(mol*K)"))) C2H6 = Species(molecule=[Molecule().fromSMILES("CC")], thermo=ThermoData( Tdata=([300, 400, 500, 600, 800, 1000, 1500], "K"), Cpdata=([ 12.684, 15.506, 18.326, 20.971, 25.500, 29.016, 34.595 ], "cal/(mol*K)"), H298=(-19.521, "kcal/mol"), S298=(54.799, "cal/(mol*K)"))) C2H5 = Species(molecule=[Molecule().fromSMILES("C[CH2]")], thermo=ThermoData( Tdata=([300, 400, 500, 600, 800, 1000, 1500], "K"), Cpdata=([ 11.635, 13.744, 16.085, 18.246, 21.885, 24.676, 29.107 ], "cal/(mol*K)"), H298=(29.496, "kcal/mol"), S298=(56.687, "cal/(mol*K)"))) rxn1 = Reaction(reactants=[C2H6, CH3], products=[C2H5, CH4], kinetics=Arrhenius(A=(686.375 * 6, 'm^3/(mol*s)'), n=4.40721, Ea=(7.82799, 'kcal/mol'), T0=(298.15, 'K'))) coreSpecies = [CH4, CH3, C2H6, C2H5] edgeSpecies = [] coreReactions = [rxn1] edgeReactions = [] T = 1000 P = 1.0e5 rxnSystem = SimpleReactor(T, P, initialMoleFractions={ C2H5: 0.1, CH3: 0.1, CH4: 0.4, C2H6: 0.4 }, termination=[]) rxnSystem.initializeModel(coreSpecies, coreReactions, edgeSpecies, edgeReactions) tlist = numpy.array([10**(i / 10.0) for i in range(-130, -49)], numpy.float64) # Integrate to get the solution at each time point t = [] y = [] reactionRates = [] speciesRates = [] for t1 in tlist: rxnSystem.advance(t1) t.append(rxnSystem.t) # You must make a copy of y because it is overwritten by DASSL at # each call to advance() y.append(rxnSystem.y.copy()) reactionRates.append(rxnSystem.coreReactionRates.copy()) speciesRates.append(rxnSystem.coreSpeciesRates.copy()) # Convert the solution vectors to numpy arrays t = numpy.array(t, numpy.float64) y = numpy.array(y, numpy.float64) reactionRates = numpy.array(reactionRates, numpy.float64) speciesRates = numpy.array(speciesRates, numpy.float64) V = constants.R * rxnSystem.T.value_si * numpy.sum( y) / rxnSystem.P.value_si # Check that we're computing the species fluxes correctly for i in range(t.shape[0]): self.assertAlmostEqual(reactionRates[i, 0], speciesRates[i, 0], delta=1e-6 * reactionRates[i, 0]) self.assertAlmostEqual(reactionRates[i, 0], -speciesRates[i, 1], delta=1e-6 * reactionRates[i, 0]) self.assertAlmostEqual(reactionRates[i, 0], -speciesRates[i, 2], delta=1e-6 * reactionRates[i, 0]) self.assertAlmostEqual(reactionRates[i, 0], speciesRates[i, 3], delta=1e-6 * reactionRates[i, 0]) # Check that we've reached equilibrium self.assertAlmostEqual(reactionRates[-1, 0], 0.0, delta=1e-2) ####### # Unit test for the jacobian function: # Solve a reaction system and check if the analytical jacobian matches the finite difference jacobian H2 = Species(molecule=[Molecule().fromSMILES("[H][H]")], thermo=ThermoData( Tdata=([300, 400, 500, 600, 800, 1000, 1500], "K"), Cpdata=([6.89, 6.97, 6.99, 7.01, 7.08, 7.22, 7.72], "cal/(mol*K)"), H298=(0, "kcal/mol"), S298=(31.23, "cal/(mol*K)"))) rxnList = [] rxnList.append( Reaction(reactants=[C2H6], products=[CH3, CH3], kinetics=Arrhenius(A=(686.375 * 6, '1/s'), n=4.40721, Ea=(7.82799, 'kcal/mol'), T0=(298.15, 'K')))) rxnList.append( Reaction(reactants=[CH3, CH3], products=[C2H6], kinetics=Arrhenius(A=(686.375 * 6, 'm^3/(mol*s)'), n=4.40721, Ea=(7.82799, 'kcal/mol'), T0=(298.15, 'K')))) rxnList.append( Reaction(reactants=[C2H6, CH3], products=[C2H5, CH4], kinetics=Arrhenius(A=(46.375 * 6, 'm^3/(mol*s)'), n=3.40721, Ea=(6.82799, 'kcal/mol'), T0=(298.15, 'K')))) rxnList.append( Reaction(reactants=[C2H5, CH4], products=[C2H6, CH3], kinetics=Arrhenius(A=(46.375 * 6, 'm^3/(mol*s)'), n=3.40721, Ea=(6.82799, 'kcal/mol'), T0=(298.15, 'K')))) rxnList.append( Reaction(reactants=[C2H5, CH4], products=[CH3, CH3, CH3], kinetics=Arrhenius(A=(246.375 * 6, 'm^3/(mol*s)'), n=1.40721, Ea=(3.82799, 'kcal/mol'), T0=(298.15, 'K')))) rxnList.append( Reaction(reactants=[CH3, CH3, CH3], products=[C2H5, CH4], kinetics=Arrhenius(A=(246.375 * 6, 'm^6/(mol^2*s)'), n=1.40721, Ea=(3.82799, 'kcal/mol'), T0=(298.15, 'K')))) # rxnList.append( Reaction(reactants=[C2H6, CH3, CH3], products=[C2H5, C2H5, H2], kinetics=Arrhenius(A=(146.375 * 6, 'm^6/(mol^2*s)'), n=2.40721, Ea=(8.82799, 'kcal/mol'), T0=(298.15, 'K')))) rxnList.append( Reaction(reactants=[C2H5, C2H5, H2], products=[C2H6, CH3, CH3], kinetics=Arrhenius(A=(146.375 * 6, 'm^6/(mol^2*s)'), n=2.40721, Ea=(8.82799, 'kcal/mol'), T0=(298.15, 'K')))) rxnList.append( Reaction(reactants=[C2H6, C2H6], products=[CH3, CH4, C2H5], kinetics=Arrhenius(A=(1246.375 * 6, 'm^3/(mol*s)'), n=0.40721, Ea=(8.82799, 'kcal/mol'), T0=(298.15, 'K')))) rxnList.append( Reaction(reactants=[CH3, CH4, C2H5], products=[C2H6, C2H6], kinetics=Arrhenius(A=(46.375 * 6, 'm^6/(mol^2*s)'), n=0.10721, Ea=(8.82799, 'kcal/mol'), T0=(298.15, 'K')))) for rxn in rxnList: coreSpecies = [CH4, CH3, C2H6, C2H5, H2] edgeSpecies = [] coreReactions = [rxn] rxnSystem0 = SimpleReactor(T, P, initialMoleFractions={ CH4: 0.2, CH3: 0.1, C2H6: 0.35, C2H5: 0.15, H2: 0.2 }, termination=[]) rxnSystem0.initializeModel(coreSpecies, coreReactions, edgeSpecies, edgeReactions) dydt0 = rxnSystem0.residual(0.0, rxnSystem0.y, numpy.zeros(rxnSystem0.y.shape))[0] numCoreSpecies = len(coreSpecies) dN = .000001 * sum(rxnSystem0.y) dN_array = dN * numpy.eye(numCoreSpecies) dydt = [] for i in range(numCoreSpecies): rxnSystem0.y[i] += dN dydt.append( rxnSystem0.residual(0.0, rxnSystem0.y, numpy.zeros(rxnSystem0.y.shape))[0]) rxnSystem0.y[i] -= dN # reset y to original y0 # Let the solver compute the jacobian solverJacobian = rxnSystem0.jacobian(0.0, rxnSystem0.y, dydt0, 0.0) # Compute the jacobian using finite differences jacobian = numpy.zeros((numCoreSpecies, numCoreSpecies)) for i in range(numCoreSpecies): for j in range(numCoreSpecies): jacobian[i, j] = (dydt[j][i] - dydt0[i]) / dN self.assertAlmostEqual(jacobian[i, j], solverJacobian[i, j], delta=abs(1e-4 * jacobian[i, j])) #print 'Solver jacobian' #print solverJacobian #print 'Numerical jacobian' #print jacobian ### # Unit test for the compute rate derivative rxnList = [] rxnList.append( Reaction(reactants=[C2H6], products=[CH3, CH3], kinetics=Arrhenius(A=(686.375e6, '1/s'), n=4.40721, Ea=(7.82799, 'kcal/mol'), T0=(298.15, 'K')))) rxnList.append( Reaction(reactants=[C2H6, CH3], products=[C2H5, CH4], kinetics=Arrhenius(A=(46.375 * 6, 'm^3/(mol*s)'), n=3.40721, Ea=(6.82799, 'kcal/mol'), T0=(298.15, 'K')))) rxnList.append( Reaction(reactants=[C2H6, CH3, CH3], products=[C2H5, C2H5, H2], kinetics=Arrhenius(A=(146.375 * 6, 'm^6/(mol^2*s)'), n=2.40721, Ea=(8.82799, 'kcal/mol'), T0=(298.15, 'K')))) coreSpecies = [CH4, CH3, C2H6, C2H5, H2] edgeSpecies = [] coreReactions = rxnList rxnSystem0 = SimpleReactor(T, P, initialMoleFractions={ CH4: 0.2, CH3: 0.1, C2H6: 0.35, C2H5: 0.15, H2: 0.2 }, termination=[]) rxnSystem0.initializeModel(coreSpecies, coreReactions, edgeSpecies, edgeReactions) dfdt0 = rxnSystem0.residual(0.0, rxnSystem0.y, numpy.zeros(rxnSystem0.y.shape))[0] solver_dfdk = rxnSystem0.computeRateDerivative() #print 'Solver d(dy/dt)/dk' #print solver_dfdk integrationTime = 1e-8 rxnSystem0.termination.append(TerminationTime((integrationTime, 's'))) rxnSystem0.simulate(coreSpecies, coreReactions, [], [], 0, 1, 0) y0 = rxnSystem0.y dfdk = numpy.zeros((numCoreSpecies, len(rxnList))) # d(dy/dt)/dk for i in range(len(rxnList)): k0 = rxnList[i].getRateCoefficient(T, P) rxnList[i].kinetics.A.value_si = rxnList[i].kinetics.A.value_si * ( 1 + 1e-3) dk = rxnList[i].getRateCoefficient(T, P) - k0 rxnSystem = SimpleReactor(T, P, initialMoleFractions={ CH4: 0.2, CH3: 0.1, C2H6: 0.35, C2H5: 0.15, H2: 0.2 }, termination=[]) rxnSystem.initializeModel(coreSpecies, coreReactions, edgeSpecies, edgeReactions) dfdt = rxnSystem.residual(0.0, rxnSystem.y, numpy.zeros(rxnSystem.y.shape))[0] dfdk[:, i] = (dfdt - dfdt0) / dk rxnSystem.termination.append( TerminationTime((integrationTime, 's'))) rxnSystem.simulate(coreSpecies, coreReactions, [], [], 0, 1, 0) rxnList[i].kinetics.A.value_si = rxnList[i].kinetics.A.value_si / ( 1 + 1e-3) # reset A factor for i in range(numCoreSpecies): for j in range(len(rxnList)): self.assertAlmostEqual(dfdk[i, j], solver_dfdk[i, j], delta=abs(1e-3 * dfdk[i, j]))
def test_jacobian(self): """ Unit test for the jacobian function: Solve a reaction system and check if the analytical jacobian matches the finite difference jacobian """ coreSpecies = [self.CH4,self.CH3,self.C2H6,self.C2H5] edgeSpecies = [] rxn1 = Reaction(reactants=[self.C2H6,self.CH3], products=[self.C2H5,self.CH4], kinetics=Arrhenius(A=(686.375*6,'m^3/(mol*s)'), n=4.40721, Ea=(7.82799,'kcal/mol'), T0=(298.15,'K'))) coreReactions = [rxn1] edgeReactions = [] numCoreSpecies = len(coreSpecies) rxnList = [] rxnList.append(Reaction(reactants=[self.C2H6], products=[self.CH3,self.CH3], kinetics=Arrhenius(A=(686.375*6,'1/s'), n=4.40721, Ea=(7.82799,'kcal/mol'), T0=(298.15,'K')))) rxnList.append(Reaction(reactants=[self.CH3,self.CH3], products=[self.C2H6], kinetics=Arrhenius(A=(686.375*6,'m^3/(mol*s)'), n=4.40721, Ea=(7.82799,'kcal/mol'), T0=(298.15,'K')))) rxnList.append(Reaction(reactants=[self.C2H6,self.CH3], products=[self.C2H5,self.CH4], kinetics=Arrhenius(A=(46.375*6,'m^3/(mol*s)'), n=3.40721, Ea=(6.82799,'kcal/mol'), T0=(298.15,'K')))) rxnList.append(Reaction(reactants=[self.C2H5,self.CH4], products=[self.C2H6,self.CH3], kinetics=Arrhenius(A=(46.375*6,'m^3/(mol*s)'), n=3.40721, Ea=(6.82799,'kcal/mol'), T0=(298.15,'K')))) rxnList.append(Reaction(reactants=[self.C2H5,self.CH4], products=[self.CH3,self.CH3,self.CH3], kinetics=Arrhenius(A=(246.375*6,'m^3/(mol*s)'), n=1.40721, Ea=(3.82799,'kcal/mol'), T0=(298.15,'K')))) rxnList.append(Reaction(reactants=[self.CH3,self.CH3,self.CH3], products=[self.C2H5,self.CH4], kinetics=Arrhenius(A=(246.375*6,'m^6/(mol^2*s)'), n=1.40721, Ea=(3.82799,'kcal/mol'), T0=(298.15,'K'))))# rxnList.append(Reaction(reactants=[self.C2H6,self.CH3,self.CH3], products=[self.C2H5,self.C2H5,self.H2], kinetics=Arrhenius(A=(146.375*6,'m^6/(mol^2*s)'), n=2.40721, Ea=(8.82799,'kcal/mol'), T0=(298.15,'K')))) rxnList.append(Reaction(reactants=[self.C2H5,self.C2H5,self.H2], products=[self.C2H6,self.CH3,self.CH3], kinetics=Arrhenius(A=(146.375*6,'m^6/(mol^2*s)'), n=2.40721, Ea=(8.82799,'kcal/mol'), T0=(298.15,'K')))) rxnList.append(Reaction(reactants=[self.C2H6,self.C2H6], products=[self.CH3,self.CH4,self.C2H5], kinetics=Arrhenius(A=(1246.375*6,'m^3/(mol*s)'), n=0.40721, Ea=(8.82799,'kcal/mol'), T0=(298.15,'K')))) rxnList.append(Reaction(reactants=[self.CH3,self.CH4,self.C2H5], products=[self.C2H6,self.C2H6], kinetics=Arrhenius(A=(46.375*6,'m^6/(mol^2*s)'), n=0.10721, Ea=(8.82799,'kcal/mol'), T0=(298.15,'K')))) for rxn in rxnList: coreSpecies = [self.CH4,self.CH3,self.C2H6,self.C2H5,self.H2] edgeSpecies = [] coreReactions = [rxn] c0={self.CH4:0.2,self.CH3:0.1,self.C2H6:0.35,self.C2H5:0.15, self.H2:0.2} rxnSystem0 = LiquidReactor(self.T, c0,termination=[]) rxnSystem0.initializeModel(coreSpecies, coreReactions, edgeSpecies, edgeReactions) dydt0 = rxnSystem0.residual(0.0, rxnSystem0.y, numpy.zeros(rxnSystem0.y.shape))[0] dN = .000001*sum(rxnSystem0.y) dN_array = dN*numpy.eye(numCoreSpecies) dydt = [] for i in xrange(numCoreSpecies): rxnSystem0.y[i] += dN dydt.append(rxnSystem0.residual(0.0, rxnSystem0.y, numpy.zeros(rxnSystem0.y.shape))[0]) rxnSystem0.y[i] -= dN # reset y to original y0 # Let the solver compute the jacobian solverJacobian = rxnSystem0.jacobian(0.0, rxnSystem0.y, dydt0, 0.0) # Compute the jacobian using finite differences jacobian = numpy.zeros((numCoreSpecies, numCoreSpecies)) for i in xrange(numCoreSpecies): for j in xrange(numCoreSpecies): jacobian[i,j] = (dydt[j][i]-dydt0[i])/dN self.assertAlmostEqual(jacobian[i,j], solverJacobian[i,j], delta=abs(1e-4*jacobian[i,j]))
def generateRules(family, database): """ For a given reaction `family` label, generate additional rate rules from the corresponding depository training set. This function does automatically what users used to do by hand to construct a rate rule from reaction kinetics found in the literature, i.e. determine the groups involved, adjust to a per-site basis, etc. """ # Load rules and determine starting index rules = family.rules index = max([entry.index for entry in rules.entries.values()] or [0]) + 1 # Load training entries for depository in family.depositories: if 'training' in depository.name: entries = sorted(depository.entries.values(), key=lambda entry: (entry.index, entry.label)) break # Generate a rate rule for each training entry for entry in entries: # Load entry's reaction, template, and kinetics reaction, template = database.kinetics.getForwardReactionForFamilyEntry( entry=entry, family=family.name, thermoDatabase=database.thermo) kinetics = reaction.kinetics # Convert KineticsData to Arrhenius if isinstance(kinetics, KineticsData): kinetics = Arrhenius().fitToData(Tdata=kinetics.Tdata.values, kdata=kinetics.kdata.values, kunits=kinetics.kdata.units, T0=1) # Ignore other kinetics types if not isinstance(kinetics, Arrhenius): continue # Change reference temperature to 1 K if necessary if kinetics.T0.value != 1: kinetics = kinetics.changeT0(1) # Convert kinetics to a per-site basis kinetics.A.value /= reaction.degeneracy # Convert to ArrheniusEP kinetics = ArrheniusEP(A=kinetics.A, n=kinetics.n, alpha=0, E0=kinetics.Ea, Tmin=kinetics.Tmin, Tmax=kinetics.Tmax) # Add new rate rule rules.entries[index] = Entry(index=index, label=';'.join( [group.label for group in template]), item=Reaction(reactants=template[:], products=None), data=kinetics, reference=entry.reference, rank=entry.rank, shortDesc=entry.shortDesc, longDesc=entry.longDesc, history=entry.history) index += 1
def test_jacobian(self): """ Unit test for the jacobian function: Solve a reaction system and check if the analytical jacobian matches the finite difference jacobian. """ core_species = [self.CH4, self.CH3, self.C2H6, self.C2H5, self.H2] edge_species = [] num_core_species = len(core_species) c0 = { self.CH4: 0.2, self.CH3: 0.1, self.C2H6: 0.35, self.C2H5: 0.15, self.H2: 0.2 } edge_reactions = [] rxn_list = [ Reaction(reactants=[self.C2H6], products=[self.CH3, self.CH3], kinetics=Arrhenius(A=(686.375 * 6, '1/s'), n=4.40721, Ea=(7.82799, 'kcal/mol'), T0=(298.15, 'K'))), Reaction(reactants=[self.CH3, self.CH3], products=[self.C2H6], kinetics=Arrhenius(A=(686.375 * 6, 'm^3/(mol*s)'), n=4.40721, Ea=(7.82799, 'kcal/mol'), T0=(298.15, 'K'))), Reaction(reactants=[self.C2H6, self.CH3], products=[self.C2H5, self.CH4], kinetics=Arrhenius(A=(46.375 * 6, 'm^3/(mol*s)'), n=3.40721, Ea=(6.82799, 'kcal/mol'), T0=(298.15, 'K'))), Reaction(reactants=[self.C2H5, self.CH4], products=[self.C2H6, self.CH3], kinetics=Arrhenius(A=(46.375 * 6, 'm^3/(mol*s)'), n=3.40721, Ea=(6.82799, 'kcal/mol'), T0=(298.15, 'K'))), Reaction(reactants=[self.C2H5, self.CH4], products=[self.CH3, self.CH3, self.CH3], kinetics=Arrhenius(A=(246.375 * 6, 'm^3/(mol*s)'), n=1.40721, Ea=(3.82799, 'kcal/mol'), T0=(298.15, 'K'))), Reaction(reactants=[self.CH3, self.CH3, self.CH3], products=[self.C2H5, self.CH4], kinetics=Arrhenius(A=(246.375 * 6, 'm^6/(mol^2*s)'), n=1.40721, Ea=(3.82799, 'kcal/mol'), T0=(298.15, 'K'))), Reaction(reactants=[self.C2H6, self.CH3, self.CH3], products=[self.C2H5, self.C2H5, self.H2], kinetics=Arrhenius(A=(146.375 * 6, 'm^6/(mol^2*s)'), n=2.40721, Ea=(8.82799, 'kcal/mol'), T0=(298.15, 'K'))), Reaction(reactants=[self.C2H5, self.C2H5, self.H2], products=[self.C2H6, self.CH3, self.CH3], kinetics=Arrhenius(A=(146.375 * 6, 'm^6/(mol^2*s)'), n=2.40721, Ea=(8.82799, 'kcal/mol'), T0=(298.15, 'K'))), Reaction(reactants=[self.C2H6, self.C2H6], products=[self.CH3, self.CH4, self.C2H5], kinetics=Arrhenius(A=(1246.375 * 6, 'm^3/(mol*s)'), n=0.40721, Ea=(8.82799, 'kcal/mol'), T0=(298.15, 'K'))), Reaction(reactants=[self.CH3, self.CH4, self.C2H5], products=[self.C2H6, self.C2H6], kinetics=Arrhenius(A=(46.375 * 6, 'm^6/(mol^2*s)'), n=0.10721, Ea=(8.82799, 'kcal/mol'), T0=(298.15, 'K'))), ] # Analytical Jacobian for reaction 6 def jacobian_rxn6(c, kf, kr, s): c1, c2, c3, c4 = c[s[1]], c[s[2]], c[s[3]], c[s[4]] jaco = np.zeros((5, 5)) jaco[1, 1] = -4 * kf * c1 * c2 jaco[1, 2] = -2 * kf * c1 * c1 jaco[1, 3] = 4 * kr * c3 * c4 jaco[1, 4] = 2 * kr * c3 * c3 jaco[2, 1:] = 0.5 * jaco[1, 1:] jaco[3, 1:] = -jaco[1, 1:] jaco[4, 1:] = -0.5 * jaco[1, 1:] return jaco # Analytical Jacobian for reaction 7 def jacobian_rxn7(c, kf, kr, s): c1, c2, c3, c4 = c[s[1]], c[s[2]], c[s[3]], c[s[4]] jaco = np.zeros((5, 5)) jaco[1, 1] = -4 * kr * c1 * c2 jaco[1, 2] = -2 * kr * c1 * c1 jaco[1, 3] = 4 * kf * c3 * c4 jaco[1, 4] = 2 * kf * c3 * c3 jaco[2, 1:] = 0.5 * jaco[1, 1:] jaco[3, 1:] = -jaco[1, 1:] jaco[4, 1:] = -0.5 * jaco[1, 1:] return jaco for rxn_num, rxn in enumerate(rxn_list): core_reactions = [rxn] rxn_system0 = LiquidReactor(self.T, c0, 1, termination=[]) rxn_system0.initialize_model(core_species, core_reactions, edge_species, edge_reactions) dydt0 = rxn_system0.residual(0.0, rxn_system0.y, np.zeros(rxn_system0.y.shape))[0] dN = .000001 * sum(rxn_system0.y) # Let the solver compute the jacobian solver_jacobian = rxn_system0.jacobian(0.0, rxn_system0.y, dydt0, 0.0) if rxn_num not in (6, 7): dydt = [] for i in range(num_core_species): rxn_system0.y[i] += dN dydt.append( rxn_system0.residual(0.0, rxn_system0.y, np.zeros(rxn_system0.y.shape))[0]) rxn_system0.y[i] -= dN # reset y # Compute the jacobian using finite differences jacobian = np.zeros((num_core_species, num_core_species)) for i in range(num_core_species): for j in range(num_core_species): jacobian[i, j] = (dydt[j][i] - dydt0[i]) / dN self.assertAlmostEqual(jacobian[i, j], solver_jacobian[i, j], delta=abs(1e-4 * jacobian[i, j])) # The forward finite difference is very unstable for reactions # 6 and 7. Use Jacobians calculated by hand instead. elif rxn_num == 6: kforward = rxn.get_rate_coefficient(self.T) kreverse = kforward / rxn.get_equilibrium_constant(self.T) jacobian = jacobian_rxn6(c0, kforward, kreverse, core_species) for i in range(num_core_species): for j in range(num_core_species): self.assertAlmostEqual(jacobian[i, j], solver_jacobian[i, j], delta=abs(1e-4 * jacobian[i, j])) elif rxn_num == 7: kforward = rxn.get_rate_coefficient(self.T) kreverse = kforward / rxn.get_equilibrium_constant(self.T) jacobian = jacobian_rxn7(c0, kforward, kreverse, core_species) for i in range(num_core_species): for j in range(num_core_species): self.assertAlmostEqual(jacobian[i, j], solver_jacobian[i, j], delta=abs(1e-4 * jacobian[i, j]))
def test_compute_derivative(self): rxn_list = [ Reaction(reactants=[self.C2H6], products=[self.CH3, self.CH3], kinetics=Arrhenius(A=(686.375e6, '1/s'), n=4.40721, Ea=(7.82799, 'kcal/mol'), T0=(298.15, 'K'))), Reaction(reactants=[self.C2H6, self.CH3], products=[self.C2H5, self.CH4], kinetics=Arrhenius(A=(46.375 * 6, 'm^3/(mol*s)'), n=3.40721, Ea=(6.82799, 'kcal/mol'), T0=(298.15, 'K'))), Reaction(reactants=[self.C2H6, self.CH3, self.CH3], products=[self.C2H5, self.C2H5, self.H2], kinetics=Arrhenius(A=(146.375 * 6, 'm^6/(mol^2*s)'), n=2.40721, Ea=(8.82799, 'kcal/mol'), T0=(298.15, 'K'))), ] core_species = [self.CH4, self.CH3, self.C2H6, self.C2H5, self.H2] edge_species = [] core_reactions = rxn_list edge_reactions = [] num_core_species = len(core_species) c0 = { self.CH4: 0.2, self.CH3: 0.1, self.C2H6: 0.35, self.C2H5: 0.15, self.H2: 0.2 } rxn_system0 = LiquidReactor(self.T, c0, 1, termination=[]) rxn_system0.initialize_model(core_species, core_reactions, edge_species, edge_reactions) dfdt0 = rxn_system0.residual(0.0, rxn_system0.y, np.zeros(rxn_system0.y.shape))[0] solver_dfdk = rxn_system0.compute_rate_derivative() # print 'Solver d(dy/dt)/dk' # print solver_dfdk integration_time = 1e-8 model_settings = ModelSettings(tol_keep_in_edge=0, tol_move_to_core=1, tol_interrupt_simulation=0) simulator_settings = SimulatorSettings() rxn_system0.termination.append(TerminationTime( (integration_time, 's'))) rxn_system0.simulate(core_species, core_reactions, [], [], [], [], model_settings=model_settings, simulator_settings=simulator_settings) y0 = rxn_system0.y dfdk = np.zeros((num_core_species, len(rxn_list))) # d(dy/dt)/dk c0 = { self.CH4: 0.2, self.CH3: 0.1, self.C2H6: 0.35, self.C2H5: 0.15, self.H2: 0.2 } for i in range(len(rxn_list)): k0 = rxn_list[i].get_rate_coefficient(self.T) rxn_list[i].kinetics.A.value_si = rxn_list[ i].kinetics.A.value_si * (1 + 1e-3) dk = rxn_list[i].get_rate_coefficient(self.T) - k0 rxn_system = LiquidReactor(self.T, c0, 1, termination=[]) rxn_system.initialize_model(core_species, core_reactions, edge_species, edge_reactions) dfdt = rxn_system.residual(0.0, rxn_system.y, np.zeros(rxn_system.y.shape))[0] dfdk[:, i] = (dfdt - dfdt0) / dk rxn_system.termination.append( TerminationTime((integration_time, 's'))) model_settings = ModelSettings(tol_keep_in_edge=0, tol_move_to_core=1, tol_interrupt_simulation=0) simulator_settings = SimulatorSettings() rxn_system.simulate(core_species, core_reactions, [], [], [], [], model_settings=model_settings, simulator_settings=simulator_settings) rxn_list[i].kinetics.A.value_si = rxn_list[ i].kinetics.A.value_si / (1 + 1e-3) # reset A factor for i in range(num_core_species): for j in range(len(rxn_list)): self.assertAlmostEqual(dfdk[i, j], solver_dfdk[i, j], delta=abs(1e-3 * dfdk[i, j]))
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
def test_compute_derivative(self): rxnList = [] rxnList.append(Reaction(reactants=[self.C2H6], products=[self.CH3,self.CH3], kinetics=Arrhenius(A=(686.375e6,'1/s'), n=4.40721, Ea=(7.82799,'kcal/mol'), T0=(298.15,'K')))) rxnList.append(Reaction(reactants=[self.C2H6,self.CH3], products=[self.C2H5,self.CH4], kinetics=Arrhenius(A=(46.375*6,'m^3/(mol*s)'), n=3.40721, Ea=(6.82799,'kcal/mol'), T0=(298.15,'K')))) rxnList.append(Reaction(reactants=[self.C2H6,self.CH3,self.CH3], products=[self.C2H5,self.C2H5,self.H2], kinetics=Arrhenius(A=(146.375*6,'m^6/(mol^2*s)'), n=2.40721, Ea=(8.82799,'kcal/mol'), T0=(298.15,'K')))) coreSpecies = [self.CH4,self.CH3,self.C2H6,self.C2H5, self.H2] edgeSpecies = [] coreReactions = rxnList edgeReactions = [] numCoreSpecies = len(coreSpecies) c0={self.CH4:0.2,self.CH3:0.1,self.C2H6:0.35,self.C2H5:0.15, self.H2:0.2} rxnSystem0 = LiquidReactor(self.T, c0,termination=[]) rxnSystem0.initializeModel(coreSpecies, coreReactions, edgeSpecies, edgeReactions) dfdt0 = rxnSystem0.residual(0.0, rxnSystem0.y, numpy.zeros(rxnSystem0.y.shape))[0] solver_dfdk = rxnSystem0.computeRateDerivative() #print 'Solver d(dy/dt)/dk' #print solver_dfdk integrationTime = 1e-8 rxnSystem0.termination.append(TerminationTime((integrationTime,'s'))) rxnSystem0.simulate(coreSpecies, coreReactions, [], [], 0, 1, 0) y0 = rxnSystem0.y dfdk = numpy.zeros((numCoreSpecies,len(rxnList))) # d(dy/dt)/dk c0={self.CH4:0.2,self.CH3:0.1,self.C2H6:0.35,self.C2H5:0.15, self.H2:0.2} for i in xrange(len(rxnList)): k0 = rxnList[i].getRateCoefficient(self.T) rxnList[i].kinetics.A.value_si = rxnList[i].kinetics.A.value_si*(1+1e-3) dk = rxnList[i].getRateCoefficient(self.T) - k0 rxnSystem = LiquidReactor(self.T, c0,termination=[]) rxnSystem.initializeModel(coreSpecies, coreReactions, edgeSpecies, edgeReactions) dfdt = rxnSystem.residual(0.0, rxnSystem.y, numpy.zeros(rxnSystem.y.shape))[0] dfdk[:,i]=(dfdt-dfdt0)/dk rxnSystem.termination.append(TerminationTime((integrationTime,'s'))) rxnSystem.simulate(coreSpecies, coreReactions, [], [], 0, 1, 0) rxnList[i].kinetics.A.value_si = rxnList[i].kinetics.A.value_si/(1+1e-3) # reset A factor for i in xrange(numCoreSpecies): for j in xrange(len(rxnList)): self.assertAlmostEqual(dfdk[i,j], solver_dfdk[i,j], delta=abs(1e-3*dfdk[i,j]))
def generateKineticsGroupValues(family, database, trainingSetLabels, method): """ Evaluate the kinetics group additivity values for the given reaction `family` using the specified lists of depository components `trainingSetLabels` as the training set. The already-loaded RMG database should be given as the `database` parameter. """ kunits = getRateCoefficientUnits(family) print 'Categorizing reactions in training sets for {0}'.format(family.label) trainingSets = createDataSet(trainingSetLabels, family, database) trainingSet = [] for label, data in trainingSets: trainingSet.extend(data) #reactions = [reaction for label, trainingSet in trainingSets for reaction, template, entry in trainingSet] #templates = [template for label, trainingSet in trainingSets for reaction, template, entry in trainingSet] #entries = [entry for label, trainingSet in trainingSets for reaction, template, entry in trainingSet] print 'Fitting new group additivity values for {0}...'.format(family.label) # keep track of previous values so we can detect if they change old_entries = dict() for label,entry in family.groups.entries.iteritems(): if entry.data is not None: old_entries[label] = entry.data # Determine a complete list of the entries in the database, sorted as in the tree groupEntries = family.groups.top[:] for entry in family.groups.top: groupEntries.extend(family.groups.descendants(entry)) # Determine a unique list of the groups we will be able to fit parameters for groupList = [] for reaction, template, entry in trainingSet: for group in template: if group not in family.groups.top: groupList.append(group) groupList.extend(family.groups.ancestors(group)[:-1]) groupList = list(set(groupList)) groupList.sort(key=lambda x: x.index) if method == 'KineticsData': # Fit a discrete set of k(T) data points by training against k(T) data Tdata = [300,400,500,600,800,1000,1500,2000] #kmodel = numpy.zeros_like(kdata) # Initialize dictionaries of fitted group values and uncertainties groupValues = {}; groupUncertainties = {}; groupCounts = {}; groupComments = {} for entry in groupEntries: groupValues[entry] = [] groupUncertainties[entry] = [] groupCounts[entry] = [] groupComments[entry] = set() # Generate least-squares matrix and vector A = []; b = [] kdata = [] for reaction, template, entry in trainingSet: if isinstance(reaction.kinetics, Arrhenius) or isinstance(reaction.kinetics, KineticsData): kd = [reaction.kinetics.getRateCoefficient(T) / reaction.degeneracy for T in Tdata] elif isinstance(reaction.kinetics, ArrheniusEP): kd = [reaction.kinetics.getRateCoefficient(T, 0) / reaction.degeneracy for T in Tdata] else: raise Exception('Unexpected kinetics model of type {0} for reaction {1}.'.format(reaction.kinetics.__class__, reaction)) kdata.append(kd) # Create every combination of each group and its ancestors with each other combinations = [] for group in template: groups = [group]; groups.extend(family.groups.ancestors(group)) combinations.append(groups) combinations = getAllCombinations(combinations) # Add a row to the matrix for each combination for groups in combinations: Arow = [1 if group in groups else 0 for group in groupList] Arow.append(1) brow = [math.log10(k) for k in kd] A.append(Arow); b.append(brow) for group in groups: groupComments[group].add("{0!s}".format(template)) if len(A) == 0: logging.warning('Unable to fit kinetics groups for family "{0}"; no valid data found.'.format(family.groups.label)) return A = numpy.array(A) b = numpy.array(b) kdata = numpy.array(kdata) x, residues, rank, s = numpy.linalg.lstsq(A, b) for t, T in enumerate(Tdata): # Determine error in each group (on log scale) stdev = numpy.zeros(len(groupList)+1, numpy.float64) count = numpy.zeros(len(groupList)+1, numpy.int) for index in range(len(trainingSet)): reaction, template, entry = trainingSet[index] kd = math.log10(kdata[index,t]) km = x[-1,t] + sum([x[groupList.index(group),t] for group in template if group in groupList]) variance = (km - kd)**2 for group in template: groups = [group]; groups.extend(family.groups.ancestors(group)) for g in groups: if g not in family.groups.top: ind = groupList.index(g) stdev[ind] += variance count[ind] += 1 stdev[-1] += variance count[-1] += 1 stdev = numpy.sqrt(stdev / (count - 1)) ci = scipy.stats.t.ppf(0.975, count - 1) * stdev # Update dictionaries of fitted group values and uncertainties for entry in groupEntries: if entry == family.groups.top[0]: groupValues[entry].append(10**x[-1,t]) groupUncertainties[entry].append(10**ci[-1]) groupCounts[entry].append(count[-1]) elif entry in groupList: index = groupList.index(entry) groupValues[entry].append(10**x[index,t]) groupUncertainties[entry].append(10**ci[index]) groupCounts[entry].append(count[index]) else: groupValues[entry] = None groupUncertainties[entry] = None groupCounts[entry] = None # Store the fitted group values and uncertainties on the associated entries for entry in groupEntries: if groupValues[entry] is not None: entry.data = KineticsData(Tdata=(Tdata,"K"), kdata=(groupValues[entry],kunits)) if not any(numpy.isnan(numpy.array(groupUncertainties[entry]))): entry.data.kdata.uncertainties = numpy.array(groupUncertainties[entry]) entry.data.kdata.uncertaintyType = '*|/' entry.shortDesc = "Group additive kinetics." entry.longDesc = "Fitted to {0} rates.\n".format(groupCounts[entry]) entry.longDesc += "\n".join(groupComments[entry]) else: entry.data = None # Print the group values print '=============================== =========== =========== =========== =======' print 'Group T (K) k(T) (SI) CI (95%) Count' print '=============================== =========== =========== =========== =======' entry = family.groups.top[0] for i in range(len(entry.data.Tdata.values)): label = ', '.join(['%s' % (top.label) for top in family.groups.top]) if i == 0 else '' T = Tdata[i] value = groupValues[entry][i] uncertainty = groupUncertainties[entry][i] count = groupCounts[entry][i] print '%-31s %-11g %-11.4e %-11.4e %-7i' % (label, T, value, uncertainty, count) print '------------------------------- ----------- ----------- ----------- -------' for entry in groupEntries: if entry.data is not None: for i in range(len(entry.data.Tdata.values)): label = entry.label if i == 0 else '' T = Tdata[i] value = groupValues[entry][i] uncertainty = groupUncertainties[entry][i] count = groupCounts[entry][i] print '%-31s %-11g %-11.4e %-11.4e %-7i' % (label, T, value, uncertainty, count) print '=============================== =========== =========== =========== =======' elif method == 'Arrhenius': # Fit Arrhenius parameters (A, n, Ea) by training against k(T) data Tdata = [300,400,500,600,800,1000,1500,2000] A = []; b = [] kdata = [] for reaction, template, entry in trainingSet: if isinstance(reaction.kinetics, Arrhenius) or isinstance(reaction.kinetics, KineticsData): kd = [reaction.kinetics.getRateCoefficient(T) / reaction.degeneracy for T in Tdata] elif isinstance(reaction.kinetics, ArrheniusEP): kd = [reaction.kinetics.getRateCoefficient(T, 0) / reaction.degeneracy for T in Tdata] else: raise Exception('Unexpected kinetics model of type {0} for reaction {1}.'.format(reaction.kinetics.__class__, reaction)) kdata.append(kd) # Create every combination of each group and its ancestors with each other combinations = [] for group in template: groups = [group]; groups.extend(family.groups.ancestors(group)) combinations.append(groups) combinations = getAllCombinations(combinations) # Add a row to the matrix for each combination at each temperature for t, T in enumerate(Tdata): logT = math.log(T) Tinv = 1000.0 / (constants.R * T) for groups in combinations: Arow = [] for group in groupList: if group in groups: Arow.extend([1,logT,-Tinv]) else: Arow.extend([0,0,0]) Arow.extend([1,logT,-Tinv]) brow = math.log(kd[t]) A.append(Arow); b.append(brow) if len(A) == 0: logging.warning('Unable to fit kinetics groups for family "{0}"; no valid data found.'.format(family.groups.label)) return A = numpy.array(A) b = numpy.array(b) kdata = numpy.array(kdata) x, residues, rank, s = numpy.linalg.lstsq(A, b) # Store the results family.groups.top[0].data = Arrhenius( A = (math.exp(x[-3]),kunits), n = x[-2], Ea = (x[-1]*1000.,"J/mol"), T0 = (1,"K"), ) for i, group in enumerate(groupList): group.data = Arrhenius( A = (math.exp(x[3*i]),kunits), n = x[3*i+1], Ea = (x[3*i+2]*1000.,"J/mol"), T0 = (1,"K"), ) # Print the results print '======================================= =========== =========== ===========' print 'Group log A (SI) n Ea (kJ/mol) ' print '======================================= =========== =========== ===========' entry = family.groups.top[0] label = ', '.join(['%s' % (top.label) for top in family.groups.top]) logA = math.log10(entry.data.A.value) n = entry.data.n.value Ea = entry.data.Ea.value / 1000. print '%-39s %11.3f %11.3f %11.3f' % (label, logA, n, Ea) print '--------------------------------------- ----------- ----------- -----------' for i, group in enumerate(groupList): label = group.label logA = math.log10(group.data.A.value) n = group.data.n.value Ea = group.data.Ea.value / 1000. print '%-39s %11.3f %11.3f %11.3f' % (label, logA, n, Ea) print '======================================= =========== =========== ===========' elif method == 'Arrhenius2': # Fit Arrhenius parameters (A, n, Ea) by training against (A, n, Ea) values A = []; b = [] for reaction, template, entry in trainingSet: # Create every combination of each group and its ancestors with each other combinations = [] for group in template: groups = [group]; groups.extend(family.groups.ancestors(group)) combinations.append(groups) combinations = getAllCombinations(combinations) # Add a row to the matrix for each parameter if isinstance(entry.data, Arrhenius) or (isinstance(entry.data, ArrheniusEP) and entry.data.alpha.value == 0): for groups in combinations: Arow = [] for group in groupList: if group in groups: Arow.append(1) else: Arow.append(0) Arow.append(1) Ea = entry.data.E0.value if isinstance(entry.data, ArrheniusEP) else entry.data.Ea.value brow = [math.log(entry.data.A.value), entry.data.n.value, Ea / 1000.] A.append(Arow); b.append(brow) if len(A) == 0: logging.warning('Unable to fit kinetics groups for family "{0}"; no valid data found.'.format(family.groups.label)) return A = numpy.array(A) b = numpy.array(b) x, residues, rank, s = numpy.linalg.lstsq(A, b) # Store the results family.groups.top[0].data = Arrhenius( A = (math.exp(x[-1,0]),kunits), n = x[-1,1], Ea = (x[-1,2]*1000.,"J/mol"), T0 = (1,"K"), ) for i, group in enumerate(groupList): group.data = Arrhenius( A = (math.exp(x[i,0]),kunits), n = x[i,1], Ea = (x[i,2]*1000.,"J/mol"), T0 = (1,"K"), ) # Print the results print '======================================= =========== =========== ===========' print 'Group log A (SI) n Ea (kJ/mol) ' print '======================================= =========== =========== ===========' entry = family.groups.top[0] label = ', '.join(['%s' % (top.label) for top in family.groups.top]) logA = math.log10(entry.data.A.value) n = entry.data.n.value Ea = entry.data.Ea.value / 1000. print '%-39s %11.3f %11.3f %11.3f' % (label, logA, n, Ea) print '--------------------------------------- ----------- ----------- -----------' for i, group in enumerate(groupList): label = group.label logA = math.log10(group.data.A.value) n = group.data.n.value Ea = group.data.Ea.value / 1000. print '%-39s %11.3f %11.3f %11.3f' % (label, logA, n, Ea) print '======================================= =========== =========== ===========' # Add a note to the history of each changed item indicating that we've generated new group values changed = False event = [time.asctime(),user,'action','Generated new group additivity values for this entry.'] for label, entry in family.groups.entries.iteritems(): if entry.data is not None and old_entries.has_key(label): if (isinstance(entry.data, KineticsData) and isinstance(old_entries[label], KineticsData) and len(entry.data.kdata.values) == len(old_entries[label].kdata.values) and all(abs(entry.data.kdata.values / old_entries[label].kdata.values - 1) < 0.01)): #print "New group values within 1% of old." pass elif (isinstance(entry.data, Arrhenius) and isinstance(old_entries[label], Arrhenius) and abs(entry.data.A.value / old_entries[label].A.value - 1) < 0.01 and abs(entry.data.n.value / old_entries[label].n.value - 1) < 0.01 and abs(entry.data.Ea.value / old_entries[label].Ea.value - 1) < 0.01 and abs(entry.data.T0.value / old_entries[label].T0.value - 1) < 0.01): #print "New group values within 1% of old." pass else: changed = True entry.history.append(event) return changed
def parse_kinetics(xml, rxn, pdep=False): """ Parse the information from a single kinetics XML. If a P-dep expression is encountered and pdep is set to False, a KineticsError will be raised. """ tree = ET.parse(xml) root = tree.getroot() prime_id = root.attrib['primeID'] # Extract ID # Extract namespace ns = nsprog.match(root.tag).group(0) # Extract rate law type try: rate_law_type = root.attrib['rateLawType'] except KeyError: # Check later if there is a rate coefficient rate_law_type = None else: if rate_law_type == 'mass action': pass elif rate_law_type == 'sum': # Save the links to the kinetics that are meant to be summed up reaction_link = root.find(ns + 'reactionLink') links = { rate_link.attrib['primeID'] for rate_link in reaction_link } return PrIMeKinetics(prime_id, rate_law_type='sum', links=links) elif rate_law_type in ('third body', 'unimolecular', 'chemical activation'): if pdep: raise NotImplementedError( 'P-dep has not been implemented for "{}" type.'.format( rate_law_type)) else: raise KineticsError( 'P-dep kinetics: {}.'.format(rate_law_type)) else: raise Exception('Unknown rate law type: {}'.format(rate_law_type)) # Make sure kinetics exist rate_coeff = root.find(ns + 'rateCoefficient') if rate_coeff is None: raise KineticsError('No rate coefficient found.') # Extract kinetics direction = rate_coeff.attrib['direction'].lower() expression = rate_coeff.find(ns + 'expression') form = expression.attrib['form'].lower() if form == 'constant': raise KineticsError('Missing activation energy.') elif form != 'arrhenius': raise NotImplementedError( 'Kinetics form "{}" has not been implemented yet.'.format(form)) a = e = None n = 0.0 has_n = False for param in expression: if param.tag == ns + 'parameter': if param.attrib['name'].lower() not in valid_params: raise Exception('Unknown parameter: {}'.format( param.attrib['name'])) if param.attrib['name'].lower() == 'a': units = _format_units(param.attrib['units'], rxn, direction) a = (float(param.find(ns + 'value').text), units) elif param.attrib['name'].lower() == 'e': units = _format_units(param.attrib['units'], rxn, direction) e = (float(param.find(ns + 'value').text), units) elif param.attrib['name'].lower() == 'n': n = float(param.find(ns + 'value').text) has_n = True if a is None: raise KineticsError('Missing prefactor.') if e is None: raise KineticsError('Missing activation energy.') # If A has units involving mol and is very small, then it's probably molecules # Smallness is assessed based on an "effective" A at 1000 K (i.e. A*T^n) if a[1] == 'cm^3/(mol*s)' and a[0] * 1000.0**n < 1.0: a[1].replace('mol', 'molecule') warnings.warn( 'Replaced "mol" by "molecule" in units for {}.'.format(xml)) # Extract year ref = root.find(ns + 'bibliographyLink') year_match = yearprog.search(ref.attrib['preferredKey']) if year_match is None: year = None warnings.warn('No year in {}.'.format(xml)) else: year = int(year_match.group(0)) return PrIMeKinetics(prime_id, expression=Arrhenius(A=a, n=n, Ea=e), has_n=has_n, rate_law_type=rate_law_type, direction=direction, year=year)
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 generateRules(family, database): """ For a given reaction `family` label, generate additional rate rules from the corresponding depository training set. This function does automatically what users used to do by hand to construct a rate rule from reaction kinetics found in the literature, i.e. determine the groups involved, adjust to a per-site basis, etc. """ # Load rules and determine starting index rules = family.rules index = max([entry.index for entry in rules.entries.values()] or [0]) + 1 # Load training entries for depository in family.depositories: if 'training' in depository.name: entries = sorted(depository.entries.values(), key=lambda entry: (entry.index, entry.label)) break # Generate a rate rule for each training entry for entry in entries: # Load entry's reaction, template, and kinetics reaction, template = database.kinetics.getForwardReactionForFamilyEntry(entry=entry, family=family.name, thermoDatabase=database.thermo) kinetics = reaction.kinetics # Convert KineticsData to Arrhenius if isinstance(kinetics, KineticsData): kinetics = Arrhenius().fitToData(Tdata=kinetics.Tdata.values, kdata=kinetics.kdata.values, kunits=kinetics.kdata.units, T0=1) # Ignore other kinetics types if not isinstance(kinetics, Arrhenius): continue # Change reference temperature to 1 K if necessary if kinetics.T0.value != 1: kinetics = kinetics.changeT0(1) # Convert kinetics to a per-site basis kinetics.A.value /= reaction.degeneracy # Convert to ArrheniusEP kinetics = ArrheniusEP(A=kinetics.A, n=kinetics.n, alpha=0, E0=kinetics.Ea, Tmin=kinetics.Tmin, Tmax=kinetics.Tmax) # Add new rate rule rules.entries[index] = Entry(index=index, label=';'.join([group.label for group in template]), item=Reaction(reactants=template[:], products=None), data=kinetics, reference=entry.reference, rank=entry.rank, shortDesc=entry.shortDesc, longDesc=entry.longDesc, history=entry.history) index += 1
def setUp(self): """ A method that is called prior to each unit test in this class. """ ethylene = Species( label = 'C2H4', conformer = Conformer( E0 = (44.7127, 'kJ/mol'), modes = [ IdealGasTranslation( mass = (28.0313, 'amu'), ), NonlinearRotor( inertia = ( [3.41526, 16.6498, 20.065], 'amu*angstrom^2', ), symmetry = 4, ), HarmonicOscillator( frequencies = ( [828.397, 970.652, 977.223, 1052.93, 1233.55, 1367.56, 1465.09, 1672.25, 3098.46, 3111.7, 3165.79, 3193.54], 'cm^-1', ), ), ], spinMultiplicity = 1, opticalIsomers = 1, ), ) hydrogen = Species( label = 'H', conformer = Conformer( E0 = (211.794, 'kJ/mol'), modes = [ IdealGasTranslation( mass = (1.00783, 'amu'), ), ], spinMultiplicity = 2, opticalIsomers = 1, ), ) ethyl = Species( label = 'C2H5', conformer = Conformer( E0 = (111.603, 'kJ/mol'), modes = [ IdealGasTranslation( mass = (29.0391, 'amu'), ), NonlinearRotor( inertia = ( [4.8709, 22.2353, 23.9925], 'amu*angstrom^2', ), symmetry = 1, ), HarmonicOscillator( frequencies = ( [482.224, 791.876, 974.355, 1051.48, 1183.21, 1361.36, 1448.65, 1455.07, 1465.48, 2688.22, 2954.51, 3033.39, 3101.54, 3204.73], 'cm^-1', ), ), HinderedRotor( inertia = (1.11481, 'amu*angstrom^2'), symmetry = 6, barrier = (0.244029, 'kJ/mol'), semiclassical = None, ), ], spinMultiplicity = 2, opticalIsomers = 1, ), ) TS = TransitionState( label = 'TS', conformer = Conformer( E0 = (266.694, 'kJ/mol'), modes = [ IdealGasTranslation( mass = (29.0391, 'amu'), ), NonlinearRotor( inertia = ( [6.78512, 22.1437, 22.2114], 'amu*angstrom^2', ), symmetry = 1, ), HarmonicOscillator( frequencies = ( [412.75, 415.206, 821.495, 924.44, 982.714, 1024.16, 1224.21, 1326.36, 1455.06, 1600.35, 3101.46, 3110.55, 3175.34, 3201.88], 'cm^-1', ), ), ], spinMultiplicity = 2, opticalIsomers = 1, ), frequency = (-750.232, 'cm^-1'), ) self.reaction = Reaction( reactants = [hydrogen, ethylene], products = [ethyl], kinetics = Arrhenius( A = (501366000.0, 'cm^3/(mol*s)'), n = 1.637, Ea = (4.32508, 'kJ/mol'), T0 = (1, 'K'), Tmin = (300, 'K'), Tmax = (2500, 'K'), ), transitionState = TS, ) # CC(=O)O[O] acetylperoxy = Species( label='acetylperoxy', thermo=Wilhoit(Cp0=(4.0*constants.R,"J/(mol*K)"), CpInf=(21.0*constants.R,"J/(mol*K)"), a0=-3.95, a1=9.26, a2=-15.6, a3=8.55, B=(500.0,"K"), H0=(-6.151e+04,"J/mol"), S0=(-790.2,"J/(mol*K)")), ) # C[C]=O acetyl = Species( label='acetyl', thermo=Wilhoit(Cp0=(4.0*constants.R,"J/(mol*K)"), CpInf=(15.5*constants.R,"J/(mol*K)"), a0=0.2541, a1=-0.4712, a2=-4.434, a3=2.25, B=(500.0,"K"), H0=(-1.439e+05,"J/mol"), S0=(-524.6,"J/(mol*K)")), ) # [O][O] oxygen = Species( label='oxygen', thermo=Wilhoit(Cp0=(3.5*constants.R,"J/(mol*K)"), CpInf=(4.5*constants.R,"J/(mol*K)"), a0=-0.9324, a1=26.18, a2=-70.47, a3=44.12, B=(500.0,"K"), H0=(1.453e+04,"J/mol"), S0=(-12.19,"J/(mol*K)")), ) self.reaction2 = Reaction( reactants=[acetyl, oxygen], products=[acetylperoxy], kinetics = Arrhenius( A = (2.65e12, 'cm^3/(mol*s)'), n = 0.0, Ea = (0.0, 'kJ/mol'), T0 = (1, 'K'), Tmin = (300, 'K'), Tmax = (2000, 'K'), ), )
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 update(self, reaction_model, pdep_settings): """ Regenerate the :math:`k(T,P)` values for this partial network if the network is marked as invalid. """ from rmgpy.kinetics import Arrhenius, KineticsData, MultiArrhenius # Get the parameters for the pressure dependence calculation job = pdep_settings job.network = self output_directory = pdep_settings.output_file Tmin = job.Tmin.value_si Tmax = job.Tmax.value_si Pmin = job.Pmin.value_si Pmax = job.Pmax.value_si Tlist = job.Tlist.value_si Plist = job.Plist.value_si maximum_grain_size = job.maximum_grain_size.value_si if job.maximum_grain_size is not None else 0.0 minimum_grain_count = job.minimum_grain_count method = job.method interpolation_model = job.interpolation_model active_j_rotor = job.active_j_rotor active_k_rotor = job.active_k_rotor rmgmode = job.rmgmode # Figure out which configurations are isomers, reactant channels, and product channels self.update_configurations(reaction_model) # Make sure we have high-P kinetics for all path reactions for rxn in self.path_reactions: if rxn.kinetics is None and rxn.reverse.kinetics is None: raise PressureDependenceError( 'Path reaction {0} with no high-pressure-limit kinetics encountered in ' 'PDepNetwork #{1:d}.'.format(rxn, self.index)) elif rxn.kinetics is not None and rxn.kinetics.is_pressure_dependent( ) and rxn.network_kinetics is None: raise PressureDependenceError( 'Pressure-dependent kinetics encountered for path reaction {0} in ' 'PDepNetwork #{1:d}.'.format(rxn, self.index)) # Do nothing if the network is already valid if self.valid: return # Do nothing if there are no explored wells if len(self.explored) == 0 and len(self.source) > 1: return # Log the network being updated logging.info("Updating {0!s}".format(self)) # Generate states data for unimolecular isomers and reactants if necessary for isomer in self.isomers: spec = isomer.species[0] if not spec.has_statmech(): spec.generate_statmech() for reactants in self.reactants: for spec in reactants.species: if not spec.has_statmech(): spec.generate_statmech() # Also generate states data for any path reaction reactants, so we can # always apply the ILT method in the direction the kinetics are known for reaction in self.path_reactions: for spec in reaction.reactants: if not spec.has_statmech(): spec.generate_statmech() # While we don't need the frequencies for product channels, we do need # the E0, so create a conformer object with the E0 for the product # channel species if necessary for products in self.products: for spec in products.species: if spec.conformer is None: spec.conformer = Conformer(E0=spec.get_thermo_data().E0) # Determine transition state energies on potential energy surface # In the absence of any better information, we simply set it to # be the reactant ground-state energy + the activation energy # Note that we need Arrhenius kinetics in order to do this for rxn in self.path_reactions: if rxn.kinetics is None: raise Exception( 'Path reaction "{0}" in PDepNetwork #{1:d} has no kinetics!' .format(rxn, self.index)) elif isinstance(rxn.kinetics, KineticsData): if len(rxn.reactants) == 1: kunits = 's^-1' elif len(rxn.reactants) == 2: kunits = 'm^3/(mol*s)' elif len(rxn.reactants) == 3: kunits = 'm^6/(mol^2*s)' else: kunits = '' rxn.kinetics = Arrhenius().fit_to_data( Tlist=rxn.kinetics.Tdata.value_si, klist=rxn.kinetics.kdata.value_si, kunits=kunits) elif isinstance(rxn.kinetics, MultiArrhenius): logging.info( 'Converting multiple kinetics to a single Arrhenius expression for reaction {rxn}' .format(rxn=rxn)) rxn.kinetics = rxn.kinetics.to_arrhenius(Tmin=Tmin, Tmax=Tmax) elif not isinstance(rxn.kinetics, Arrhenius) and rxn.network_kinetics is None: raise Exception( 'Path reaction "{0}" in PDepNetwork #{1:d} has invalid kinetics ' 'type "{2!s}".'.format(rxn, self.index, rxn.kinetics.__class__)) rxn.fix_barrier_height(force_positive=True) if rxn.network_kinetics is None: E0 = sum( [spec.conformer.E0.value_si for spec in rxn.reactants]) + rxn.kinetics.Ea.value_si else: E0 = sum([ spec.conformer.E0.value_si for spec in rxn.reactants ]) + rxn.network_kinetics.Ea.value_si rxn.transition_state = rmgpy.species.TransitionState( conformer=Conformer(E0=(E0 * 0.001, "kJ/mol"))) # Set collision model bath_gas = [ spec for spec in reaction_model.core.species if not spec.reactive ] assert len( bath_gas) > 0, 'No unreactive species to identify as bath gas' self.bath_gas = {} for spec in bath_gas: # is this really the only/best way to weight them? self.bath_gas[spec] = 1.0 / len(bath_gas) # Save input file if not self.label: self.label = str(self.index) if output_directory: job.save_input_file( os.path.join( output_directory, 'pdep', 'network{0:d}_{1:d}.py'.format(self.index, len(self.isomers)))) self.log_summary(level=logging.INFO) # Calculate the rate coefficients self.initialize(Tmin, Tmax, Pmin, Pmax, maximum_grain_size, minimum_grain_count, active_j_rotor, active_k_rotor, rmgmode) K = self.calculate_rate_coefficients(Tlist, Plist, method) # Generate PDepReaction objects configurations = [] configurations.extend([isom.species[:] for isom in self.isomers]) configurations.extend( [reactant.species[:] for reactant in self.reactants]) configurations.extend( [product.species[:] for product in self.products]) j = configurations.index(self.source) for i in range(K.shape[2]): if i != j: # Find the path reaction net_reaction = None for r in self.net_reactions: if r.has_template(configurations[j], configurations[i]): net_reaction = r # If net reaction does not already exist, make a new one if net_reaction is None: net_reaction = PDepReaction(reactants=configurations[j], products=configurations[i], network=self, kinetics=None) net_reaction = reaction_model.make_new_pdep_reaction( net_reaction) self.net_reactions.append(net_reaction) # Place the net reaction in the core or edge if necessary # Note that leak reactions are not placed in the edge if all([s in reaction_model.core.species for s in net_reaction.reactants]) \ and all([s in reaction_model.core.species for s in net_reaction.products]): # Check whether netReaction already exists in the core as a LibraryReaction for rxn in reaction_model.core.reactions: if isinstance(rxn, LibraryReaction) \ and rxn.is_isomorphic(net_reaction, either_direction=True) \ and not rxn.allow_pdep_route and not rxn.elementary_high_p: logging.info( 'Network reaction {0} matched an existing core reaction {1}' ' from the {2} library, and was not added to the model' .format(str(net_reaction), str(rxn), rxn.library)) break else: reaction_model.add_reaction_to_core(net_reaction) else: # Check whether netReaction already exists in the edge as a LibraryReaction for rxn in reaction_model.edge.reactions: if isinstance(rxn, LibraryReaction) \ and rxn.is_isomorphic(net_reaction, either_direction=True) \ and not rxn.allow_pdep_route and not rxn.elementary_high_p: logging.info( 'Network reaction {0} matched an existing edge reaction {1}' ' from the {2} library, and was not added to the model' .format(str(net_reaction), str(rxn), rxn.library)) break else: reaction_model.add_reaction_to_edge(net_reaction) # Set/update the net reaction kinetics using interpolation model kdata = K[:, :, i, j].copy() order = len(net_reaction.reactants) kdata *= 1e6**(order - 1) kunits = { 1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)' }[order] net_reaction.kinetics = job.fit_interpolation_model( Tlist, Plist, kdata, kunits) # Check: For each net reaction that has a path reaction, make # sure the k(T,P) values for the net reaction do not exceed # the k(T) values of the path reaction # Only check the k(T,P) value at the highest P and lowest T, # as this is the one most likely to be in the high-pressure # limit t = 0 p = len(Plist) - 1 for pathReaction in self.path_reactions: if pathReaction.is_isomerization(): # Don't check isomerization reactions, since their # k(T,P) values potentially contain both direct and # well-skipping contributions, and therefore could be # significantly larger than the direct k(T) value # (This can also happen for association/dissociation # reactions, but the effect is generally not too large) continue if pathReaction.reactants == net_reaction.reactants and pathReaction.products == net_reaction.products: if pathReaction.network_kinetics is not None: kinf = pathReaction.network_kinetics.get_rate_coefficient( Tlist[t]) else: kinf = pathReaction.kinetics.get_rate_coefficient( Tlist[t]) if K[t, p, i, j] > 2 * kinf: # To allow for a small discretization error logging.warning( 'k(T,P) for net reaction {0} exceeds high-P k(T) by {1:g} at {2:g} K, ' '{3:g} bar'.format(net_reaction, K[t, p, i, j] / kinf, Tlist[t], Plist[p] / 1e5)) logging.info( ' k(T,P) = {0:9.2e} k(T) = {1:9.2e}'. format(K[t, p, i, j], kinf)) break elif pathReaction.products == net_reaction.reactants and pathReaction.reactants == net_reaction.products: if pathReaction.network_kinetics is not None: kinf = pathReaction.network_kinetics.get_rate_coefficient( Tlist[t] ) / pathReaction.get_equilibrium_constant(Tlist[t]) else: kinf = pathReaction.kinetics.get_rate_coefficient( Tlist[t] ) / pathReaction.get_equilibrium_constant(Tlist[t]) if K[t, p, i, j] > 2 * kinf: # To allow for a small discretization error logging.warning( 'k(T,P) for net reaction {0} exceeds high-P k(T) by {1:g} at {2:g} K, ' '{3:g} bar'.format(net_reaction, K[t, p, i, j] / kinf, Tlist[t], Plist[p] / 1e5)) logging.info( ' k(T,P) = {0:9.2e} k(T) = {1:9.2e}'. format(K[t, p, i, j], kinf)) break # Delete intermediate arrays to conserve memory self.cleanup() # We're done processing this network, so mark it as valid self.valid = True
def queryNISTKinetics(reactants, products, cookiejar): """ Query the NIST website for the gas-phase reaction consisting of the given lists of `reactants` and `products`, which should already be in a form that NIST understands (e.g. CAS number). """ entries = [] # Set the POST data to contain the parameters for our reaction kinetics query post = { 'boolean1': '', 'boolean2': 'and', 'boolean3': 'and', 'boolean4': 'and', 'category': '0', 'database': 'kinetics', 'doc': 'SearchForm', 'lp1': ' ', 'lp2': ' ', 'lp3': ' ', 'lp4': ' ', 'relate1': '=', 'relate2': '=', 'relate3': '=', 'relate4': '=', 'rp1': ' ', 'rp2': ' ', 'rp3': ' ', 'rp4': ' ', 'type': 'java', 'Units': '', } count = 0 for reactant in reactants: count += 1 post['field{0:d}'.format(count)] = 'reactants' post['text{0:d}'.format(count)] = reactant for product in products: count += 1 post['field{0:d}'.format(count)] = 'products' post['text{0:d}'.format(count)] = product post['numberOfFields'] = str(count) # Formally query the NIST kinetics database opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookiejar)) request = opener.open('http://kinetics.nist.gov/kinetics/Search.jsp', data=urllib.urlencode(post)) searchHTML = request.read() request.close() # Some checks to ensure that the search was successful if "No records found" in searchHTML: return [] elif "There was no match in the database for the following input" in searchHTML: return [] elif "Click on a link in the table below to see detail on the selected reaction" in searchHTML: return [] # Parse the returned HTML to extract the kinetics information # We use the BeautifulSoup package because the returned HTML is probably # not valid XML, so DOM and SAX parsers would choke soup = BeautifulSoup(searchHTML) form = soup.findAll(name='form', attrs={'name': 'KineticsResults'})[0] for tr in form.findAll(name='tr'): tdlist = tr.findAll(name='td') if len(tdlist) == 17: # Some extra effort is needed to extract the squib from the BeautifulSoup DOM tree squib = re.search('\d\d\d\d\w\w\w?(/\w\w\w?)?\w+(-\w+)?:\d+', unicode(tdlist[2].a)) squib = squib.group() # Assume the result is of modified Arrhenius form Trange = tdlist[6].text A = tdlist[8].text n = tdlist[10].text Ea = tdlist[12].text k300 = tdlist[14].text order = tdlist[16].text # Reject results that don't have a valid preexponential or activation energy if A == ' ' or Ea == ' ': continue # Reject results whose reaction order does not match the number of reactants if int(order) != len(reactants): continue # Many times n is not given, so set it to zero if that happens if n == ' ': n = 0.0 # Convert the temperature range to separate minimum and maximum valies if '-' in Trange: Tmin, Tmax = Trange.split('-') else: Tmin = Trange Tmax = Trange # Create an entry for this result and append it to the list of entries entry = Entry( data=Arrhenius( A=(float(A), "cm^3/(mol*s)" if len(reactants) == 2 else "s^-1"), n=float(n), Ea=(float(Ea), "kJ/mol"), T0=(1.0, "K"), Tmin=(float(Tmin), "K"), Tmax=(float(Tmax), "K"), ), reference= squib, # Just save the squib for now; we'll get the actual reference later ) entries.append(entry) return entries
def generateAdditionalRateRules(family, database): """ For a given reaction `family` label, generate additional rate rules from the corresponding depository training set. This function does automatically what users used to do by hand to construct a rate rule from reaction kinetics found in the literature, i.e. determine the groups involved, adjust to a per-site basis, etc. """ # Find the database components we will need rules = database.kinetics.depository['{0}/rules'.format(family)] groups = database.kinetics.groups[family] index = max([entry.index for entry in rules.entries.values()]) + 1 for depositoryLabel in ['training']: depository = database.kinetics.depository['{0}/{1}'.format(family, depositoryLabel)] entries = depository.entries.values() entries.sort(key=lambda x: (x.index, x.label)) for entry0 in entries: reaction, template = database.kinetics.getForwardReactionForFamilyEntry(entry=entry0, family=family, thermoDatabase=database.thermo) # We must convert the kinetics to ArrheniusEP to finish our rate rule kinetics = reaction.kinetics # If kinetics are KineticsData, then first convert to Arrhenius if isinstance(kinetics, KineticsData): kinetics = Arrhenius().fitToData( Tdata=kinetics.Tdata.values, kdata=kinetics.kdata.values, kunits=kinetics.kdata.units, T0=1, ) # Now convert from Arrhenius to ArrheniusEP # If not Arrhenius (or KineticsData), then skip this entry if isinstance(kinetics, Arrhenius): if kinetics.T0.value != 1: kinetics.changeT0(1) kinetics = ArrheniusEP( A = kinetics.A, n = kinetics.n, alpha = 0, E0 = kinetics.Ea, Tmin = kinetics.Tmin, Tmax = kinetics.Tmax, ) else: break # Put the kinetics on a per-site basis kinetics.A.value /= reaction.degeneracy # Construct a new entry for the new rate rule label = ';'.join([group.label for group in template]) item = Reaction( reactants = template[:], products = None, ) entry = Entry( index = index, label = label, item = item, data = kinetics, reference = entry0.reference, rank = entry0.rank, shortDesc = entry0.shortDesc, longDesc = entry0.longDesc, history = entry0.history, ) # Add the new rate rule to the depository of rate rules rules.entries[entry.index] = entry index += 1