示例#1
0
文件: pdep.py 项目: jbarlow3/RMG-Py
 def fitInterpolationModels(self):
         
     configurations = []
     configurations.extend(self.network.isomers)
     configurations.extend(self.network.reactants)
     configurations.extend(self.network.products)
     
     self.network.netReactions = []
     
     Nreac = self.network.Nisom + self.network.Nreac
     Nprod = Nreac + self.network.Nprod
     
     Tmin = self.Tmin.value_si
     Tmax = self.Tmax.value_si
     Tdata = self.Tlist.value_si
     Pmin = self.Pmin.value_si
     Pmax = self.Pmax.value_si
     Pdata = self.Plist.value_si
     
     for prod in range(Nprod):
         for reac in range(Nreac):
             if reac == prod: continue
             reaction = Reaction(
                 reactants = configurations[reac].species,
                 products = configurations[prod].species,
             )
             
             kdata = self.K[:,:,prod,reac].copy()
             order = len(reaction.reactants)
             kdata *= 1e6 ** (order-1)
             kunits = {1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)'}[order]
             
             reaction.kinetics = self.fitInterpolationModel(Tdata, Pdata, kdata, kunits)
             
             self.network.netReactions.append(reaction)
示例#2
0
    def fit_interpolation_models(self):
        """Fit all pressure dependent rates with interpolation models"""
        configurations = []
        configurations.extend(self.network.isomers)
        configurations.extend(self.network.reactants)
        configurations.extend(self.network.products)

        self.network.net_reactions = []

        n_reac = self.network.n_isom + self.network.n_reac
        n_prod = n_reac + self.network.n_prod

        Tdata, Pdata = self.Tlist.value_si, self.Plist.value_si

        for prod in range(n_prod):
            for reac in range(n_reac):
                if reac == prod:
                    continue
                reaction = Reaction(reactants=configurations[reac].species,
                                    products=configurations[prod].species)

                kdata = self.K[:, :, prod, reac].copy()
                order = len(reaction.reactants)
                kdata *= 1e6 ** (order - 1)
                k_units = {1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)'}[order]
                logging.debug('Fitting master eqn data to kinetics for reaction {}.'.format(reaction))
                reaction.kinetics = self.fit_interpolation_model(Tdata, Pdata, kdata, k_units)

                self.network.net_reactions.append(reaction)
示例#3
0
 def fitInterpolationModels(self):
         
     configurations = []
     configurations.extend(self.network.isomers)
     configurations.extend(self.network.reactants)
     configurations.extend(self.network.products)
     
     self.network.netReactions = []
     
     Nreac = self.network.Nisom + self.network.Nreac
     Nprod = Nreac + self.network.Nprod
     
     Tmin = self.Tmin.value_si
     Tmax = self.Tmax.value_si
     Tdata = self.Tlist.value_si
     Pmin = self.Pmin.value_si
     Pmax = self.Pmax.value_si
     Pdata = self.Plist.value_si
     
     for prod in range(Nprod):
         for reac in range(Nreac):
             if reac == prod: continue
             reaction = Reaction(
                 reactants = configurations[reac].species,
                 products = configurations[prod].species,
             )
             
             kdata = self.K[:,:,prod,reac].copy()
             order = len(reaction.reactants)
             kdata *= 1e6 ** (order-1)
             kunits = {1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)'}[order]
             
             reaction.kinetics = self.fitInterpolationModel(Tdata, Pdata, kdata, kunits)
             
             self.network.netReactions.append(reaction)
