def setUp(self): """ A method that is run before each unit test in this class. """ self.nC4H10O = Species( label='n-C4H10O', conformer=Conformer( E0=(-317.807, 'kJ/mol'), modes=[ IdealGasTranslation(mass=(74.07, "g/mol")), NonlinearRotor(inertia=([41.5091, 215.751, 233.258], "amu*angstrom^2"), symmetry=1), HarmonicOscillator(frequencies=([ 240.915, 341.933, 500.066, 728.41, 809.987, 833.93, 926.308, 948.571, 1009.3, 1031.46, 1076, 1118.4, 1184.66, 1251.36, 1314.36, 1321.42, 1381.17, 1396.5, 1400.54, 1448.08, 1480.18, 1485.34, 1492.24, 1494.99, 1586.16, 2949.01, 2963.03, 2986.19, 2988.1, 2995.27, 3026.03, 3049.05, 3053.47, 3054.83, 3778.88 ], "cm^-1")), HinderedRotor(inertia=(0.854054, "amu*angstrom^2"), symmetry=1, fourier=([[ 0.25183, -1.37378, -2.8379, 0.0305112, 0.0028088 ], [ 0.458307, 0.542121, -0.599366, -0.00283925, 0.0398529 ]], "kJ/mol")), HinderedRotor( inertia=(8.79408, "amu*angstrom^2"), symmetry=1, fourier=([[ 0.26871, -0.59533, -8.15002, -0.294325, -0.145357 ], [1.1884, 0.99479, -0.940416, -0.186538, 0.0309834]], "kJ/mol")), HinderedRotor(inertia=(7.88153, "amu*angstrom^2"), symmetry=1, fourier=([[ -4.67373, 2.03735, -6.25993, -0.27325, -0.048748 ], [ -0.982845, 1.76637, -1.57619, 0.474364, -0.000681718 ]], "kJ/mol")), HinderedRotor(inertia=(2.81525, "amu*angstrom^2"), symmetry=3, barrier=(2.96807, "kcal/mol")), ], spin_multiplicity=1, optical_isomers=1, ), molecular_weight=(74.07, "g/mol"), transport_data=TransportData(sigma=(5.94, 'angstrom'), epsilon=(559, 'K')), energy_transfer_model=SingleExponentialDown( alpha0=(447.5 * 0.011962, "kJ/mol"), T0=(300, "K"), n=0.85), ) self.nC4H10O.from_smiles('CCCCO') self.nC4H8 = Species( label='n-C4H8', conformer=Conformer( E0=(-17.8832, 'kJ/mol'), modes=[ IdealGasTranslation(mass=(56.06, "g/mol")), NonlinearRotor(inertia=([22.2748, 122.4, 125.198], "amu*angstrom^2"), symmetry=1), HarmonicOscillator(frequencies=([ 308.537, 418.67, 636.246, 788.665, 848.906, 936.762, 979.97, 1009.48, 1024.22, 1082.96, 1186.38, 1277.55, 1307.65, 1332.87, 1396.67, 1439.09, 1469.71, 1484.45, 1493.19, 1691.49, 2972.12, 2994.31, 3018.48, 3056.87, 3062.76, 3079.38, 3093.54, 3174.52 ], "cm^-1")), HinderedRotor(inertia=(5.28338, "amu*angstrom^2"), symmetry=1, fourier=([[ -0.579364, -0.28241, -4.46469, 0.143368, 0.126756 ], [ 1.01804, -0.494628, -0.00318651, -0.245289, 0.193728 ]], "kJ/mol")), HinderedRotor( inertia=(2.60818, "amu*angstrom^2"), symmetry=3, fourier=([[ 0.0400372, 0.0301986, -6.4787, -0.0248675, -0.0324753 ], [0.0312541, 0.0538, -0.493785, 0.0965968, 0.125292]], "kJ/mol")), ], spin_multiplicity=1, optical_isomers=1, ), ) self.nC4H8.from_smiles('CCC=C') self.H2O = Species( label='H2O', conformer=Conformer( E0=(-269.598, 'kJ/mol'), modes=[ IdealGasTranslation(mass=(18.01, "g/mol")), NonlinearRotor(inertia=([0.630578, 1.15529, 1.78586], "amu*angstrom^2"), symmetry=2), HarmonicOscillator( frequencies=([1622.09, 3771.85, 3867.85], "cm^-1")), ], spin_multiplicity=1, optical_isomers=1, ), ) self.H2O.from_smiles('O') self.N2 = Species( label='N2', molecular_weight=(28.04, "g/mol"), transport_data=TransportData(sigma=(3.41, "angstrom"), epsilon=(124, "K")), energy_transfer_model=None, ) self.N2.from_smiles('N#N') logging.error('to TS') self.TS = TransitionState( label='TS', conformer=Conformer( E0=(-42.4373, "kJ/mol"), modes=[ IdealGasTranslation(mass=(74.07, "g/mol")), NonlinearRotor(inertia=([40.518, 232.666, 246.092], "u*angstrom**2"), symmetry=1, quantum=False), HarmonicOscillator(frequencies=([ 134.289, 302.326, 351.792, 407.986, 443.419, 583.988, 699.001, 766.1, 777.969, 829.671, 949.753, 994.731, 1013.59, 1073.98, 1103.79, 1171.89, 1225.91, 1280.67, 1335.08, 1373.9, 1392.32, 1417.43, 1469.51, 1481.61, 1490.16, 1503.73, 1573.16, 2972.85, 2984.3, 3003.67, 3045.78, 3051.77, 3082.37, 3090.44, 3190.73, 3708.52 ], "kayser")), HinderedRotor(inertia=(2.68206, "amu*angstrom^2"), symmetry=3, barrier=(3.35244, "kcal/mol")), HinderedRotor(inertia=(9.77669, "amu*angstrom^2"), symmetry=1, fourier=([[ 0.208938, -1.55291, -4.05398, -0.105798, -0.104752 ], [ 2.00518, -0.020767, -0.333595, 0.137791, -0.274578 ]], "kJ/mol")), ], spin_multiplicity=1, optical_isomers=1, ), frequency=(-2038.34, 'cm^-1'), ) self.reaction = Reaction(label='dehydration', reactants=[self.nC4H10O], products=[self.nC4H8, self.H2O], transition_state=self.TS, kinetics=Arrhenius(A=(0.0387, 'm^3/(mol*s)'), n=2.7, Ea=(2.6192e4, 'J/mol'), T0=(1, 'K'))) self.network = Network( label='n-butanol', isomers=[Configuration(self.nC4H10O)], reactants=[], products=[Configuration(self.nC4H8, self.H2O)], path_reactions=[self.reaction], bath_gas={self.N2: 1.0}, ) self.pdepnetwork = deepcopy(self.network) self.pdepnetwork.__class__ = PDepNetwork self.pdepnetwork.source = [self.pdepnetwork.isomers[0].species[0]] self.pdepnetwork.index = 1 self.pdepnetwork.explored = []
def update(self, reactionModel, pdepSettings): """ 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 from rmgpy.pdep.collision import SingleExponentialDown from rmgpy.pdep.reaction import fitInterpolationModel # Get the parameters for the pressure dependence calculation job = pdepSettings job.network = self outputDirectory = pdepSettings.outputFile 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 maximumGrainSize = job.maximumGrainSize.value_si if job.maximumGrainSize is not None else 0.0 minimumGrainCount = job.minimumGrainCount method = job.method interpolationModel = job.interpolationModel activeJRotor = job.activeJRotor activeKRotor = job.activeKRotor rmgmode = job.rmgmode # Figure out which configurations are isomers, reactant channels, and product channels self.updateConfigurations(reactionModel) # Make sure we have high-P kinetics for all path reactions for rxn in self.pathReactions: 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.isPressureDependent( ): 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.hasStatMech(): spec.generateStatMech() for reactants in self.reactants: for spec in reactants.species: if not spec.hasStatMech(): spec.generateStatMech() # 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.pathReactions: for spec in reaction.reactants: if not spec.hasStatMech(): spec.generateStatMech() # 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.getThermoData().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.pathReactions: 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().fitToData( 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.toArrhenius(Tmin=Tmin, Tmax=Tmax) elif not isinstance(rxn.kinetics, Arrhenius): raise Exception( 'Path reaction "{0}" in PDepNetwork #{1:d} has invalid kinetics type "{2!s}".' .format(rxn, self.index, rxn.kinetics.__class__)) rxn.fixBarrierHeight(forcePositive=True) E0 = sum([spec.conformer.E0.value_si for spec in rxn.reactants]) + rxn.kinetics.Ea.value_si rxn.transitionState = rmgpy.species.TransitionState( conformer=Conformer(E0=(E0 * 0.001, "kJ/mol")), ) # Set collision model bathGas = [ spec for spec in reactionModel.core.species if not spec.reactive ] self.bathGas = {} for spec in bathGas: # is this really the only/best way to weight them? And what is alpha0? self.bathGas[spec] = 1.0 / len(bathGas) spec.collisionModel = SingleExponentialDown(alpha0=(4.86, 'kcal/mol')) # Save input file if not self.label: self.label = str(self.index) job.saveInputFile( os.path.join( outputDirectory, 'pdep', 'network{0:d}_{1:d}.py'.format(self.index, len(self.isomers)))) self.printSummary(level=logging.INFO) # Calculate the rate coefficients self.initialize(Tmin, Tmax, Pmin, Pmax, maximumGrainSize, minimumGrainCount, activeJRotor, activeKRotor, rmgmode) K = self.calculateRateCoefficients(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 netReaction = None for r in self.netReactions: if r.hasTemplate(configurations[j], configurations[i]): netReaction = r # If net reaction does not already exist, make a new one if netReaction is None: netReaction = PDepReaction(reactants=configurations[j], products=configurations[i], network=self, kinetics=None) netReaction = reactionModel.makeNewPDepReaction( netReaction) self.netReactions.append(netReaction) # 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 reactionModel.core.species for s in netReaction.reactants ]) and all([ s in reactionModel.core.species for s in netReaction.products ]): reactionModel.addReactionToCore(netReaction) else: reactionModel.addReactionToEdge(netReaction) # Set/update the net reaction kinetics using interpolation model Tdata = job.Tlist.value_si Pdata = job.Plist.value_si kdata = K[:, :, i, j].copy() order = len(netReaction.reactants) kdata *= 1e6**(order - 1) kunits = { 1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)' }[order] netReaction.kinetics = job.fitInterpolationModel( 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.pathReactions: if pathReaction.isIsomerization(): # 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/dissocation # reactions, but the effect is generally not too large) continue if pathReaction.reactants == netReaction.reactants and pathReaction.products == netReaction.products: kinf = pathReaction.kinetics.getRateCoefficient( 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(netReaction, 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 == netReaction.reactants and pathReaction.reactants == netReaction.products: kinf = pathReaction.kinetics.getRateCoefficient( Tlist[t]) / pathReaction.getEquilibriumConstant( 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(netReaction, 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 test_reaction(self): """ Test loading a reaction from input file-like kew word arguments """ species(label='methoxy', structure=SMILES('C[O]'), E0=(9.44, 'kcal/mol'), modes=[ HarmonicOscillator(frequencies=( [758, 960, 1106, 1393, 1403, 1518, 2940, 3019, 3065], 'cm^-1')), NonlinearRotor(rotationalConstant=([0.916, 0.921, 5.251], "cm^-1"), symmetry=3, quantum=False), IdealGasTranslation(mass=(31.01843, "g/mol")) ], spinMultiplicity=2, opticalIsomers=1, molecularWeight=(31.01843, 'amu'), collisionModel=TransportData(sigma=(3.69e-10, 'm'), epsilon=(4.0, 'kJ/mol')), energyTransferModel=SingleExponentialDown(alpha0=(0.956, 'kJ/mol'), T0=(300, 'K'), n=0.95)) species(label='formaldehyde', E0=(28.69, 'kcal/mol'), molecularWeight=(30.0106, "g/mol"), collisionModel=TransportData(sigma=(3.69e-10, 'm'), epsilon=(4.0, 'kJ/mol')), energyTransferModel=SingleExponentialDown(alpha0=(0.956, 'kJ/mol'), T0=(300, 'K'), n=0.95), spinMultiplicity=1, opticalIsomers=1, modes=[ HarmonicOscillator( frequencies=([1180, 1261, 1529, 1764, 2931, 2999], 'cm^-1')), NonlinearRotor(rotationalConstant=([ 1.15498821005263, 1.3156969584727, 9.45570474524524 ], "cm^-1"), symmetry=2, quantum=False), IdealGasTranslation(mass=(30.0106, "g/mol")) ]) species(label='H', E0=(0.000, 'kcal/mol'), molecularWeight=(1.00783, "g/mol"), collisionModel=TransportData(sigma=(3.69e-10, 'm'), epsilon=(4.0, 'kJ/mol')), energyTransferModel=SingleExponentialDown(alpha0=(0.956, 'kJ/mol'), T0=(300, 'K'), n=0.95), modes=[IdealGasTranslation(mass=(1.00783, "g/mol"))], spinMultiplicity=2, opticalIsomers=1) transitionState( label='TS3', E0=(34.1, 'kcal/mol'), spinMultiplicity=2, opticalIsomers=1, frequency=(-967, 'cm^-1'), modes=[ HarmonicOscillator(frequencies=( [466, 581, 1169, 1242, 1499, 1659, 2933, 3000], 'cm^-1')), NonlinearRotor(rotationalConstant=([0.970, 1.029, 3.717], "cm^-1"), symmetry=1, quantum=False), IdealGasTranslation(mass=(31.01843, "g/mol")) ]) reactants = ['formaldehyde', 'H'] products = ['methoxy'] tunneling = 'Eckart' rxn = reaction('CH2O+H=Methoxy', reactants, products, 'TS3', tunneling=tunneling) self.assertEqual(rxn.label, 'CH2O+H=Methoxy') self.assertEqual(len(rxn.reactants), 2) self.assertEqual(len(rxn.products), 1) self.assertAlmostEqual(rxn.reactants[0].conformer.E0.value_si, 0) self.assertAlmostEqual(rxn.reactants[1].conformer.E0.value_si, 120038.96) self.assertAlmostEqual(rxn.products[0].conformer.E0.value_si, 39496.96) self.assertAlmostEqual(rxn.transition_state.conformer.E0.value_si, 142674.4) self.assertAlmostEqual(rxn.transition_state.frequency.value_si, -967.0) self.assertIsInstance(rxn.transition_state.tunneling, Eckart)
def setUp(self): """ A function run before each unit test in this class. """ self.nC4H10O = Species( label='n-C4H10O', conformer=Conformer( E0=(-317.807, 'kJ/mol'), modes=[ IdealGasTranslation(mass=(74.07, "g/mol")), NonlinearRotor(inertia=([41.5091, 215.751, 233.258], "amu*angstrom^2"), symmetry=1), HarmonicOscillator(frequencies=([ 240.915, 341.933, 500.066, 728.41, 809.987, 833.93, 926.308, 948.571, 1009.3, 1031.46, 1076, 1118.4, 1184.66, 1251.36, 1314.36, 1321.42, 1381.17, 1396.5, 1400.54, 1448.08, 1480.18, 1485.34, 1492.24, 1494.99, 1586.16, 2949.01, 2963.03, 2986.19, 2988.1, 2995.27, 3026.03, 3049.05, 3053.47, 3054.83, 3778.88 ], "cm^-1")), HinderedRotor(inertia=(0.854054, "amu*angstrom^2"), symmetry=1, fourier=([[ 0.25183, -1.37378, -2.8379, 0.0305112, 0.0028088 ], [ 0.458307, 0.542121, -0.599366, -0.00283925, 0.0398529 ]], "kJ/mol")), HinderedRotor( inertia=(8.79408, "amu*angstrom^2"), symmetry=1, fourier=([[ 0.26871, -0.59533, -8.15002, -0.294325, -0.145357 ], [1.1884, 0.99479, -0.940416, -0.186538, 0.0309834]], "kJ/mol")), HinderedRotor(inertia=(7.88153, "amu*angstrom^2"), symmetry=1, fourier=([[ -4.67373, 2.03735, -6.25993, -0.27325, -0.048748 ], [ -0.982845, 1.76637, -1.57619, 0.474364, -0.000681718 ]], "kJ/mol")), HinderedRotor(inertia=(2.81525, "amu*angstrom^2"), symmetry=3, barrier=(2.96807, "kcal/mol")), ], spin_multiplicity=1, optical_isomers=1, ), molecular_weight=(74.07, "g/mol"), transport_data=TransportData(sigma=(5.94, 'angstrom'), epsilon=(559, 'K')), energy_transfer_model=SingleExponentialDown( alpha0=(447.5 * 0.011962, "kJ/mol"), T0=(300, "K"), n=0.85), ) self.nC4H8 = Species( label='n-C4H8', conformer=Conformer( E0=(-17.8832, 'kJ/mol'), modes=[ IdealGasTranslation(mass=(56.06, "g/mol")), NonlinearRotor(inertia=([22.2748, 122.4, 125.198], "amu*angstrom^2"), symmetry=1), HarmonicOscillator(frequencies=([ 308.537, 418.67, 636.246, 788.665, 848.906, 936.762, 979.97, 1009.48, 1024.22, 1082.96, 1186.38, 1277.55, 1307.65, 1332.87, 1396.67, 1439.09, 1469.71, 1484.45, 1493.19, 1691.49, 2972.12, 2994.31, 3018.48, 3056.87, 3062.76, 3079.38, 3093.54, 3174.52 ], "cm^-1")), HinderedRotor(inertia=(5.28338, "amu*angstrom^2"), symmetry=1, fourier=([[ -0.579364, -0.28241, -4.46469, 0.143368, 0.126756 ], [ 1.01804, -0.494628, -0.00318651, -0.245289, 0.193728 ]], "kJ/mol")), HinderedRotor( inertia=(2.60818, "amu*angstrom^2"), symmetry=3, fourier=([[ 0.0400372, 0.0301986, -6.4787, -0.0248675, -0.0324753 ], [0.0312541, 0.0538, -0.493785, 0.0965968, 0.125292]], "kJ/mol")), ], spin_multiplicity=1, optical_isomers=1, ), ) self.H2O = Species( label='H2O', conformer=Conformer( E0=(-269.598, 'kJ/mol'), modes=[ IdealGasTranslation(mass=(18.01, "g/mol")), NonlinearRotor(inertia=([0.630578, 1.15529, 1.78586], "amu*angstrom^2"), symmetry=2), HarmonicOscillator( frequencies=([1622.09, 3771.85, 3867.85], "cm^-1")), ], spin_multiplicity=1, optical_isomers=1, ), ) self.configuration = Configuration(self.nC4H8, self.H2O)