1429 lines
60 KiB
Python
1429 lines
60 KiB
Python
#!/usr/bin/env python
|
|
# encoding: utf-8
|
|
|
|
################################################################################
|
|
#
|
|
# RMG - Reaction Mechanism Generator
|
|
#
|
|
# Copyright (c) 2009-2011 by the RMG Team (rmg_dev@mit.edu)
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
# copy of this software and associated documentation files (the 'Software'),
|
|
# to deal in the Software without restriction, including without limitation
|
|
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
# and/or sell copies of the Software, and to permit persons to whom the
|
|
# Software is furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
# DEALINGS IN THE SOFTWARE.
|
|
#
|
|
################################################################################
|
|
|
|
"""
|
|
This module contains functions for converting Chemkin input files to
|
|
Cantera input files (CTI).
|
|
"""
|
|
|
|
import logging
|
|
import re
|
|
import types
|
|
|
|
import numpy as np
|
|
|
|
################################################################################
|
|
|
|
class ChemkinError(Exception):
|
|
"""
|
|
An exception class for exceptional behavior involving Chemkin files. Pass a
|
|
string describing the circumstances that caused the exceptional behavior.
|
|
"""
|
|
pass
|
|
|
|
################################################################################
|
|
|
|
class Species(object):
|
|
def __init__(self, label):
|
|
self.label = label
|
|
|
|
def __str__(self):
|
|
return self.label
|
|
|
|
def __repr__(self):
|
|
return 'Species({0!r})'.format(self.label)
|
|
|
|
################################################################################
|
|
|
|
class ThermoModel:
|
|
"""
|
|
A base class for thermodynamics models, containing several attributes
|
|
common to all models:
|
|
|
|
=============== =================== ========================================
|
|
Attribute Type Description
|
|
=============== =================== ========================================
|
|
`Tmin` ``float`` The minimum temperature at which the model is valid, or ``None`` if unknown or undefined
|
|
`Tmax` ``float`` The maximum temperature at which the model is valid, or ``None`` if unknown or undefined
|
|
`comment` ``str`` Information about the model (e.g. its source)
|
|
=============== =================== ========================================
|
|
|
|
"""
|
|
|
|
def __init__(self, Tmin=None, Tmax=None, comment=''):
|
|
if Tmin is not None:
|
|
self.Tmin = Tmin
|
|
else:
|
|
self.Tmin = None
|
|
if Tmax is not None:
|
|
self.Tmax = Tmax
|
|
else:
|
|
self.Tmax = None
|
|
self.comment = comment
|
|
|
|
def __repr__(self):
|
|
"""
|
|
Return a string representation that can be used to reconstruct the
|
|
ThermoModel object.
|
|
"""
|
|
return 'ThermoModel(Tmin={0!r}, Tmax={1!r}, comment="""{2}""")'.format(self.Tmin, self.Tmax, self.comment)
|
|
|
|
################################################################################
|
|
|
|
class NASA(ThermoModel):
|
|
"""
|
|
A single NASA polynomial for thermodynamic data. The `coeffs` attribute
|
|
stores the seven or nine polynomial coefficients
|
|
:math:`\\mathbf{a} = \\left[a_{-2}\\ a_{-1}\\ a_0\\ a_1\\ a_2\\ a_3\\ a_4\\ a_5\\ a_6 \\right]`
|
|
from which the relevant thermodynamic parameters are evaluated via the
|
|
expressions
|
|
|
|
.. math:: \\frac{C_\\mathrm{p}(T)}{R} = a_{-2} T^{-2} + a_{-1} T^{-1} + a_0 + a_1 T + a_2 T^2 + a_3 T^3 + a_4 T^4
|
|
|
|
.. math:: \\frac{H(T)}{RT} = - a_{-2} T^{-2} + a_{-1} T^{-1} \\ln T + a_0 + \\frac{1}{2} a_1 T + \\frac{1}{3} a_2 T^2 + \\frac{1}{4} a_3 T^3 + \\frac{1}{5} a_4 T^4 + \\frac{a_5}{T}
|
|
|
|
.. math:: \\frac{S(T)}{R} = -\\frac{1}{2} a_{-2} T^{-2} - a_{-1} T^{-1} + a_0 \\ln T + a_1 T + \\frac{1}{2} a_2 T^2 + \\frac{1}{3} a_3 T^3 + \\frac{1}{4} a_4 T^4 + a_6
|
|
|
|
The coefficients are stored internally in the nine-coefficient format, even
|
|
when only seven coefficients are provided.
|
|
"""
|
|
|
|
def __init__(self, coeffs, Tmin=None, Tmax=None, comment=''):
|
|
ThermoModel.__init__(self, Tmin=Tmin, Tmax=Tmax, comment=comment)
|
|
coeffs = coeffs or (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
|
|
if len(coeffs) == 7:
|
|
self.cm2 = 0.0; self.cm1 = 0.0
|
|
self.c0, self.c1, self.c2, self.c3, self.c4, self.c5, self.c6 = coeffs
|
|
elif len(coeffs) == 9:
|
|
self.cm2, self.cm1, self.c0, self.c1, self.c2, self.c3, self.c4, self.c5, self.c6 = coeffs
|
|
else:
|
|
raise ChemkinError('Invalid number of NASA polynomial coefficients; should be 7 or 9.')
|
|
|
|
def __repr__(self):
|
|
"""
|
|
Return a string representation that can be used to reconstruct the
|
|
object.
|
|
"""
|
|
string = 'NASA(Tmin={0!r}, Tmax={1!r}'.format(self.Tmin, self.Tmax)
|
|
if self.cm2 == 0 and self.cm1 == 0:
|
|
string += ', coeffs=[{0:g},{1:g},{2:g},{3:g},{4:g},{5:g},{6:g}]'.format(self.c0, self.c1, self.c2, self.c3, self.c4, self.c5, self.c6)
|
|
else:
|
|
string += ', coeffs=[{0:g},{1:g},{2:g},{3:g},{4:g},{5:g},{6:g},{7:g},{8:g}]'.format(self.cm2, self.cm1, self.c0, self.c1, self.c2, self.c3, self.c4, self.c5, self.c6)
|
|
if self.comment != '': string += ', comment="""{0}"""'.format(self.comment)
|
|
string += ')'
|
|
return string
|
|
|
|
################################################################################
|
|
|
|
class MultiNASA(ThermoModel):
|
|
"""
|
|
A set of thermodynamic parameters given by NASA polynomials. This class
|
|
stores a list of :class:`NASA` objects in the `polynomials`
|
|
attribute. When evaluating a thermodynamic quantity, a polynomial that
|
|
contains the desired temperature within its valid range will be used.
|
|
"""
|
|
|
|
def __init__(self, polynomials=None, Tmin=0.0, Tmax=0.0, comment=''):
|
|
ThermoModel.__init__(self, Tmin=Tmin, Tmax=Tmax, comment=comment)
|
|
self.polynomials = polynomials or []
|
|
|
|
def __repr__(self):
|
|
"""
|
|
Return a string representation that can be used to reconstruct the
|
|
MultiNASA object.
|
|
"""
|
|
string = 'MultiNASA(Tmin={0!r}, Tmax={1!r}'.format(self.Tmin, self.Tmax)
|
|
string += ', polynomials=[{0}]'.format(','.join(['%r' % poly for poly in self.polynomials]))
|
|
if self.comment != '': string += ', comment="""{0}"""'.format(self.comment)
|
|
string += ')'
|
|
return string
|
|
|
|
################################################################################
|
|
|
|
class Reaction(object):
|
|
"""
|
|
A chemical reaction. The attributes are:
|
|
|
|
=================== =========================== ============================
|
|
Attribute Type Description
|
|
=================== =========================== ============================
|
|
`index` :class:`int` A unique nonnegative integer index
|
|
`reactants` :class:`list` The reactant species (as :class:`Species` objects)
|
|
`products` :class:`list` The product species (as :class:`Species` objects)
|
|
`kinetics` :class:`KineticsModel` The kinetics model to use for the reaction
|
|
`reversible` ``bool`` ``True`` if the reaction is reversible, ``False`` if not
|
|
`transitionState` :class:`TransitionState` The transition state
|
|
`thirdBody` ``bool`` ``True`` if the reaction if the reaction kinetics imply a third body, ``False`` if not
|
|
`duplicate` ``bool`` ``True`` if the reaction is known to be a duplicate, ``False`` if not
|
|
`degeneracy` :class:`double` The reaction path degeneracy for the reaction
|
|
`pairs` ``list`` Reactant-product pairings to use in converting reaction flux to species flux
|
|
=================== =========================== ============================
|
|
|
|
"""
|
|
|
|
def __init__(self, index=-1, reactants=None, products=None,
|
|
kinetics=None, reversible=True, transitionState=None,
|
|
thirdBody=False, duplicate=False, degeneracy=1, pairs=None):
|
|
self.index = index
|
|
self.reactants = reactants
|
|
self.products = products
|
|
self.kinetics = kinetics
|
|
self.reversible = reversible
|
|
self.transitionState = transitionState
|
|
self.thirdBody = thirdBody
|
|
self.duplicate = duplicate
|
|
self.degeneracy = degeneracy
|
|
self.pairs = pairs
|
|
|
|
def __repr__(self):
|
|
"""
|
|
Return a string representation that can be used to reconstruct the
|
|
object.
|
|
"""
|
|
string = 'Reaction('
|
|
if self.index != -1:
|
|
string += 'index={0:d}, '.format(self.index)
|
|
if self.reactants is not None:
|
|
string += 'reactants={0!r}, '.format(self.reactants)
|
|
if self.products is not None:
|
|
string += 'products={0!r}, '.format(self.products)
|
|
if self.kinetics is not None:
|
|
string += 'kinetics={0!r}, '.format(self.kinetics)
|
|
if not self.reversible:
|
|
string += 'reversible={0}, '.format(self.reversible)
|
|
if self.transitionState is not None:
|
|
string += 'transitionState={0!r}, '.format(self.transitionState)
|
|
if self.thirdBody:
|
|
string += 'thirdBody={0}, '.format(self.thirdBody)
|
|
if self.duplicate:
|
|
string += 'duplicate={0}, '.format(self.duplicate)
|
|
if self.degeneracy != 1:
|
|
string += 'degeneracy={0:d}, '.format(self.degeneracy)
|
|
if self.pairs is not None:
|
|
string += 'pairs={0}, '.format(self.pairs)
|
|
string = string[:-2] + ')'
|
|
return string
|
|
|
|
def __str__(self):
|
|
"""
|
|
Return a string representation of the reaction, in the form 'A + B <=> C + D'.
|
|
"""
|
|
arrow = ' <=> '
|
|
if not self.reversible: arrow = ' -> '
|
|
return arrow.join([' + '.join([str(s) for s in self.reactants]), ' + '.join([str(s) for s in self.products])])
|
|
|
|
def hasTemplate(self, reactants, products):
|
|
"""
|
|
Return ``True`` if the reaction matches the template of `reactants`
|
|
and `products`, which are both lists of :class:`Species` objects, or
|
|
``False`` if not.
|
|
"""
|
|
return ((all([spec in self.reactants for spec in reactants]) and
|
|
all([spec in self.products for spec in products])) or
|
|
(all([spec in self.products for spec in reactants]) and
|
|
all([spec in self.reactants for spec in products])))
|
|
|
|
################################################################################
|
|
|
|
################################################################################
|
|
|
|
class KineticsModel(object):
|
|
"""
|
|
A base class for kinetics models, containing several attributes common to
|
|
all models:
|
|
|
|
=============== =================== ========================================
|
|
Attribute Type Description
|
|
=============== =================== ========================================
|
|
`Tmin` :class:`Quantity` The minimum absolute temperature in K at which the model is valid
|
|
`Tmax` :class:`Quantity` The maximum absolute temperature in K at which the model is valid
|
|
`Pmin` :class:`Quantity` The minimum absolute pressure in Pa at which the model is valid
|
|
`Pmax` :class:`Quantity` The maximum absolute pressure in Pa at which the model is valid
|
|
`comment` :class:`str` A string containing information about the model (e.g. its source)
|
|
=============== =================== ========================================
|
|
|
|
"""
|
|
|
|
def __init__(self, Tmin=None, Tmax=None, Pmin=None, Pmax=None, comment=''):
|
|
if Tmin is not None:
|
|
self.Tmin = Tmin
|
|
else:
|
|
self.Tmin = None
|
|
if Tmax is not None:
|
|
self.Tmax = Tmax
|
|
else:
|
|
self.Tmax = None
|
|
if Pmin is not None:
|
|
self.Pmin = Pmin
|
|
else:
|
|
self.Pmin = None
|
|
if Pmax is not None:
|
|
self.Pmax = Pmax
|
|
else:
|
|
self.Pmax = None
|
|
self.comment = comment
|
|
|
|
def __repr__(self):
|
|
"""
|
|
Return a string representation that can be used to reconstruct the
|
|
KineticsModel object.
|
|
"""
|
|
string = self.toPrettyRepr()
|
|
string = re.sub(r'\(\n ', '(', string)
|
|
string = re.sub(r',\n ', ', ', string)
|
|
string = re.sub(r',\n\)', ')', string)
|
|
string = re.sub(r' = ', '=', string)
|
|
return string
|
|
|
|
def toPrettyRepr(self):
|
|
"""
|
|
Return a string representation that can be used to reconstruct the
|
|
KineticsModel object.
|
|
"""
|
|
raise NotImplementedError('You must implement this method in your derived class.')
|
|
|
|
def __reduce__(self):
|
|
"""
|
|
A helper function used when pickling a KineticsModel object.
|
|
"""
|
|
return (KineticsModel, (self.Tmin, self.Tmax, self.Pmin, self.Pmax, self.comment))
|
|
|
|
def isPressureDependent(self):
|
|
"""
|
|
Return ``True`` if the kinetics are pressure-dependent or ``False`` if
|
|
they are pressure-independent. This method must be overloaded in the
|
|
derived class.
|
|
"""
|
|
raise ChemkinError('Unexpected call to KineticsModel.isPressureDependent(); you should be using a class derived from KineticsModel.')
|
|
|
|
################################################################################
|
|
|
|
class KineticsData(KineticsModel):
|
|
"""
|
|
A kinetics model based around a set of discrete (high-pressure limit)
|
|
rate coefficients at various temperatures. The attributes are:
|
|
|
|
=========== =================== ============================================
|
|
Attribute Type Description
|
|
=========== =================== ============================================
|
|
`Tdata` :class:`Quantity` The temperatures at which the heat capacity data is provided
|
|
`kdata` :class:`Quantity` The rate coefficients in SI units at each temperature in `Tdata`
|
|
=========== =================== ============================================
|
|
|
|
"""
|
|
|
|
def __init__(self, Tdata=None, kdata=None, Tmin=None, Tmax=None, comment=''):
|
|
KineticsModel.__init__(self, Tmin=Tmin, Tmax=Tmax, comment=comment)
|
|
self.Tdata = Tdata
|
|
self.kdata = kdata
|
|
|
|
def toPrettyRepr(self):
|
|
"""
|
|
Return a string representation of the reference that can be used to
|
|
reconstruct the object.
|
|
"""
|
|
string = u'KineticsData(\n'
|
|
string += u' Tdata = {0!r},\n'.format(self.Tdata)
|
|
string += u' kdata = {0!r},\n'.format(self.kdata)
|
|
if self.Tmin is not None: string += ' Tmin = {0!r},\n'.format(self.Tmin)
|
|
if self.Tmax is not None: string += ' Tmax = {0!r},\n'.format(self.Tmax)
|
|
if self.comment != '': string += ' comment = """{0}""",\n'.format(self.comment)
|
|
return string + u')'
|
|
|
|
def __reduce__(self):
|
|
"""
|
|
A helper function used when pickling a KineticsData object.
|
|
"""
|
|
return (KineticsData, (self.Tdata, self.kdata, self.Tmin, self.Tmax, self.comment))
|
|
|
|
def isPressureDependent(self):
|
|
"""
|
|
Returns ``False`` since KineticsData kinetics are not
|
|
pressure-dependent.
|
|
"""
|
|
return False
|
|
|
|
################################################################################
|
|
|
|
class Arrhenius(KineticsModel):
|
|
"""
|
|
Represent a set of modified Arrhenius kinetics. The kinetic expression has
|
|
the form
|
|
|
|
.. math:: k(T) = A \\left( \\frac{T}{T_0} \\right)^n \\exp \\left( - \\frac{E_\\mathrm{a}}{RT} \\right)
|
|
|
|
where :math:`A`, :math:`n`, :math:`E_\\mathrm{a}`, and :math:`T_0` are the
|
|
parameters to be set, :math:`T` is absolute temperature, and :math:`R` is
|
|
the gas law constant. The attributes are:
|
|
|
|
=============== =================== ========================================
|
|
Attribute Type Description
|
|
=============== =================== ========================================
|
|
`A` :class:`Quantity` The preexponential factor in s^-1, m^3/mol*s, etc.
|
|
`T0` :class:`Quantity` The reference temperature in K
|
|
`n` :class:`Quantity` The temperature exponent
|
|
`Ea` :class:`Quantity` The activation energy in J/mol
|
|
=============== =================== ========================================
|
|
|
|
"""
|
|
|
|
def __init__(self, A=0.0, n=0.0, Ea=0.0, T0=1.0, Tmin=None, Tmax=None, comment=''):
|
|
KineticsModel.__init__(self, Tmin=Tmin, Tmax=Tmax, comment=comment)
|
|
self.A = A
|
|
self.T0 = T0
|
|
self.n = n
|
|
self.Ea = Ea
|
|
|
|
def toPrettyRepr(self):
|
|
"""
|
|
Return a string representation of the reference that can be used to
|
|
reconstruct the object.
|
|
"""
|
|
string = u'Arrhenius(\n'
|
|
string += u' A = {0!r},\n'.format(self.A)
|
|
string += u' n = {0!r},\n'.format(self.n)
|
|
string += u' Ea = {0!r},\n'.format(self.Ea)
|
|
string += u' T0 = {0!r},\n'.format(self.T0)
|
|
if self.Tmin is not None: string += ' Tmin = {0!r},\n'.format(self.Tmin)
|
|
if self.Tmax is not None: string += ' Tmax = {0!r},\n'.format(self.Tmax)
|
|
if self.comment != '': string += ' comment = """{0}""",\n'.format(self.comment)
|
|
return string + u')'
|
|
|
|
def __str__(self):
|
|
"""
|
|
Return a string representation that is a bit shorter and prettier than __repr__.
|
|
"""
|
|
string = 'Arrhenius(A={0!r}, n={1!r}, Ea={2!r}, T0={3!r})'.format(self.A, self.n, self.Ea, self.T0)
|
|
return string
|
|
|
|
def __reduce__(self):
|
|
"""
|
|
A helper function used when pickling an Arrhenius object.
|
|
"""
|
|
return (Arrhenius, (self.A, self.n, self.Ea, self.T0, self.Tmin, self.Tmax, self.comment))
|
|
|
|
def isPressureDependent(self):
|
|
"""
|
|
Returns ``False`` since Arrhenius kinetics are not pressure-dependent.
|
|
"""
|
|
return False
|
|
|
|
################################################################################
|
|
|
|
class PDepArrhenius(KineticsModel):
|
|
"""
|
|
A kinetic model of a phenomenological rate coefficient k(T, P) using the
|
|
expression
|
|
|
|
.. math:: k(T,P) = A(P) T^{n(P)} \\exp \\left[ \\frac{-E_\\mathrm{a}(P)}{RT} \\right]
|
|
|
|
where the modified Arrhenius parameters are stored at a variety of pressures
|
|
and interpolated between on a logarithmic scale. The attributes are:
|
|
|
|
=============== ================== ============================================
|
|
Attribute Type Description
|
|
=============== ================== ============================================
|
|
`pressures` :class:`list` The list of pressures in Pa
|
|
`arrhenius` :class:`list` The list of :class:`Arrhenius` objects at each pressure
|
|
`highPlimit` :class:`Arrhenius` The high (infinite) pressure limiting :class:`Arrhenius` expression
|
|
=============== ================== ============================================
|
|
|
|
Note that `highPlimit` is not used in evaluating k(T,P).
|
|
"""
|
|
|
|
def __init__(self, pressures=None, arrhenius=None, highPlimit=None, Tmin=None, Tmax=None, Pmin=None, Pmax=None, comment=''):
|
|
KineticsModel.__init__(self, Tmin=Tmin, Tmax=Tmax, Pmin=Pmin, Pmax=Pmax, comment=comment)
|
|
self.pressures = pressures
|
|
self.arrhenius = arrhenius or []
|
|
self.highPlimit = highPlimit or None
|
|
|
|
def toPrettyRepr(self):
|
|
"""
|
|
Return a string representation of the reference that can be used to
|
|
reconstruct the object.
|
|
"""
|
|
string = u'MultiKinetics(\n'
|
|
string += u' pressures = {0!r},\n'.format(self.pressures)
|
|
string += u' arrhenius = [\n'
|
|
for kinetics in self.arrhenius:
|
|
for line in kinetics.toPrettyRepr().splitlines():
|
|
string += u' {0}\n'.format(line)
|
|
string += u' ],\n'
|
|
if self.Tmin is not None: string += ' Tmin = {0!r},\n'.format(self.Tmin)
|
|
if self.Tmax is not None: string += ' Tmax = {0!r},\n'.format(self.Tmax)
|
|
if self.Pmin is not None: string += ' Pmin = {0!r},\n'.format(self.Pmin)
|
|
if self.Pmax is not None: string += ' Pmax = {0!r},\n'.format(self.Pmax)
|
|
if self.comment != '': string += ' comment = """{0}""",\n'.format(self.comment)
|
|
return string + u')'
|
|
|
|
def __repr__(self):
|
|
"""
|
|
Return a string representation that can be used to reconstruct the
|
|
PDepArrhenius object.
|
|
"""
|
|
string = 'PDepArrhenius(\n pressures={0!r},\n arrhenius=[\n {1}]'.format(self.pressures, ',\n '.join([repr(arrh) for arrh in self.arrhenius]))
|
|
if self.highPlimit is not None: string += ",\n highPlimit={0!r}".format(self.highPlimit)
|
|
if self.Tmin is not None: string += ', Tmin={0!r}'.format(self.Tmin)
|
|
if self.Tmax is not None: string += ', Tmax={0!r}'.format(self.Tmax)
|
|
if self.Pmin is not None: string += ', Pmin={0!r}'.format(self.Pmin)
|
|
if self.Pmax is not None: string += ', Pmax={0!r}'.format(self.Pmax)
|
|
if self.comment != '': string += ',\n comment="""{0}"""'.format(self.comment)
|
|
string += '\n)'
|
|
return string
|
|
|
|
def __reduce__(self):
|
|
"""
|
|
A helper function used when pickling a PDepArrhenius object.
|
|
"""
|
|
return (PDepArrhenius, (self.pressures, self.arrhenius, self.highPlimit, self.Tmin, self.Tmax, self.Pmin, self.Pmax, self.comment))
|
|
|
|
def isPressureDependent(self):
|
|
"""
|
|
Returns ``True`` since PDepArrhenius kinetics are pressure-dependent.
|
|
"""
|
|
return True
|
|
|
|
################################################################################
|
|
|
|
class Chebyshev(KineticsModel):
|
|
"""
|
|
A kinetic model of a phenomenological rate coefficient k(T, P) using the
|
|
expression
|
|
|
|
.. math:: \\log k(T,P) = \\sum_{t=1}^{N_T} \\sum_{p=1}^{N_P} \\alpha_{tp} \\phi_t(\\tilde{T}) \\phi_p(\\tilde{P})
|
|
|
|
where :math:`\\alpha_{tp}` is a constant, :math:`\\phi_n(x)` is the
|
|
Chebyshev polynomial of degree :math:`n` evaluated at :math:`x`, and
|
|
|
|
.. math:: \\tilde{T} \\equiv \\frac{2T^{-1} - T_\\mathrm{min}^{-1} - T_\\mathrm{max}^{-1}}{T_\\mathrm{max}^{-1} - T_\\mathrm{min}^{-1}}
|
|
|
|
.. math:: \\tilde{P} \\equiv \\frac{2 \\log P - \\log P_\\mathrm{min} - \\log P_\\mathrm{max}}{\\log P_\\mathrm{max} - \\log P_\\mathrm{min}}
|
|
|
|
are reduced temperature and reduced pressures designed to map the ranges
|
|
:math:`(T_\\mathrm{min}, T_\\mathrm{max})` and
|
|
:math:`(P_\\mathrm{min}, P_\\mathrm{max})` to :math:`(-1, 1)`.
|
|
The attributes are:
|
|
|
|
=============== =============== ============================================
|
|
Attribute Type Description
|
|
=============== =============== ============================================
|
|
`coeffs` :class:`list` Matrix of Chebyshev coefficients
|
|
`kunits` ``str`` The units of the generated k(T, P) values
|
|
`degreeT` :class:`int` The number of terms in the inverse temperature direction
|
|
`degreeP` :class:`int` The number of terms in the log pressure direction
|
|
=============== =============== ============================================
|
|
|
|
"""
|
|
|
|
def __init__(self, coeffs=None, kunits='', Tmin=None, Tmax=None, Pmin=None, Pmax=None, comment=''):
|
|
KineticsModel.__init__(self, Tmin=Tmin, Tmax=Tmax, Pmin=Pmin, Pmax=Pmax, comment=comment)
|
|
if coeffs is not None:
|
|
self.coeffs = np.array(coeffs, np.float64)
|
|
self.degreeT = self.coeffs.shape[0]
|
|
self.degreeP = self.coeffs.shape[1]
|
|
else:
|
|
self.coeffs = None
|
|
self.degreeT = 0
|
|
self.degreeP = 0
|
|
self.kunits = kunits
|
|
|
|
def toPrettyRepr(self):
|
|
"""
|
|
Return a string representation of the reference that can be used to
|
|
reconstruct the object.
|
|
"""
|
|
string = u'Chebyshev(\n'
|
|
string += u' coeffs = [\n'
|
|
for i in range(self.degreeT):
|
|
string += u' [{0}]'.format(','.join(['{0:g}'.format(self.coeffs[i,j]) for j in range(self.degreeP)]))
|
|
string += u' ],\n'
|
|
if self.Tmin is not None: string += ' Tmin = {0!r},\n'.format(self.Tmin)
|
|
if self.Tmax is not None: string += ' Tmax = {0!r},\n'.format(self.Tmax)
|
|
if self.Pmin is not None: string += ' Pmin = {0!r},\n'.format(self.Pmin)
|
|
if self.Pmax is not None: string += ' Pmax = {0!r},\n'.format(self.Pmax)
|
|
if self.comment != '': string += ' comment = """{0}""",\n'.format(self.comment)
|
|
return string + u')'
|
|
|
|
def __repr__(self):
|
|
"""
|
|
Return a string representation that can be used to reconstruct the
|
|
Chebyshev object.
|
|
"""
|
|
coeffs = '['
|
|
for i in range(self.degreeT):
|
|
if i > 0: coeffs += ', '
|
|
coeffs += '[{0}]'.format(','.join(['{0:g}'.format(self.coeffs[i,j]) for j in range(self.degreeP)]))
|
|
coeffs += ']'
|
|
|
|
string = 'Chebyshev(coeffs={0}'.format(coeffs)
|
|
if self.kunits != '': string += ', kunits="{0}"'.format(self.kunits)
|
|
if self.Tmin is not None: string += ', Tmin={0!r}'.format(self.Tmin)
|
|
if self.Tmax is not None: string += ', Tmax={0!r}'.format(self.Tmax)
|
|
if self.Pmin is not None: string += ', Pmin={0!r}'.format(self.Pmin)
|
|
if self.Pmax is not None: string += ', Pmax={0!r}'.format(self.Pmax)
|
|
if self.comment != '': string += ', comment="""{0}"""'.format(self.comment)
|
|
string += ')'
|
|
return string
|
|
|
|
def __reduce__(self):
|
|
"""
|
|
A helper function used when pickling a Chebyshev object.
|
|
"""
|
|
return (Chebyshev, (self.coeffs, self.kunits, self.Tmin, self.Tmax, self.Pmin, self.Pmax, self.comment))
|
|
|
|
def isPressureDependent(self):
|
|
"""
|
|
Returns ``True`` since Chebyshev polynomial kinetics are
|
|
pressure-dependent.
|
|
"""
|
|
return True
|
|
|
|
################################################################################
|
|
|
|
class ThirdBody(KineticsModel):
|
|
"""
|
|
A kinetic model of a phenomenological rate coefficient k(T, P) using the
|
|
expression
|
|
|
|
.. math:: k(T,P) = k(T) [\\ce{M}]
|
|
|
|
where :math:`k(T)` is an Arrhenius expression and
|
|
:math:`[\\ce{M}] \\approx P/RT` is the concentration of the third body
|
|
(i.e. the bath gas). A collision efficiency can be used to further correct
|
|
the value of :math:`k(T,P)`.
|
|
|
|
The attributes are:
|
|
|
|
=============== ======================= ====================================
|
|
Attribute Type Description
|
|
=============== ======================= ====================================
|
|
`arrheniusHigh` :class:`Arrhenius` The Arrhenius kinetics
|
|
`efficiencies` ``dict`` A mapping of species to collider efficiencies
|
|
=============== ======================= ====================================
|
|
|
|
"""
|
|
|
|
def __init__(self, arrheniusHigh=None, efficiencies=None, Tmin=None, Tmax=None, Pmin=None, Pmax=None, comment=''):
|
|
KineticsModel.__init__(self, Tmin=Tmin, Tmax=Tmax, Pmin=Pmin, Pmax=Pmax, comment=comment)
|
|
self.arrheniusHigh = arrheniusHigh
|
|
self.efficiencies = {}
|
|
if efficiencies is not None:
|
|
for mol, eff in efficiencies.iteritems():
|
|
self.efficiencies[mol] = eff
|
|
|
|
def toPrettyRepr(self):
|
|
"""
|
|
Return a string representation of the reference that can be used to
|
|
reconstruct the object.
|
|
"""
|
|
string = u'ThirdBody(\n'
|
|
|
|
lines = self.arrheniusHigh.toPrettyRepr().splitlines()
|
|
string += u' arrheniusHigh = {0}\n'.format(lines[0])
|
|
for line in lines[1:-1]:
|
|
string += u' {0}\n'.format(line)
|
|
string += u' ),\n'
|
|
|
|
if len(self.efficiencies) > 0:
|
|
string += u' efficiencies = {\n'
|
|
for species in sorted(self.efficiencies):
|
|
string += u' "{0}": {1:g},\n'.format(species, self.efficiencies[species])
|
|
string += u' },\n'
|
|
|
|
if self.Tmin is not None: string += ' Tmin = {0!r},\n'.format(self.Tmin)
|
|
if self.Tmax is not None: string += ' Tmax = {0!r},\n'.format(self.Tmax)
|
|
if self.Pmin is not None: string += ' Pmin = {0!r},\n'.format(self.Pmin)
|
|
if self.Pmax is not None: string += ' Pmax = {0!r},\n'.format(self.Pmax)
|
|
if self.comment != '': string += ' comment = """{0}""",\n'.format(self.comment)
|
|
return string + u')'
|
|
|
|
def __reduce__(self):
|
|
"""
|
|
A helper function used when pickling a ThirdBody object.
|
|
"""
|
|
return (ThirdBody, (self.arrheniusHigh, self.efficiencies, self.Tmin, self.Tmax, self.Pmin, self.Pmax, self.comment))
|
|
|
|
def isPressureDependent(self):
|
|
"""
|
|
Returns ``True`` since third-body kinetics are pressure-dependent.
|
|
"""
|
|
return True
|
|
|
|
def getColliderEfficiency(self, collider):
|
|
"""
|
|
Return the collider efficiency for the specified `collider`, which can
|
|
take one of two forms:
|
|
|
|
* A single collider species. If the collider exists in the in the set
|
|
of efficiencies, its efficiency will be returned. If not, an
|
|
efficiency of unity will be returned.
|
|
|
|
* A ``dict`` mapping collider species to mole fractions. The overall
|
|
efficiency will be a weighted sum of the efficiencies of the collider
|
|
species, using the mole fractions as the weights. Collider species not
|
|
present in the set of efficiencies will be assumed to have an
|
|
efficiency of unity.
|
|
|
|
If collider is ``None`` or otherwise invalid, an efficiency of unity
|
|
will be returned.
|
|
"""
|
|
if isinstance(collider, dict):
|
|
# Assume collider is a dict mapping species to weights
|
|
efficiency = 0.0
|
|
for spec, frac in collider.iteritems():
|
|
try:
|
|
eff = self.efficiencies[spec]
|
|
except KeyError:
|
|
eff = 1.0
|
|
efficiency += eff * frac
|
|
efficiency /= sum(collider.values())
|
|
else:
|
|
# Assume collider is a single species
|
|
try:
|
|
efficiency = self.efficiencies[collider]
|
|
except KeyError:
|
|
efficiency = 1.0
|
|
|
|
return efficiency
|
|
|
|
################################################################################
|
|
|
|
class Lindemann(ThirdBody):
|
|
"""
|
|
A kinetic model of a phenomenological rate coefficient k(T, P) using the
|
|
expression
|
|
|
|
.. math:: k(T,P) = k_\\infty(T) \\left[ \\frac{P_\\mathrm{r}}{1 + P_\\mathrm{r}} \\right] F
|
|
|
|
where
|
|
|
|
.. math::
|
|
|
|
P_\\mathrm{r} &= \\frac{k_0(T)}{k_\\infty(T)} [\\ce{M}]
|
|
|
|
k_0(T) &= A_0 T^{n_0} \\exp \\left( - \\frac{E_0}{RT} \\right)
|
|
|
|
k_\\infty(T) &= A_\\infty T^{n_\\infty} \\exp \\left( - \\frac{E_\\infty}{RT} \\right)
|
|
|
|
and :math:`[\\ce{M}] \\approx P/RT` is the concentration of the
|
|
bath gas. The Arrhenius expressions :math:`k_0(T)` and :math:`k_\\infty(T)`
|
|
represent the low-pressure and high-pressure limit kinetics, respectively.
|
|
The former is necessarily one reaction order higher than the latter. For
|
|
the Lindemann model, :math:`F = 1`. A collision efficiency can be used to
|
|
further correct the value of :math:`k(T,P)`.
|
|
|
|
The attributes are:
|
|
|
|
=============== ======================= ====================================
|
|
Attribute Type Description
|
|
=============== ======================= ====================================
|
|
`arrheniusLow` :class:`Arrhenius` The Arrhenius kinetics at the low-pressure limit
|
|
`arrheniusHigh` :class:`Arrhenius` The Arrhenius kinetics at the high-pressure limit
|
|
`efficiencies` ``dict`` A mapping of species to collider efficiencies
|
|
=============== ======================= ====================================
|
|
|
|
"""
|
|
|
|
def __init__(self, arrheniusLow=None, arrheniusHigh=None, efficiencies=None, Tmin=None, Tmax=None, Pmin=None, Pmax=None, comment=''):
|
|
ThirdBody.__init__(self, arrheniusHigh=arrheniusHigh, efficiencies=efficiencies, Tmin=Tmin, Tmax=Tmax, Pmin=Pmin, Pmax=Pmax, comment=comment)
|
|
self.arrheniusLow = arrheniusLow
|
|
|
|
def toPrettyRepr(self):
|
|
"""
|
|
Return a string representation of the reference that can be used to
|
|
reconstruct the object.
|
|
"""
|
|
string = u'Lindemann(\n'
|
|
|
|
lines = self.arrheniusHigh.toPrettyRepr().splitlines()
|
|
string += u' arrheniusHigh = {0}\n'.format(lines[0])
|
|
for line in lines[1:-1]:
|
|
string += u' {0}\n'.format(line)
|
|
string += u' ),\n'
|
|
|
|
lines = self.arrheniusLow.toPrettyRepr().splitlines()
|
|
string += u' arrheniusLow = {0}\n'.format(lines[0])
|
|
for line in lines[1:-1]:
|
|
string += u' {0}\n'.format(line)
|
|
string += u' ),\n'
|
|
|
|
if len(self.efficiencies) > 0:
|
|
string += u' efficiencies = {\n'
|
|
for species in sorted(self.efficiencies):
|
|
string += u' "{0}": {1:g},\n'.format(species, self.efficiencies[species])
|
|
string += u' },\n'
|
|
|
|
if self.Tmin is not None: string += ' Tmin = {0!r},\n'.format(self.Tmin)
|
|
if self.Tmax is not None: string += ' Tmax = {0!r},\n'.format(self.Tmax)
|
|
if self.Pmin is not None: string += ' Pmin = {0!r},\n'.format(self.Pmin)
|
|
if self.Pmax is not None: string += ' Pmax = {0!r},\n'.format(self.Pmax)
|
|
if self.comment != '': string += ' comment = """{0}""",\n'.format(self.comment)
|
|
return string + u')'
|
|
|
|
def __reduce__(self):
|
|
"""
|
|
A helper function used when pickling a Lindemann object.
|
|
"""
|
|
return (Lindemann, (self.arrheniusLow, self.arrheniusHigh, self.efficiencies, self.Tmin, self.Tmax, self.Pmin, self.Pmax, self.comment))
|
|
|
|
################################################################################
|
|
|
|
class Troe(Lindemann):
|
|
"""
|
|
A kinetic model of a phenomenological rate coefficient k(T, P) using the
|
|
expression
|
|
|
|
.. math:: k(T,P) = k_\\infty(T) \\left[ \\frac{P_\\mathrm{r}}{1 + P_\\mathrm{r}} \\right] F
|
|
|
|
where
|
|
|
|
.. math::
|
|
|
|
P_\\mathrm{r} &= \\frac{k_0(T)}{k_\\infty(T)} [\\ce{M}]
|
|
|
|
k_0(T) &= A_0 T^{n_0} \\exp \\left( - \\frac{E_0}{RT} \\right)
|
|
|
|
k_\\infty(T) &= A_\\infty T^{n_\\infty} \\exp \\left( - \\frac{E_\\infty}{RT} \\right)
|
|
|
|
and :math:`[\\ce{M}] \\approx P/RT` is the concentration of the
|
|
bath gas. The Arrhenius expressions :math:`k_0(T)` and :math:`k_\\infty(T)`
|
|
represent the low-pressure and high-pressure limit kinetics, respectively.
|
|
The former is necessarily one reaction order higher than the latter. A
|
|
collision efficiency can be used to further correct the value of
|
|
:math:`k(T,P)`.
|
|
|
|
For the Troe model the parameter :math:`F` is computed via
|
|
|
|
.. math::
|
|
|
|
\\log F &= \\left\\{1 + \\left[ \\frac{\\log P_\\mathrm{r} + c}{n - d (\\log P_\\mathrm{r} + c)} \\right]^2 \\right\\}^{-1} \\log F_\\mathrm{cent}
|
|
|
|
c &= -0.4 - 0.67 \\log F_\\mathrm{cent}
|
|
|
|
n &= 0.75 - 1.27 \\log F_\\mathrm{cent}
|
|
|
|
d &= 0.14
|
|
|
|
F_\\mathrm{cent} &= (1 - \\alpha) \\exp \\left( -T/T_3 \\right) + \\alpha \\exp \\left( -T/T_1 \\right) + \\exp \\left( -T_2/T \\right)
|
|
|
|
The attributes are:
|
|
|
|
=============== ======================= ====================================
|
|
Attribute Type Description
|
|
=============== ======================= ====================================
|
|
`arrheniusLow` :class:`Arrhenius` The Arrhenius kinetics at the low-pressure limit
|
|
`arrheniusHigh` :class:`Arrhenius` The Arrhenius kinetics at the high-pressure limit
|
|
`efficiencies` ``dict`` A mapping of species to collider efficiencies
|
|
`alpha` :class:`Quantity` The :math:`\\alpha` parameter
|
|
`T1` :class:`Quantity` The :math:`T_1` parameter
|
|
`T2` :class:`Quantity` The :math:`T_2` parameter
|
|
`T3` :class:`Quantity` The :math:`T_3` parameter
|
|
=============== ======================= ====================================
|
|
|
|
"""
|
|
|
|
def __init__(self, arrheniusLow=None, arrheniusHigh=None, efficiencies=None, alpha=0.0, T3=0.0, T1=0.0, T2=None, Tmin=None, Tmax=None, Pmin=None, Pmax=None, comment=''):
|
|
Lindemann.__init__(self, arrheniusLow=arrheniusLow, arrheniusHigh=arrheniusHigh, efficiencies=efficiencies, Tmin=Tmin, Tmax=Tmax, Pmin=Pmin, Pmax=Pmax, comment=comment)
|
|
self.alpha = alpha
|
|
self.T3 = T3
|
|
self.T1 = T1
|
|
if T2 is not None:
|
|
self.T2 = T2
|
|
else:
|
|
self.T2 = None
|
|
|
|
def toPrettyRepr(self):
|
|
"""
|
|
Return a string representation of the reference that can be used to
|
|
reconstruct the object.
|
|
"""
|
|
string = u'Troe(\n'
|
|
|
|
lines = self.arrheniusHigh.toPrettyRepr().splitlines()
|
|
string += u' arrheniusHigh = {0}\n'.format(lines[0])
|
|
for line in lines[1:-1]:
|
|
string += u' {0}\n'.format(line)
|
|
string += u' ),\n'
|
|
|
|
lines = self.arrheniusLow.toPrettyRepr().splitlines()
|
|
string += u' arrheniusLow = {0}\n'.format(lines[0])
|
|
for line in lines[1:-1]:
|
|
string += u' {0}\n'.format(line)
|
|
string += u' ),\n'
|
|
|
|
string += u' alpha = {0!r},\n'.format(self.alpha)
|
|
string += u' T3 = {0!r},\n'.format(self.T3)
|
|
string += u' T1 = {0!r},\n'.format(self.T1)
|
|
if self.T2 is not None: string += u' T2 = {0!r},\n'.format(self.T2)
|
|
|
|
if len(self.efficiencies) > 0:
|
|
string += u' efficiencies = {\n'
|
|
for molecule in sorted(self.efficiencies):
|
|
string += u' "{0}": {1:g},\n'.format(molecule, self.efficiencies[molecule])
|
|
string += u' },\n'
|
|
|
|
if self.Tmin is not None: string += ' Tmin = {0!r},\n'.format(self.Tmin)
|
|
if self.Tmax is not None: string += ' Tmax = {0!r},\n'.format(self.Tmax)
|
|
if self.Pmin is not None: string += ' Pmin = {0!r},\n'.format(self.Pmin)
|
|
if self.Pmax is not None: string += ' Pmax = {0!r},\n'.format(self.Pmax)
|
|
if self.comment != '': string += ' comment = """{0}""",\n'.format(self.comment)
|
|
return string + u')'
|
|
|
|
def __reduce__(self):
|
|
"""
|
|
A helper function used when pickling a Troe object.
|
|
"""
|
|
return (Troe, (self.arrheniusLow, self.arrheniusHigh, self.efficiencies, self.alpha, self.T3, self.T1, self.T2, self.Tmin, self.Tmax, self.Pmin, self.Pmax, self.comment))
|
|
|
|
################################################################################
|
|
|
|
class TransportData(object):
|
|
def __init__(self, label, geometry, wellDepth, collisionDiameter,
|
|
dipoleMoment, polarizability, zRot, comment=None):
|
|
|
|
assert isinstance(label, types.StringTypes)
|
|
assert int(geometry) in (0,1,2)
|
|
|
|
self.label = label
|
|
self.geometry = int(geometry)
|
|
self.wellDepth = float(wellDepth)
|
|
self.collisionDiameter = float(collisionDiameter)
|
|
self.dipoleMoment = float(dipoleMoment)
|
|
self.polarizability = float(polarizability)
|
|
self.zRot = float(zRot)
|
|
self.comment = comment or ''
|
|
|
|
def __repr__(self):
|
|
return ('TransportData({label!r}, {geometry!r}, {wellDepth!r}, '
|
|
'{collisionDiameter!r}, {dipoleMoment!r}, {polarizability!r}, '
|
|
'{zRot!r}, {comment!r})').format(**self.__dict__)
|
|
|
|
################################################################################
|
|
|
|
def readThermoEntry(entry):
|
|
"""
|
|
Read a thermodynamics `entry` for one species in a Chemkin file. Returns
|
|
the label of the species, the thermodynamics model as a :class:`MultiNASA`
|
|
object and the elemental composition of the species.
|
|
"""
|
|
lines = entry.splitlines()
|
|
species = str(lines[0][0:24].split()[0].strip())
|
|
|
|
# Extract the NASA polynomial coefficients
|
|
# Remember that the high-T polynomial comes first!
|
|
try:
|
|
Tmin = float(lines[0][45:55].strip())
|
|
Tmax = float(lines[0][55:65].strip())
|
|
Tint = float(lines[0][65:75].strip())
|
|
|
|
a0_high = float(lines[1][0:15].strip())
|
|
a1_high = float(lines[1][15:30].strip())
|
|
a2_high = float(lines[1][30:45].strip())
|
|
a3_high = float(lines[1][45:60].strip())
|
|
a4_high = float(lines[1][60:75].strip())
|
|
|
|
a5_high = float(lines[2][0:15].strip())
|
|
a6_high = float(lines[2][15:30].strip())
|
|
a0_low = float(lines[2][30:45].strip())
|
|
a1_low = float(lines[2][45:60].strip())
|
|
a2_low = float(lines[2][60:75].strip())
|
|
|
|
a3_low = float(lines[3][0:15].strip())
|
|
a4_low = float(lines[3][15:30].strip())
|
|
a5_low = float(lines[3][30:45].strip())
|
|
a6_low = float(lines[3][45:60].strip())
|
|
except (IndexError, ValueError):
|
|
raise ChemkinError('Error while reading thermo entry for species {0}'.format(species))
|
|
|
|
elements = lines[0][24:44]
|
|
composition = {}
|
|
for i in range(4):
|
|
symbol = elements[5*i:5*i+2].strip()
|
|
count = elements[5*i+2:5*i+5].strip()
|
|
if not symbol:
|
|
continue
|
|
try:
|
|
count = int(float(count))
|
|
if count:
|
|
composition[symbol.capitalize()] = count
|
|
except ValueError:
|
|
pass
|
|
|
|
# Construct and return the thermodynamics model
|
|
thermo = MultiNASA(
|
|
polynomials = [
|
|
NASA(Tmin=(Tmin,"K"), Tmax=(Tint,"K"), coeffs=[a0_low, a1_low, a2_low, a3_low, a4_low, a5_low, a6_low]),
|
|
NASA(Tmin=(Tint,"K"), Tmax=(Tmax,"K"), coeffs=[a0_high, a1_high, a2_high, a3_high, a4_high, a5_high, a6_high])
|
|
],
|
|
Tmin = (Tmin,"K"),
|
|
Tmax = (Tmax,"K"),
|
|
)
|
|
|
|
return species, thermo, composition
|
|
|
|
################################################################################
|
|
|
|
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() not in ['moles']:
|
|
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
|
|
if '<=>' in reaction:
|
|
reversible = True
|
|
reactants, products = reaction.split('<=>')
|
|
elif '=>' in reaction:
|
|
reversible = False
|
|
reactants, products = reaction.split('=>')
|
|
elif '=' in reaction:
|
|
reversible = True
|
|
reactants, products = reaction.split('=')
|
|
else:
|
|
raise ChemkinError("Failed to find reactant/product delimiter in reaction string.")
|
|
|
|
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
|
|
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()
|
|
tokens = [t.strip() for t in tokens]
|
|
if 'TCHEB' in line:
|
|
index = tokens.index('TCHEB')
|
|
tokens2 = tokens[index+1].split()
|
|
chebyshev.Tmin = float(tokens2[0].strip())
|
|
chebyshev.Tmax = float(tokens2[1].strip())
|
|
if 'PCHEB' in line:
|
|
index = tokens.index('PCHEB')
|
|
tokens2 = tokens[index+1].split()
|
|
chebyshev.Pmin = float(tokens2[0].strip())
|
|
chebyshev.Pmax = float(tokens2[1].strip())
|
|
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 = np.zeros((chebyshev.degreeT,chebyshev.degreeP), np.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[collider.strip()] = 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
|
|
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(arrheniusHigh=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
|
|
|
|
################################################################################
|
|
|
|
def loadChemkinFile(path):
|
|
"""
|
|
Load a Chemkin input file to `path` on disk, returning lists of the species
|
|
and reactions in the Chemkin file.
|
|
"""
|
|
|
|
speciesList = []; speciesDict = {}
|
|
reactionList = []
|
|
transportLines = []
|
|
|
|
def removeCommentFromLine(line):
|
|
if '!' in line:
|
|
index = line.index('!')
|
|
comment = line[index+1:-1]
|
|
line = line[0:index] + '\n'
|
|
return line, comment
|
|
else:
|
|
comment = ''
|
|
return line, comment
|
|
|
|
with open(path, 'r') as f:
|
|
line = f.readline()
|
|
while line != '':
|
|
line = removeCommentFromLine(line)[0]
|
|
line = line.strip()
|
|
tokens = line.split()
|
|
|
|
if 'SPECIES' in line:
|
|
# List of species identifiers
|
|
index = tokens.index('SPECIES')
|
|
tokens = tokens[index+1:]
|
|
while 'END' not in tokens:
|
|
line = f.readline()
|
|
line = removeCommentFromLine(line)[0]
|
|
line = line.strip()
|
|
tokens.extend(line.split())
|
|
|
|
for token in tokens:
|
|
if token == 'END':
|
|
break
|
|
if token in speciesDict:
|
|
species = speciesDict[token]
|
|
else:
|
|
species = Species(label=token)
|
|
speciesDict[token] = species
|
|
speciesList.append(species)
|
|
|
|
elif 'THERM' in line:
|
|
# List of thermodynamics (hopefully one per species!)
|
|
line = f.readline()
|
|
thermo = ''
|
|
while line != '' and 'END' not in line:
|
|
line = removeCommentFromLine(line)[0]
|
|
if len(line) >= 80:
|
|
if line[79] in ['1', '2', '3', '4']:
|
|
thermo += line
|
|
if line[79] == '4':
|
|
label, thermo, comp = readThermoEntry(thermo)
|
|
try:
|
|
speciesDict[label].thermo = thermo
|
|
speciesDict[label].composition = comp
|
|
except KeyError:
|
|
logging.warning('Skipping unexpected species "{0}" while reading thermodynamics entry.'.format(label))
|
|
thermo = ''
|
|
line = f.readline()
|
|
|
|
elif 'REACTIONS' in line:
|
|
# Reactions section
|
|
energyUnits = 'CAL/MOL'
|
|
moleculeUnits = 'MOLES'
|
|
try:
|
|
energyUnits = tokens[1]
|
|
moleculeUnits = tokens[2]
|
|
except IndexError:
|
|
pass
|
|
|
|
kineticsList = []
|
|
commentsList = []
|
|
kinetics = ''
|
|
comments = ''
|
|
|
|
line = f.readline()
|
|
while line != '' and 'END' not in line:
|
|
|
|
lineStartsWithComment = line.startswith('!')
|
|
line, comment = removeCommentFromLine(line)
|
|
line = line.strip(); comment = comment.strip()
|
|
|
|
if 'rev' in line or 'REV' in line:
|
|
# can no longer name reactants rev...
|
|
line = f.readline()
|
|
|
|
if '=' in line and not lineStartsWithComment:
|
|
# Finish previous record
|
|
kineticsList.append(kinetics)
|
|
commentsList.append(comments)
|
|
kinetics = ''
|
|
comments = ''
|
|
|
|
if line: kinetics += line + '\n'
|
|
if comment: comments += comment + '\n'
|
|
|
|
line = f.readline()
|
|
|
|
# Don't forget the last reaction!
|
|
if kinetics.strip() != '':
|
|
kineticsList.append(kinetics)
|
|
commentsList.append(comments)
|
|
|
|
if kineticsList[0] == '' and commentsList[-1] == '':
|
|
# True for Chemkin files generated from RMG-Py
|
|
kineticsList.pop(0)
|
|
commentsList.pop(-1)
|
|
elif kineticsList[0] == '' and commentsList[0] == '':
|
|
# True for Chemkin files generated from RMG-Java
|
|
kineticsList.pop(0)
|
|
commentsList.pop(0)
|
|
else:
|
|
# In reality, comments can occur anywhere in the Chemkin
|
|
# file (e.g. either or both of before and after the
|
|
# reaction equation)
|
|
# If we can't tell what semantics we are using, then just
|
|
# throw the comments away
|
|
# (This is better than failing to load the Chemkin file at
|
|
# all, which would likely occur otherwise)
|
|
if kineticsList[0] == '':
|
|
kineticsList.pop(0)
|
|
if len(kineticsList) != len(commentsList):
|
|
commentsList = ['' for kinetics in kineticsList]
|
|
|
|
for kinetics, comments in zip(kineticsList, commentsList):
|
|
reaction = readKineticsEntry(kinetics, speciesDict, energyUnits, moleculeUnits)
|
|
reactionList.append(reaction)
|
|
|
|
elif 'TRAN' in line:
|
|
line = f.readline()
|
|
while 'END' not in line:
|
|
transportLines.append(line)
|
|
|
|
line = f.readline()
|
|
|
|
# Check for marked (and unmarked!) duplicate reactions
|
|
# Raise exception for unmarked duplicate reactions
|
|
for index1 in range(len(reactionList)):
|
|
reaction1 = reactionList[index1]
|
|
for index2 in range(index1+1, len(reactionList)):
|
|
reaction2 = reactionList[index2]
|
|
if reaction1.reactants == reaction2.reactants and reaction1.products == reaction2.products:
|
|
if reaction1.duplicate and reaction2.duplicate:
|
|
pass
|
|
elif reaction1.kinetics.isPressureDependent() == reaction2.kinetics.isPressureDependent():
|
|
# If both reactions are pressure-independent or both are pressure-dependent, then they need duplicate tags
|
|
# Chemkin treates pdep and non-pdep reactions as different, so those are okay
|
|
raise ChemkinError('Encountered unmarked duplicate reaction {0}.'.format(reaction1))
|
|
|
|
index = 0
|
|
for reaction in reactionList:
|
|
index += 1
|
|
reaction.index = index
|
|
|
|
if transportLines:
|
|
parseTransportData(transportLines, speciesList)
|
|
|
|
return speciesList, reactionList
|
|
|
|
################################################################################
|
|
|
|
def parseTransportData(lines, speciesList):
|
|
"""
|
|
Parse the Chemkin-format transport data in ``lines`` (a list of strings)
|
|
and add that transport data to the species in ``speciesList``.
|
|
"""
|
|
speciesDict = dict((species.label, species) for species in speciesList)
|
|
for line in lines:
|
|
line = line.strip()
|
|
if not line or line.startswith('!'):
|
|
continue
|
|
|
|
data = line.split()
|
|
if len(data) < 7:
|
|
raise ChemkinError('Unable to parse transport data: not enough parameters')
|
|
if len(data) >= 8:
|
|
# comment may contain spaces. Rejoin into a single field.
|
|
comment = ''.join(data[7:]).lstrip('!')
|
|
data = data[:7] + [comment]
|
|
|
|
speciesName = data[0]
|
|
if speciesName in speciesDict:
|
|
speciesDict[speciesName].transport = TransportData(*data)
|
|
|
|
################################################################################
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
species, reactions = loadChemkinFile(sys.argv[1])
|
|
|
|
if len(sys.argv) > 2:
|
|
lines = open(sys.argv[2]).readlines()
|
|
parseTransportData(lines, species)
|
|
|
|
for s in species:
|
|
print s
|
|
print
|
|
for r in reactions:
|
|
print r
|