示例#4
0
    def compute(self):
        """
        Compute the pressure-dependent rate coefficients :math:`k(T,P)` for
        the loaded MEASURE calculation.
        """
        
        # Only proceed if the input network is valid
        if self.network is None or self.network.errorString != '':
            raise PDepError('Attempted to run MEASURE calculation with invalid input.')
    
        Nisom = len(self.network.isomers)
        Nreac = len(self.network.reactants)
        Nprod = len(self.network.products)

        network = self.network   
        Tmin = self.Tmin.value_si
        Tmax = self.Tmax.value_si
        Tlist = self.Tlist.value_si
        Pmin = self.Pmin.value_si
        Pmax = self.Pmax.value_si
        Plist = self.Plist.value_si
        method = self.method
        model = self.model
        
        # Calculate the rate coefficients
        K = network.calculateRateCoefficients(Tlist, Plist, method, grainCount=self.grainCount, grainSize=self.grainSize.value_si)

        # Fit interpolation model
        from rmgpy.reaction import Reaction
        from rmgpy.measure.reaction import fitInterpolationModel
        if model[0] != '':
            logging.info('Fitting {0} interpolation models...'.format(model[0]))
        configurations = []
        configurations.extend([[isom] for isom in network.isomers])
        configurations.extend([reactants for reactants in network.reactants])
        configurations.extend([products for products in network.products])
        for i in range(Nisom+Nreac+Nprod):
            for j in range(Nisom+Nreac):
                if i != j:
                    # Check that we have nonzero k(T,P) values
                    if (numpy.any(K[:,:,i,j]) and not numpy.all(K[:,:,i,j])):
                        raise NetworkError('Zero rate coefficient encountered while updating network {0}.'.format(network))

                    # Make a new net reaction
                    netReaction = Reaction(
                        reactants=configurations[j],
                        products=configurations[i],
                        kinetics=None,
                        reversible=(i<Nisom+Nreac),
                    )
                    network.netReactions.append(netReaction)
                    
                    # Set/update the net reaction kinetics using interpolation model
                    netReaction.kinetics = fitInterpolationModel(netReaction, Tlist, Plist,
                        K[:,:,i,j],
                        model, Tmin, Tmax, Pmin, Pmax, errorCheck=True)
        logging.info('')
示例#5
0
文件: main.py 项目: ajalan/RMG-Py
    def compute(self):
        """
        Compute the pressure-dependent rate coefficients :math:`k(T,P)` for
        the loaded MEASURE calculation.
        """
        
        # Only proceed if the input network is valid
        if self.network is None or self.network.errorString != '':
            raise PDepError('Attempted to run MEASURE calculation with invalid input.')
    
        Nisom = len(self.network.isomers)
        Nreac = len(self.network.reactants)
        Nprod = len(self.network.products)

        network = self.network   
        Tmin = self.Tmin.value
        Tmax = self.Tmax.value
        Tlist = self.Tlist.values
        Pmin = self.Pmin.value
        Pmax = self.Pmax.value
        Plist = self.Plist.values
        method = self.method
        model = self.model
        
        # Calculate the rate coefficients
        K = network.calculateRateCoefficients(Tlist, Plist, method, grainCount=self.grainCount, grainSize=self.grainSize.value)

        # Fit interpolation model
        from rmgpy.reaction import Reaction
        from rmgpy.measure.reaction import fitInterpolationModel
        if model[0] != '':
            logging.info('Fitting {0} interpolation models...'.format(model[0]))
        configurations = []
        configurations.extend([[isom] for isom in network.isomers])
        configurations.extend([reactants for reactants in network.reactants])
        configurations.extend([products for products in network.products])
        for i in range(Nisom+Nreac+Nprod):
            for j in range(Nisom+Nreac):
                if i != j:
                    # Check that we have nonzero k(T,P) values
                    if (numpy.any(K[:,:,i,j]) and not numpy.all(K[:,:,i,j])):
                        raise NetworkError('Zero rate coefficient encountered while updating network {0}.'.format(network))

                    # Make a new net reaction
                    netReaction = Reaction(
                        reactants=configurations[j],
                        products=configurations[i],
                        kinetics=None,
                        reversible=(i<Nisom+Nreac),
                    )
                    network.netReactions.append(netReaction)
                    
                    # Set/update the net reaction kinetics using interpolation model
                    netReaction.kinetics = fitInterpolationModel(netReaction, Tlist, Plist,
                        K[:,:,i,j],
                        model, Tmin, Tmax, Pmin, Pmax, errorCheck=True)
        logging.info('')
