Example #1
0
 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
Example #2
0
    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
Example #3
0
    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