def get_leak_coefficient(self, T, P): """ Return the pressure-dependent rate coefficient :math:`k(T,P)` describing the total rate of "leak" from this network. This is defined as the sum of the :math:`k(T,P)` values for all net reactions to nonexplored unimolecular isomers. """ k = 0.0 if len(self.net_reactions) == 0 and len(self.path_reactions) == 1: # The network is of the form A + B -> C* (with C* nonincluded) # For this special case we use the high-pressure limit k(T) to # ensure that we're estimating the total leak flux rxn = self.path_reactions[0] if rxn.kinetics is None: if rxn.reverse.kinetics is not None: rxn = rxn.reverse else: raise PressureDependenceError( 'Path reaction {0} with no high-pressure-limit kinetics encountered ' 'in PDepNetwork #{1:d} while evaluating leak flux.'. format(rxn, self.index)) if rxn.products is self.source: k = rxn.get_rate_coefficient( T, P) / rxn.get_equilibrium_constant(T) else: k = rxn.get_rate_coefficient(T, P) else: # The network has at least one included isomer, so we can calculate # the leak flux normally for rxn in self.net_reactions: if len(rxn.products ) == 1 and rxn.products[0] not in self.explored: k += rxn.get_rate_coefficient(T, P) return k
def fit_interpolation_model(self, Tdata, Pdata, kdata, k_units): """Fit an interpolation model to a pressure dependent rate""" Tmin = self.Tmin.value_si Tmax = self.Tmax.value_si Pmin = self.Pmin.value_si Pmax = self.Pmax.value_si model = self.interpolation_model[0].lower() if model == 'chebyshev': kinetics = Chebyshev().fit_to_data(Tdata, Pdata, kdata, k_units, self.interpolation_model[1], self.interpolation_model[2], Tmin, Tmax, Pmin, Pmax) elif model == 'pdeparrhenius': kinetics = PDepArrhenius().fit_to_data(Tdata, Pdata, kdata, k_units) else: raise PressureDependenceError('Invalid interpolation model {0!r}.'.format(self.interpolation_model[0])) return kinetics
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