def getKineticsDepository(FullDatabase, family, depositoryLabel):
    """
    Retrieve dictionaries of exact kinetics from NIST depository and approximated kinetics 
    for those same reactions from RMG.  
    Note: does NOT average up the database or create any rate rules from training data.  
    If that is desired it must be done prior to entering this function.
    """
    
    depository = None
    for tempDepository in family.depositories:
        if re.search(re.escape(depositoryLabel), tempDepository.label):
            depository=tempDepository
            break
    else:
        print 'Depository {} not found in {} family.'.format(depositoryLabel, family.label)
        return
            
    
    exactKinetics={}
    approxKinetics={}
    
    for key, entry in depository.entries.iteritems():
        try:
            reaction=entry.item
            template=family.getReactionTemplate(reaction)
            exactKinetics[key]=entry.data
            approxKinetics[key]=family.rules.estimateKinetics(template)[0]
        except UndeterminableKineticsError:
            # See if the reaction was written in the reverse direction
            reaction = Reaction(reactants = copy.deepcopy(entry.item.products),
                                products = copy.deepcopy(entry.item.reactants),
                                kinetics = copy.deepcopy(entry.data)
                                )
            
            template=family.getReactionTemplate(reaction)

            # Getting thermo data erases the atomLabels, so do this after finding the template
            # But we need it for setting the reverse kinetics
            for spec in reaction.reactants + reaction.products:
                spec.getThermoData()

            reverseKinetics = reaction.generateReverseRateCoefficient()
            reaction.kinetics = reverseKinetics
            
            exactKinetics[key]=reaction.kinetics
            approxKinetics[key]=family.rules.estimateKinetics(template)[0]

    return exactKinetics, approxKinetics
示例#7
0
def getKineticsDepository(FullDatabase, family, depositoryLabel):
    """
    Retrieve dictionaries of exact kinetics from NIST depository and approximated kinetics 
    for those same reactions from RMG.  
    Note: does NOT average up the database or create any rate rules from training data.  
    If that is desired it must be done prior to entering this function.
    """

    depository = None
    for tempDepository in family.depositories:
        if re.search(re.escape(depositoryLabel), tempDepository.label):
            depository = tempDepository
            break
    else:
        print 'Depository {} not found in {} family.'.format(
            depositoryLabel, family.label)
        return

    exactKinetics = {}
    approxKinetics = {}

    for key, entry in depository.entries.iteritems():
        try:
            reaction = entry.item
            template = family.getReactionTemplate(reaction)
            exactKinetics[key] = entry.data
            approxKinetics[key] = family.rules.estimateKinetics(template)[0]
        except UndeterminableKineticsError:
            # See if the reaction was written in the reverse direction
            reaction = Reaction(reactants=copy.deepcopy(entry.item.products),
                                products=copy.deepcopy(entry.item.reactants),
                                kinetics=copy.deepcopy(entry.data))

            template = family.getReactionTemplate(reaction)

            # Getting thermo data erases the atomLabels, so do this after finding the template
            # But we need it for setting the reverse kinetics
            for spec in reaction.reactants + reaction.products:
                spec.getThermoData()

            reverseKinetics = reaction.generateReverseRateCoefficient()
            reaction.kinetics = reverseKinetics

            exactKinetics[key] = reaction.kinetics
            approxKinetics[key] = family.rules.estimateKinetics(template)[0]

    return exactKinetics, approxKinetics
示例#8
0
def get_reaction_for_entry(entry, database):
    """
    Return a Reaction object for a given entry that uses Species instead of
    Molecules (so that we can compute the reaction thermo).
    """
    reaction = Reaction(reactants=[], products=[])
    for reactant in entry.item.reactants:
        reactant.generate_resonance_structures()
        reactant.thermo = generate_thermo_data(reactant, database)
        reaction.reactants.append(reactant)
    for product in entry.item.products:
        product.generate_resonance_structures()
        product.thermo = generate_thermo_data(product, database)
        reaction.products.append(product)

    reaction.kinetics = entry.data
    reaction.degeneracy = entry.item.degeneracy

    return reaction
