def readKineticsEntry(entry, speciesDict, energyUnits, moleculeUnits): """ Read a kinetics `entry` for a single reaction as loaded from a Chemkin file. The associated mapping of labels to species `speciesDict` should also be provided. Returns a :class:`Reaction` object with the reaction and its associated kinetics. """ if energyUnits.lower() in ['kcal/mole', 'kcal/mol']: energyFactor = 1.0 elif energyUnits.lower() in ['cal/mole', 'cal/mol']: energyFactor = 0.001 else: raise ChemkinError('Unexpected energy units "{0}" in reaction block.'.format(energyUnits)) if moleculeUnits.lower() in ['moles']: moleculeFactor = 1.0 else: raise ChemkinError('Unexpected molecule units "{0}" in reaction block.'.format(energyUnits)) lines = entry.strip().splitlines() # The first line contains the reaction equation and a set of modified Arrhenius parameters tokens = lines[0].split() A = float(tokens[-3]) n = float(tokens[-2]) Ea = float(tokens[-1]) reaction = ''.join(tokens[:-3]) thirdBody = False # Split the reaction equation into reactants and products reversible = True reactants, products = reaction.split('=') if '=>' in reaction: products = products[1:] reversible = False if '(+M)' in reactants: reactants = reactants.replace('(+M)','') if '(+m)' in reactants: reactants = reactants.replace('(+m)','') if '(+M)' in products: products = products.replace('(+M)','') if '(+m)' in products: products = products.replace('(+m)','') # Create a new Reaction object for this reaction reaction = Reaction(reactants=[], products=[], reversible=reversible) # Convert the reactants and products to Species objects using the speciesDict for reactant in reactants.split('+'): reactant = reactant.strip() stoichiometry = 1 if reactant[0].isdigit(): # This allows for reactions to be of the form 2A=B+C instead of A+A=B+C # The implementation below assumes an integer between 0 and 9, inclusive stoichiometry = int(reactant[0]) reactant = reactant[1:] if reactant == 'M' or reactant == 'm': thirdBody = True elif reactant not in speciesDict: raise ChemkinError('Unexpected reactant "{0}" in reaction {1}.'.format(reactant, reaction)) else: for i in range(stoichiometry): reaction.reactants.append(speciesDict[reactant]) for product in products.split('+'): product = product.strip() stoichiometry = 1 if product[0].isdigit(): # This allows for reactions to be of the form A+B=2C instead of A+B=C+C # The implementation below assumes an integer between 0 and 9, inclusive stoichiometry = int(product[0]) product = product[1:] if product.upper() == 'M' or product == 'm': pass elif product not in speciesDict: raise ChemkinError('Unexpected product "{0}" in reaction {1}.'.format(product, reaction)) else: for i in range(stoichiometry): reaction.products.append(speciesDict[product]) # Determine the appropriate units for k(T) and k(T,P) based on the number of reactants # This assumes elementary kinetics for all reactions if len(reaction.reactants) + (1 if thirdBody else 0) == 3: kunits = "cm^6/(mol^2*s)" klow_units = "cm^9/(mol^3*s)" elif len(reaction.reactants) + (1 if thirdBody else 0) == 2: kunits = "cm^3/(mol*s)" klow_units = "cm^6/(mol^2*s)" elif len(reaction.reactants) + (1 if thirdBody else 0) == 1: kunits = "s^-1" klow_units = "cm^3/(mol*s)" else: raise ChemkinError('Invalid number of reactant species for reaction {0}.'.format(reaction)) # The rest of the first line contains the high-P limit Arrhenius parameters (if available) #tokens = lines[0][52:].split() tokens = lines[0].split()[1:] arrheniusHigh = Arrhenius( A = (A,kunits), n = n, Ea = (Ea * energyFactor,"kcal/mol"), T0 = (1,"K"), ) if len(lines) == 1: # If there's only one line then we know to use the high-P limit kinetics as-is reaction.kinetics = arrheniusHigh else: # There's more kinetics information to be read arrheniusLow = None troe = None lindemann = None chebyshev = None pdepArrhenius = None efficiencies = {} chebyshevCoeffs = [] # Note that the subsequent lines could be in any order for line in lines[1:]: tokens = line.split('/') if 'DUP' in line or 'dup' in line: # Duplicate reaction reaction.duplicate = True elif 'LOW' in line or 'low' in line: # Low-pressure-limit Arrhenius parameters tokens = tokens[1].split() arrheniusLow = Arrhenius( A = (float(tokens[0].strip()),klow_units), n = float(tokens[1].strip()), Ea = (float(tokens[2].strip()) * energyFactor,"kcal/mol"), T0 = (1,"K"), ) elif 'TROE' in line or 'troe' in line: # Troe falloff parameters tokens = tokens[1].split() alpha = float(tokens[0].strip()) T3 = float(tokens[1].strip()) T1 = float(tokens[2].strip()) try: T2 = float(tokens[3].strip()) except (IndexError, ValueError): T2 = None troe = Troe( alpha = (alpha,''), T3 = (T3,"K"), T1 = (T1,"K"), T2 = (T2,"K") if T2 is not None else None, ) elif 'CHEB' in line or 'cheb' in line: # Chebyshev parameters if chebyshev is None: chebyshev = Chebyshev() chebyshev.kunits = kunits tokens = [t.strip() for t in tokens] if 'TCHEB' in line: index = tokens.index('TCHEB') tokens2 = tokens[index+1].split() chebyshev.Tmin = Quantity(float(tokens2[0].strip()),"K") chebyshev.Tmax = Quantity(float(tokens2[1].strip()),"K") if 'PCHEB' in line: index = tokens.index('PCHEB') tokens2 = tokens[index+1].split() chebyshev.Pmin = Quantity(float(tokens2[0].strip()),"atm") chebyshev.Pmax = Quantity(float(tokens2[1].strip()),"atm") if 'TCHEB' in line or 'PCHEB' in line: pass elif chebyshev.degreeT == 0 or chebyshev.degreeP == 0: tokens2 = tokens[1].split() chebyshev.degreeT = int(float(tokens2[0].strip())) chebyshev.degreeP = int(float(tokens2[1].strip())) chebyshev.coeffs = numpy.zeros((chebyshev.degreeT,chebyshev.degreeP), numpy.float64) else: tokens2 = tokens[1].split() chebyshevCoeffs.extend([float(t.strip()) for t in tokens2]) elif 'PLOG' in line or 'plog' in line: # Pressure-dependent Arrhenius parameters if pdepArrhenius is None: pdepArrhenius = [] tokens = tokens[1].split() pdepArrhenius.append([float(tokens[0].strip()), Arrhenius( A = (float(tokens[1].strip()),kunits), n = float(tokens[2].strip()), Ea = (float(tokens[3].strip()) * energyFactor,"kcal/mol"), T0 = (1,"K"), )]) else: # Assume a list of collider efficiencies for collider, efficiency in zip(tokens[0::2], tokens[1::2]): efficiencies[speciesDict[collider.strip()].molecule[0]] = float(efficiency.strip()) # Decide which kinetics to keep and store them on the reaction object # Only one of these should be true at a time! if chebyshev is not None: if chebyshev.Tmin is None or chebyshev.Tmax is None: raise ChemkinError('Missing TCHEB line for reaction {0}'.format(reaction)) if chebyshev.Pmin is None or chebyshev.Pmax is None: raise ChemkinError('Missing PCHEB line for reaction {0}'.format(reaction)) index = 0 for t in range(chebyshev.degreeT): for p in range(chebyshev.degreeP): chebyshev.coeffs[t,p] = chebyshevCoeffs[index] index += 1 # Don't forget to convert the Chebyshev coefficients to SI units! # This assumes that s^-1, cm^3/mol*s, etc. are compulsory chebyshev.coeffs[0,0] -= (len(reaction.reactants) - 1) * 6.0 reaction.kinetics = chebyshev elif pdepArrhenius is not None: reaction.kinetics = PDepArrhenius( pressures = ([P for P, arrh in pdepArrhenius],"atm"), arrhenius = [arrh for P, arrh in pdepArrhenius], ) elif troe is not None: troe.arrheniusHigh = arrheniusHigh troe.arrheniusLow = arrheniusLow troe.efficiencies = efficiencies reaction.kinetics = troe elif arrheniusLow is not None: reaction.kinetics = Lindemann(arrheniusHigh=arrheniusHigh, arrheniusLow=arrheniusLow) reaction.kinetics.efficiencies = efficiencies elif thirdBody: reaction.kinetics = ThirdBody(arrheniusLow=arrheniusHigh) reaction.kinetics.efficiencies = efficiencies elif reaction.duplicate: reaction.kinetics = arrheniusHigh else: raise ChemkinError('Unable to determine pressure-dependent kinetics for reaction {0}.'.format(reaction)) return reaction