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 getForwardReactionForFamilyEntry(self, entry, family, thermoDatabase): """ For a given `entry` for a reaction of the given reaction `family` (the string label of the family), return the reaction with kinetics and degeneracy for the "forward" direction as defined by the reaction family. For families that are their own reverse, the direction the kinetics is given in will be preserved. If the entry contains functional groups for the reactants, assume that it is given in the forward direction and do nothing. Returns the reaction in the direction consistent with the reaction family template, and the matching template. Note that the returned reaction will have its kinetics and degeneracy set appropriately. In order to reverse the reactions that are given in the reverse of the direction the family is defined, we need to compute the thermodynamics of the reactants and products. For this reason you must also pass the `thermoDatabase` to use to generate the thermo data. """ def generateThermoData(species, thermoDatabase): thermoData = [thermoDatabase.getThermoData(species)] thermoData.sort(key=lambda x: x.getEnthalpy(298)) return thermoData[0] def matchSpeciesToMolecules(species, molecules): if len(species) == len(molecules) == 1: return species[0].isIsomorphic(molecules[0]) elif len(species) == len(molecules) == 2: if species[0].isIsomorphic( molecules[0]) and species[1].isIsomorphic( molecules[1]): return True elif species[0].isIsomorphic( molecules[1]) and species[1].isIsomorphic( molecules[0]): return True return False reaction = None template = None # Get the indicated reaction family try: groups = self.families[family].groups except KeyError: raise ValueError( 'Invalid value "{0}" for family parameter.'.format(family)) if all([(isinstance(reactant, Group) or isinstance(reactant, LogicNode)) for reactant in entry.item.reactants]): # The entry is a rate rule, containing functional groups only # By convention, these are always given in the forward direction and # have kinetics defined on a per-site basis reaction = Reaction( reactants=entry.item.reactants[:], products=[], kinetics=entry.data, degeneracy=1, ) template = [ groups.entries[label] for label in entry.label.split(';') ] elif (all([ isinstance(reactant, Molecule) for reactant in entry.item.reactants ]) and all( [isinstance(product, Molecule) for product in entry.item.products])): # The entry is a real reaction, containing molecules # These could be defined for either the forward or reverse direction # and could have a reaction-path degeneracy reaction = Reaction(reactants=[], products=[]) for molecule in entry.item.reactants: reactant = Species(molecule=[molecule]) reactant.generateResonanceIsomers() reactant.thermo = generateThermoData(reactant, thermoDatabase) reaction.reactants.append(reactant) for molecule in entry.item.products: product = Species(molecule=[molecule]) product.generateResonanceIsomers() product.thermo = generateThermoData(product, thermoDatabase) reaction.products.append(product) # Generate all possible reactions involving the reactant species generatedReactions = self.generateReactionsFromFamilies( [reactant.molecule for reactant in reaction.reactants], [], only_families=[family]) # Remove from that set any reactions that don't produce the desired reactants and products forward = [] reverse = [] for rxn in generatedReactions: if matchSpeciesToMolecules( reaction.reactants, rxn.reactants) and matchSpeciesToMolecules( reaction.products, rxn.products): forward.append(rxn) if matchSpeciesToMolecules( reaction.reactants, rxn.products) and matchSpeciesToMolecules( reaction.products, rxn.reactants): reverse.append(rxn) # We should now know whether the reaction is given in the forward or # reverse direction if len(forward) == 1 and len(reverse) == 0: # The reaction is in the forward direction, so use as-is reaction = forward[0] template = reaction.template # Don't forget to overwrite the estimated kinetics from the database with the kinetics for this entry reaction.kinetics = entry.data elif len(reverse) == 1 and len(forward) == 0: # The reaction is in the reverse direction # First fit Arrhenius kinetics in that direction Tdata = 1000.0 / numpy.arange(0.5, 3.301, 0.1, numpy.float64) kdata = numpy.zeros_like(Tdata) for i in range(Tdata.shape[0]): kdata[i] = entry.data.getRateCoefficient( Tdata[i]) / reaction.getEquilibriumConstant(Tdata[i]) kunits = 'm^3/(mol*s)' if len( reverse[0].reactants) == 2 else 's^-1' kinetics = Arrhenius().fitToData(Tdata, kdata, kunits, T0=1.0) kinetics.Tmin = entry.data.Tmin kinetics.Tmax = entry.data.Tmax kinetics.Pmin = entry.data.Pmin kinetics.Pmax = entry.data.Pmax # Now flip the direction reaction = reverse[0] reaction.kinetics = kinetics template = reaction.template elif len(reverse) > 0 and len(forward) > 0: print 'FAIL: Multiple reactions found for {0!r}.'.format( entry.label) elif len(reverse) == 0 and len(forward) == 0: print 'FAIL: No reactions found for "%s".' % (entry.label) else: print 'FAIL: Unable to estimate kinetics for {0!r}.'.format( entry.label) assert reaction is not None assert template is not None return reaction, template
def get_forward_reaction_for_family_entry(self, entry, family, thermo_database): """ For a given `entry` for a reaction of the given reaction `family` (the string label of the family), return the reaction with kinetics and degeneracy for the "forward" direction as defined by the reaction family. For families that are their own reverse, the direction the kinetics is given in will be preserved. If the entry contains functional groups for the reactants, assume that it is given in the forward direction and do nothing. Returns the reaction in the direction consistent with the reaction family template, and the matching template. Note that the returned reaction will have its kinetics and degeneracy set appropriately. In order to reverse the reactions that are given in the reverse of the direction the family is defined, we need to compute the thermodynamics of the reactants and products. For this reason you must also pass the `thermo_database` to use to generate the thermo data. """ reaction = None template = None # Get the indicated reaction family try: groups = self.families[family].groups except KeyError: raise ValueError( 'Invalid value "{0}" for family parameter.'.format(family)) if all([(isinstance(reactant, Group) or isinstance(reactant, LogicNode)) for reactant in entry.item.reactants]): # The entry is a rate rule, containing functional groups only # By convention, these are always given in the forward direction and # have kinetics defined on a per-site basis reaction = Reaction( reactants=entry.item.reactants[:], products=[], specific_collider=entry.item.specific_collider, kinetics=entry.data, degeneracy=1, ) template = [ groups.entries[label] for label in entry.label.split(';') ] elif (all([ isinstance(reactant, Molecule) for reactant in entry.item.reactants ]) and all( [isinstance(product, Molecule) for product in entry.item.products])): # The entry is a real reaction, containing molecules # These could be defined for either the forward or reverse direction # and could have a reaction-path degeneracy reaction = Reaction(reactants=[], products=[]) for molecule in entry.item.reactants: reactant = Species(molecule=[molecule]) reactant.generate_resonance_structures() reactant.thermo = thermo_database.get_thermo_data(reactant) reaction.reactants.append(reactant) for molecule in entry.item.products: product = Species(molecule=[molecule]) product.generate_resonance_structures() product.thermo = thermo_database.get_thermo_data(product) reaction.products.append(product) # Generate all possible reactions involving the reactant species generated_reactions = self.generate_reactions_from_families( [reactant.molecule for reactant in reaction.reactants], [], only_families=[family]) # Remove from that set any reactions that don't produce the desired reactants and products forward = [] reverse = [] for rxn in generated_reactions: if (same_species_lists(reaction.reactants, rxn.reactants) and same_species_lists(reaction.products, rxn.products)): forward.append(rxn) if (same_species_lists(reaction.reactants, rxn.products) and same_species_lists(reaction.products, rxn.reactants)): reverse.append(rxn) # We should now know whether the reaction is given in the forward or # reverse direction if len(forward) == 1 and len(reverse) == 0: # The reaction is in the forward direction, so use as-is reaction = forward[0] template = reaction.template # Don't forget to overwrite the estimated kinetics from the database with the kinetics for this entry reaction.kinetics = entry.data elif len(reverse) == 1 and len(forward) == 0: # The reaction is in the reverse direction # First fit Arrhenius kinetics in that direction T_data = 1000.0 / np.arange(0.5, 3.301, 0.1, np.float64) k_data = np.zeros_like(T_data) for i in range(T_data.shape[0]): k_data[i] = entry.data.get_rate_coefficient( T_data[i]) / reaction.get_equilibrium_constant( T_data[i]) try: k_units = ('s^-1', 'm^3/(mol*s)', 'm^6/(mol^2*s)')[len(reverse[0].reactants) - 1] except IndexError: raise NotImplementedError( 'Cannot reverse reactions with {} products'.format( len(reverse[0].reactants))) kinetics = Arrhenius().fit_to_data(T_data, k_data, k_units, T0=1.0) kinetics.Tmin = entry.data.Tmin kinetics.Tmax = entry.data.Tmax kinetics.Pmin = entry.data.Pmin kinetics.Pmax = entry.data.Pmax # Now flip the direction reaction = reverse[0] reaction.kinetics = kinetics template = reaction.template elif len(reverse) > 0 and len(forward) > 0: print('FAIL: Multiple reactions found for {0!r}.'.format( entry.label)) elif len(reverse) == 0 and len(forward) == 0: print('FAIL: No reactions found for "%s".' % (entry.label)) else: print('FAIL: Unable to estimate kinetics for {0!r}.'.format( entry.label)) assert reaction is not None assert template is not None return reaction, template