示例#9
0
def kinetics(label, kinetics):
    reactants, products = label.split('_')
    reactants = reactants.split('+')
    products = products.split('+')

    reaction = Reaction(reactants=[], products=[], reversible=True)
    
    for in_list, out_list in [(reactants, reaction.reactants), (products, reaction.products)]:
        for i, smiles in enumerate(in_list):
            if smiles not in species_dict:
                species = Species().fromSMILES(smiles)
                species_dict[smiles] = species
                species.label = '{0}({1})'.format(species.toChemkin(), len(species_dict))
            out_list.append(species_dict[smiles])
    
    reaction.kinetics = kinetics
    print repr(reaction)
    display(reaction)
    
    addReactionToKineticsLibrary(reaction)
示例#10
0
    def get_rmg_reaction(self):
        """
        Convert PrIMeReaction to an RMG reaction.
        """
        if not all((self.reactants, self.products, self.kinetics)):
            raise ConversionError(
                'Missing reactants, products, or kinetics for reaction {}.'.
                format(self.prime_id))
        if not self._direction_matched:
            self.match_direction()

        reaction = Reaction()
        if self.rmg_reactants is None or self.rmg_products is None:
            reaction.reactants = [s.get_rmg_species() for s in self.reactants]
            reaction.products = [s.get_rmg_species() for s in self.products]
        else:
            reaction.reactants = self.rmg_reactants
            reaction.products = self.rmg_products
        reaction.kinetics = self.kinetics.expression

        return reaction
示例#11
0
def getReactionForEntry(entry, database):
    """
    Return a Reaction object for a given entry that uses Species instead of
    Molecules (so that we can compute the reaction thermo).
    """
    reaction = Reaction(reactants=[], products=[])
    for molecule in entry.item.reactants:
        molecule.makeHydrogensExplicit()
        reactant = Species(molecule=[molecule], label=molecule.toSMILES())
        reactant.generate_resonance_structures()
        reactant.thermo = generateThermoData(reactant, database)
        reaction.reactants.append(reactant)
    for molecule in entry.item.products:
        molecule.makeHydrogensExplicit()
        product = Species(molecule=[molecule], label=molecule.toSMILES())
        product.generate_resonance_structures()
        product.thermo = generateThermoData(product, database)
        reaction.products.append(product)

    reaction.kinetics = entry.data
    reaction.degeneracy = entry.item.degeneracy

    return reaction
示例#12
0
def getReactionForEntry(entry, database):
    """
    Return a Reaction object for a given entry that uses Species instead of
    Molecules (so that we can compute the reaction thermo).
    """
    reaction = Reaction(reactants=[], products=[])
    for molecule in entry.item.reactants:
        molecule.makeHydrogensExplicit()
        reactant = Species(molecule=[molecule], label=molecule.toSMILES())
        reactant.generateResonanceIsomers()
        reactant.thermo = generateThermoData(reactant, database)
        reaction.reactants.append(reactant)
    for molecule in entry.item.products:
        molecule.makeHydrogensExplicit()
        product = Species(molecule=[molecule], label=molecule.toSMILES())
        product.generateResonanceIsomers()
        product.thermo = generateThermoData(product, database)
        reaction.products.append(product)
    
    reaction.kinetics = entry.data
    reaction.degeneracy = entry.item.degeneracy
    
    return reaction
示例#13
0
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
示例#14
0
    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
示例#15
0
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.model = ['chebyshev', int(model[1]), int(model[2])]
    elif model[0].lower() == 'pdeparrhenius':
        job.model = ['pdeparrhenius']
    
    # Read grain size or number of grains
    job.grainCount = 0
    job.grainSize = Quantity(0.0, "J/mol")
    for i in range(2):
        data = readMeaningfulLine(f).split()
        if data[0].lower() == 'numgrains':
            job.grainCount = int(data[1])
        elif data[0].lower() == 'grainsize':
            job.grainSize = Quantity(float(data[2]), data[1])

    # 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.lennardJones = LennardJones(
        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()
        
        # 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),
        )
        
        # 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.lennardJones = LennardJones(
            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,
            ))
            
        # 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
示例#16
0
    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
示例#17
0
    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