# -*- coding: utf-8 -*-
"""Chemkin-format mechanism interpreter module.
"""
# Python 2 compatibility
from __future__ import division
# Standard libraries
import sys
import math
import re
from copy import deepcopy
import logging
import numpy as np
# Local imports
from .. import utils
from . import chem_utilities as chem
# Related module
CANTERA_FLAG = False
try:
import cantera as ct
version = ct.__version__.split('.')
if int(version[0]) < 2 or int(version[1]) < 3:
print('Parsing of Cantera mechanisms requires at least version 2.3.0 in order to access species thermo properties...')
print('Detected version is only {}'.format(ct.__version__))
sys.exit(1)
CANTERA_FLAG = True
except ImportError:
CANTERA_FLAG = False
pre_units = ['moles', 'molecules']
"""list(`str`): Supported units list for pre-exponential factor"""
act_energy_units = ['kelvins', 'evolts', 'cal/mole', 'joules/kmole',
'kcal/mole', 'joules/mole', 'kjoules/mole'
]
"""list(`str`): Supported units list for activation energy"""
act_energy_fact = dict({'kelvins': 1.0,
'evolts': 11595.,
'cal/mole': 4.184 / chem.RU_JOUL,
'kcal/mole': 4184. / chem.RU_JOUL,
'joules/mole': 1. / chem.RU_JOUL,
'kjoules/mole': 1000.0 / chem.RU_JOUL,
'joules/kmole': 1. / (chem.RU_JOUL * 1000.)
})
"""dict: Activation energy conversion factor"""
# get local element atomic weight dict
elem_wt = chem.get_elem_wt()
[docs]def read_mech(mech_filename, therm_filename):
"""Read and interpret mechanism file for elements, species, and reactions.
Parameters
----------
mech_filename : str
Reaction mechanism filename (e.g. 'mech.dat')
therm_filename : str, optional
Thermodynamic database filename (e.g., 'therm.dat')
Returns
-------
elems : list of str
List of elements in mechanism.
specs : list of `SpecInfo`
List of species in mechanism.
reacs : list of `ReacInfo`
List of reactions in mechanism.
units : str
Units of reactions' Arrhenius coefficients
Notes
-----
Doesn't support element names with digits.
"""
elems = []
reacs = []
specs = []
key = ''
with open(mech_filename, 'r') as file:
# start line reading loop
while True:
line = file.readline()
# end of file
if not line: break
# skip blank or commented lines
if re.search('^\s*$', line) or re.search('^\s*!', line): continue
# don't convert to lowercase, since thermo
# needs to match (for Chemkin)
# Remove trailing and leading whitespace, tabs, newline
line = line.strip()
# remove any comments from end of line
ind = line.find('!')
if ind > 0: line = line[0:ind]
# now determine key
if line[0:4].lower() == 'elem':
key = 'elem'
# check for any entries on this line
line_split = line.split()
if len(line_split) > 1:
ind = line.index(line_split[1])
line = line[ind:]
else:
continue
elif line[0:4].lower() == 'spec':
key = 'spec'
# check for any entries on this line
line_split = line.split()
if len(line_split) > 1:
ind = line.index(line_split[1])
line = line[ind:]
else:
continue
elif line[0:4].lower() == 'reac':
key = 'reac'
# default units from Chemkin
units_E = 'cal/mole'
units_A = 'moles'
# get Arrhenius coefficient units (if specified)
for unit in line.split()[1:]:
if unit.lower() in pre_units:
units_A = unit.lower()
elif unit.lower() in act_energy_units:
units_E = unit.lower()
else:
print('Error: unsupported units on REACTION line.')
print('For pre-exponential factor, choose from: ' +
pre_units
)
print('For activation energy, choose from: ' +
act_energy_units
)
print('Otherwise leave blank for moles and cal/mole.')
sys.exit(1)
if units_A == 'molecules':
raise NotImplementedError('Molecules units not '
'supported, sorry.'
)
continue
elif line[0:4].lower() == 'ther':
# thermo data is in mechanism file
read_thermo(mech_filename, elems, specs)
continue
elif line[0:3].lower() == 'end':
key = ''
continue
if key == 'elem':
# if any atomic weight declarations, replace / with spaces
line = line.replace('/', ' ')
line_split = line.split()
e_last = ''
for e in line_split:
if e.isalpha():
if e[0:3] == 'end': continue
if e not in elems:
elems.append(e)
e_last = e
else:
# either add new element or update existing
# atomic weight
elem_wt[e_last.lower()] = float(e)
elif key == 'spec':
line_split = line.split()
for s in line_split:
if s[0:3] == 'end': continue
if not next((sp for sp in specs if sp.name == s), None):
specs.append(chem.SpecInfo(s))
elif key == 'reac':
# determine if reaction or auxiliary info line
if '=' in line:
# new reaction
cheb_flag = False
# get Arrhenius coefficients
line_split = line.split()
n = len(line_split)
reac_A = float(line_split[n - 3])
reac_b = float(line_split[n - 2])
reac_E = float(line_split[n - 1])
ind = line.index(line_split[n - 3])
line = line[0:ind].strip()
if '<=>' in line:
ind = line.index('<=>')
reac_rev = True
reac_str = line[0:ind].strip()
prod_str = line[ind + 3:].strip()
elif '=>' in line:
ind = line.index('=>')
reac_rev = False
reac_str = line[0:ind].strip()
prod_str = line[ind + 2:].strip()
else:
ind = line.index('=')
reac_rev = True
reac_str = line[0:ind].strip()
prod_str = line[ind + 1:].strip()
thd = False
pdep = False
pdep_sp = ''
reac_spec = []
reac_nu = []
prod_spec = []
prod_nu = []
# reactants
# look for third-body species
sub_str = reac_str
while '(' in sub_str:
ind1 = sub_str.find('(')
ind2 = sub_str.find(')')
# Need to check if '+' is first character inside
# parentheses and not embedded within parentheses
# (e.g., '(+)').
# If not, part of species name.
inParen = sub_str[ind1 + 1: ind2].strip()
if inParen is '+':
# '+' embedded within parentheses
sub_str = sub_str[ind2 + 1:]
elif inParen[0] is '+':
pdep = True
# either 'm' or a specific species
pdep_sp = sub_str[ind1 + 1: ind2].replace('+', ' ')
pdep_sp = pdep_sp.strip()
if pdep_sp.lower() == 'm':
thd = True
pdep_sp = ''
# now remove from string
ind = reac_str.find(sub_str)
reac_str = (reac_str[0: ind1 + ind] +
reac_str[ind2 + ind + 1:]
)
break
else:
# Part of species name, remove from substring
# and look at rest of reactant line.
sub_str = sub_str[ind2 + 1:]
reac_list = reac_str.split('+')
# Check for empty list elements, meaning there were
# multiple '+' in a row, which indicates species'
# name ended in '+'.
while '' in reac_list:
ind = reac_list.index('')
reac_list[ind - 1] = reac_list[ind - 1] + '+'
del reac_list[ind]
# check for any species with '(+)' that was split apart
for sp in reac_list:
ind = reac_list.index(sp)
# ensure not last entry
if (ind < len(reac_list) - 1):
spNext = reac_list[ind + 1]
if sp[len(sp) - 1] is '(' and spNext[0] is ')':
reac_list[ind] = sp + '+' + spNext
del reac_list[ind + 1]
for sp in reac_list:
sp = sp.strip()
# look for coefficient
if sp[0:1].isdigit():
# starts with number (coefficient)
# search for first letter
for i in range(len(sp)):
if sp[i: i + 1].isalpha(): break
nu = sp[0:i]
if '.' in nu:
# float
nu = float(nu)
else:
# integer
nu = int(nu)
sp = sp[i:].strip()
else:
# no coefficient given
nu = 1
# check for third body
if sp.lower() == 'm':
thd = True
continue
# check if species already in reaction
if sp not in reac_spec:
# new reactant
reac_spec.append(sp)
reac_nu.append(nu)
else:
# existing reactant
i = reac_spec.index(sp)
reac_nu[i] += nu
# products
# look for third-body species
sub_str = prod_str
while '(' in sub_str:
ind1 = sub_str.find('(')
ind2 = sub_str.find(')')
# Need to check if '+' is first character inside
# parentheses and not embedded within parentheses
# (e.g., '(+)'). If not, part of species name.
inParen = sub_str[ind1 + 1: ind2].strip()
if inParen is '+':
# '+' embedded within parentheses
sub_str = sub_str[ind2 + 1:]
elif inParen[0] is '+':
pdep = True
# either 'm' or a specific species
pdep_sp = sub_str[ind1 + 1: ind2].replace('+', ' ')
pdep_sp = pdep_sp.strip()
if pdep_sp.lower() == 'm':
thd = True
pdep_sp = ''
# now remove from string
ind = prod_str.find(sub_str)
prod_str = (prod_str[0: ind1 + ind] +
prod_str[ind2 + ind + 1:]
)
break
else:
# Part of species name, remove from substring and
# look at rest of product line.
sub_str = sub_str[ind2 + 1:]
prod_list = prod_str.split('+')
# Check for empty list elements, meaning there were
# multiple '+' in a row, which indicates species
# name ended in '+'.
while '' in prod_list:
ind = prod_list.index('')
prod_list[ind - 1] = prod_list[ind - 1] + '+'
del prod_list[ind]
# check for any species with '(+)' that was split apart
for sp in prod_list:
ind = prod_list.index(sp)
# ensure not last entry
if (ind < len(prod_list) - 1):
spNext = prod_list[ind + 1]
if sp[len(sp) - 1] is '(' and spNext[0] is ')':
prod_list[ind] = sp + '+' + spNext
del prod_list[ind + 1]
for sp in prod_list:
sp = sp.strip()
# look for coefficient
if sp[0:1].isdigit():
# starts with number (coefficient)
# search for first letter
for i in range(len(sp)):
if sp[i: i + 1].isalpha(): break
nu = sp[0:i]
if '.' in nu:
# float
nu = float(nu)
else:
# integer
nu = int(nu)
sp = sp[i:].strip()
else:
# no coefficient given
nu = 1
# check for third body
if sp in ['m', 'M']:
thd = True
continue
# check if species already in reaction
if sp not in prod_spec:
# new product
prod_spec.append(sp)
prod_nu.append(nu)
else:
# existing product
i = prod_spec.index(sp)
prod_nu[i] += nu
# Don't want to confuse third-body and pressure-dependent
# reactions... they are different!
if pdep:
thd = False
# Convert given activation energy units to internal units
reac_E *= act_energy_fact[units_E]
# Convert given pre-exponential units to internal units
if units_A == 'moles':
reac_ord = sum(reac_nu)
if thd:
reac_A /= 1000. ** reac_ord
elif pdep:
# Low- (chemically activated bimolecular reaction) or
# high-pressure (fall-off reaction) limit parameters
reac_A /= 1000. ** (reac_ord - 1.)
else:
# Elementary reaction
reac_A /= 1000. ** (reac_ord - 1.)
# add reaction to list
reac = chem.ReacInfo(reac_rev, reac_spec, reac_nu,
prod_spec, prod_nu, reac_A, reac_b,
reac_E
)
reac.thd_body = thd
reac.pdep = pdep
if pdep: reac.pdep_sp = pdep_sp
reacs.append(reac)
else:
# auxiliary reaction info
aux = line[0:3].lower()
if aux == 'dup':
reacs[-1].dup = True
elif aux == 'rev':
line = line.replace('/', ' ')
line = line.replace(',', ' ')
line_split = line.split()
par1 = float(line_split[1])
par2 = float(line_split[2])
par3 = float(line_split[3])
# Convert reverse activation energy units
par3 *= act_energy_fact[units_E]
# Convert reverse pre-exponential factor
if units_A == 'moles':
reac_ord = sum(reacs[-1].prod_nu)
if thd:
par1 /= 1000. ** reac_ord
elif pdep:
# Low- (chemically activated bimolecular reaction) or
# high-pressure (fall-off reaction) limit parameters
par1 /= 1000. ** (reac_ord - 1.)
else:
# Elementary reaction
par1 /= 1000. ** (reac_ord - 1.)
# Ensure nonzero reverse coefficients
if par1 != 0.0:
reacs[-1].rev_par.append(par1)
reacs[-1].rev_par.append(par2)
reacs[-1].rev_par.append(par3)
else:
reacs[-1].rev = False
elif aux == 'low':
line = line.replace('/', ' ')
line = line.replace(',', ' ')
line_split = line.split()
par1 = float(line_split[1])
par2 = float(line_split[2])
par3 = float(line_split[3])
# Convert low-pressure activation energy units
par3 *= act_energy_fact[units_E]
# Convert low-pressure pre-exponential factor
if units_A == 'moles':
par1 /= 1000. ** sum(reacs[-1].reac_nu)
reacs[-1].low.append(par1)
reacs[-1].low.append(par2)
reacs[-1].low.append(par3)
elif aux == 'hig':
line = line.replace('/', ' ')
line = line.replace(',', ' ')
line_split = line.split()
par1 = float(line_split[1])
par2 = float(line_split[2])
par3 = float(line_split[3])
# Convert high-pressure activation energy units
par3 *= act_energy_fact[units_E]
# Convert high-pressure pre-exponential factor
if units_A == 'moles':
par1 /= 1000. ** (sum(reacs[-1].reac_nu) - 2.)
reacs[-1].high.append(par1)
reacs[-1].high.append(par2)
reacs[-1].high.append(par3)
elif aux == 'tro':
line = line.replace('/', ' ')
line = line.replace(',', ' ')
line_split = line.split()
reacs[-1].troe = True
par1 = float(line_split[1])
par2 = float(line_split[2])
par3 = float(line_split[3])
do_warn = False
if par2 == 0:
do_warn=True
par2 = 1e-30
if par3 == 0:
do_warn=True
par3 = 1e-30
if do_warn:
logging.warn('Troe parameters in reaction {} modified to avoid'
' division by zero!.'.format(len(reacs)))
reacs[-1].troe_par.append(par1)
reacs[-1].troe_par.append(par2)
reacs[-1].troe_par.append(par3)
# optional fourth parameter
if len(line_split) > 4:
par4 = float(line_split[4])
reacs[-1].troe_par.append(par4)
elif aux == 'sri':
line = line.replace('/', ' ')
line = line.replace(',', ' ')
line_split = line.split()
reacs[-1].sri = True
par1 = float(line_split[1])
par2 = float(line_split[2])
par3 = float(line_split[3])
reacs[-1].sri_par.append(par1)
reacs[-1].sri_par.append(par2)
reacs[-1].sri_par.append(par3)
# optional fourth and fifth parameters
if len(line_split) > 4:
par4 = float(line_split[4])
par5 = float(line_split[5])
reacs[-1].sri_par.append(par4)
reacs[-1].sri_par.append(par5)
elif aux == 'che':
reacs[-1].cheb = True
line = line.replace('/', ' ')
line_split = line.split()
if cheb_flag:
for par in line_split[1:]:
reacs[-1].cheb_par.append(float(par))
else:
# first CHEB line
cheb_flag = True
# Don't want Cheb reactions lumped in with
# standard falloff.
reacs[-1].pdep = False
reacs[-1].cheb_n_temp = int(line_split[1])
reacs[-1].cheb_n_pres = int(line_split[2])
reacs[-1].cheb_par = []
for par in line_split[3:]:
reacs[-1].cheb_par.append(float(par))
elif aux == 'pch':
line = line.replace('/', ' ')
line_split = line.split()
# Convert pressure from atm to Pa
reacs[-1].cheb_plim = [float(line_split[1]) * chem.PA,
float(line_split[2]) * chem.PA
]
# Look for temperature limits on same line:
if line_split[3].lower() == 'tcheb':
reacs[-1].cheb_tlim = [float(line_split[4]),
float(line_split[5])
]
elif aux == 'tch':
line = line.replace('/', ' ')
line_split = line.split()
reacs[-1].cheb_tlim = [float(line_split[1]),
float(line_split[2])
]
# Look for pressure limits on same line:
if line_split[3].lower() == 'pcheb':
reacs[-1].cheb_plim = [float(line_split[4]) * chem.PA,
float(line_split[5]) * chem.PA
]
elif aux == 'plo':
line = line.replace('/', ' ')
line_split = line.split()
if not reacs[-1].plog:
reacs[-1].plog = True
# Don't want Plog reactions lumped in with
# standard falloff.
reacs[-1].pdep = False
reacs[-1].plog_par = []
pars = [float(n) for n in line_split[1:5]]
# Convert pressure from atm to Pa
pars[0] *= 101325.0
# Convert given activation energy units to internal units
pars[3] *= act_energy_fact[units_E]
# Convert given pre-exponential units to internal units
if units_A == 'moles':
reac_ord = sum(reacs[-1].reac_nu)
# Looks like elementary reaction
pars[1] /= 1000. ** (reac_ord - 1.)
reacs[-1].plog_par.append(pars)
else:
# enhanced third body efficiencies
line = line.replace('/', ' ')
line_split = line.split()
for i in range(0, len(line_split), 2):
pair = [line_split[i], float(line_split[i + 1])]
reacs[-1].thd_body_eff.append(pair)
# process some reaction auxiliary info
for idx, reac in enumerate(reacs):
if reac.cheb:
# check for correct number
n = reac.cheb_n_temp
m = reac.cheb_n_pres
if len(reac.cheb_par) != n * m:
print('Error: incorrect number of CHEB coefficients in '
'reaction ' + repr(idx)
)
sys.exit(1)
else:
# Convert units of first Chebyshev parameter
order = sum(reac.reac_nu)
if units_A == 'moles':
reac.cheb_par[0] += math.log10(0.001 ** (order - 1.))
reacs[idx].cheb_par = np.reshape(reac.cheb_par, (n, m))
# check that all species in reactions correspond to a known species
spec_names = set(spec.name for spec in specs)
for idx, reac in enumerate(reacs):
in_rxn = set(reac.reac + reac.prod)
for spec in in_rxn:
if spec not in spec_names:
logger = logging.getLogger(__name__)
logger.error('Reaction {} contains unknown species {}'.format(
idx, spec))
sys.exit(-1)
# Split reversible reactions with explicit reverse parameters into
# two irreversible reactions to match Cantera's behavior
for reac in reacs[:]:
if reac.rev_par:
new_reac = deepcopy(reac)
idx = reacs.index(reac)
reacs[idx].rev = False
reacs[idx].rev_par = []
new_reac.A = new_reac.rev_par[0]
new_reac.b = new_reac.rev_par[1]
new_reac.E = new_reac.rev_par[2]
new_reac.rev = False
new_reac.rev_par = []
new_reac.prod = reac.reac[:]
new_reac.prod_nu = reac.reac_nu[:]
new_reac.reac = reac.prod[:]
new_reac.reac_nu = reac.prod_nu[:]
reacs.insert(idx + 1, new_reac)
# Read seperate thermo file if present and needed
if any([not sp.mw for sp in specs]):
if therm_filename:
read_thermo(therm_filename, elems, specs)
else:
print('Error: no thermo file specified, but species missing \n'
'data. Either specify file, or ensure complete data in\n'
'mechanism file with THERMO option.'
)
sys.exit(1)
# Check for missing thermo data again
missing_mw = [sp.name for sp in specs if not sp.mw]
if missing_mw:
print('Error: missing thermo data for ' + ', '.join(missing_mw))
sys.exit(1)
return (elems, specs, reacs)
[docs]def read_thermo(filename, elems, specs):
"""Read and interpret thermodynamic database for species data.
Reads the thermodynamic file and returns the species thermodynamic
coefficients as well as the species-specific temperature range
values (if given).
Parameters
----------
filename : str
Name of thermo database file.
elems : list of str
List of element names in mechanism.
specs : list of `SpecInfo`
List of species in mechanism.
Returns
-------
None
"""
with open(filename, 'r') as file:
# loop through intro lines
while True:
line = file.readline()
# skip blank or commented lines
if re.search('^\s*$', line) or re.search('^\s*!', line): continue
# skip 'thermo' at beginning
if 'thermo' in line.lower(): break
# next line either has common temperature ranges or first species
last_line = file.tell()
line = file.readline()
line_split = line.split()
if line_split[0][0:1].isdigit():
T_ranges = utils.read_str_num(line)
else:
# no common temperature info
file.seek(last_line)
# default
# now start reading species thermo info
while True:
# first line of species info
line = file.readline()
# don't convert to lowercase, needs to match thermo for Chemkin
# break if end of file
if line is None or line[0:3].lower() == 'end': break
# skip blank/commented line
if re.search('^\s*$', line) or re.search('^\s*!', line): continue
# species name, columns 0:18
spec = line[0:18].strip()
# Apparently, in some cases, notes are in the
# columns of shorter species names, so make
# sure no spaces.
if spec.find(' ') > 0:
spec = spec[0: spec.find(' ')]
# now need to determine if this species is in mechanism
if next((sp for sp in specs if sp.name == spec), None):
sp_ind = next(i for i in range(len(specs))
if specs[i].name == spec
)
else:
# not in mechanism, read next three lines and continue
line = file.readline()
line = file.readline()
line = file.readline()
continue
# set species to the one matched
spec = specs[sp_ind]
# ensure not reading the same species more than once...
if spec.mw:
# already done! skip next three lines
line = file.readline()
line = file.readline()
line = file.readline()
continue
# now get element composition of species, columns 24:44
# each piece of data is 5 characters long (2 for element, 3 for #)
elem_str = utils.split_str(line[24:44], 5)
for e_str in elem_str:
e = e_str[0:2].strip()
# skip if blank
if e == '' or e == '0': continue
# may need to convert to float first, in case of e.g. "1."
e_num = float(e_str[2:].strip())
e_num = int(e_num)
spec.elem.append([e, e_num])
# calculate molecular weight
spec.mw += e_num * elem_wt[e.lower()]
# temperatures for species
T_spec = utils.read_str_num(line[45:74])
T_low = T_spec[0]
T_high = T_spec[1]
if len(T_spec) == 3:
T_com = T_spec[2]
else:
T_com = T_ranges[1]
spec.Trange = [T_low, T_com, T_high]
# second species line
line = file.readline()
coeffs = utils.split_str(line[0:75], 15)
spec.hi[0] = float(coeffs[0])
spec.hi[1] = float(coeffs[1])
spec.hi[2] = float(coeffs[2])
spec.hi[3] = float(coeffs[3])
spec.hi[4] = float(coeffs[4])
# third species line
line = file.readline()
coeffs = utils.split_str(line[0:75], 15)
spec.hi[5] = float(coeffs[0])
spec.hi[6] = float(coeffs[1])
spec.lo[0] = float(coeffs[2])
spec.lo[1] = float(coeffs[3])
spec.lo[2] = float(coeffs[4])
# fourth species line
line = file.readline()
coeffs = utils.split_str(line[0:75], 15)
spec.lo[3] = float(coeffs[0])
spec.lo[4] = float(coeffs[1])
spec.lo[5] = float(coeffs[2])
spec.lo[6] = float(coeffs[3])
# stop reading if all species in mechanism accounted for
if not next((sp for sp in specs if sp.mw == 0.0), None): break
return None
[docs]def read_mech_ct(filename=None, gas=None):
"""Read and interpret Cantera-format mechanism file.
Parameters
----------
filename : str
Reaction mechanism filename (e.g. 'mech.cti'). Optional.
gas : `cantera.Solution` object
Existing Cantera Solution object to be used. Optional.
Returns
-------
elems : list of str
List of elements in mechanism.
specs : list of `SpecInfo`
List of species in mechanism.
reacs : list of `ReacInfo`
List of reactions in mechanism.
units : str
Units of reactions' Arrhenius coefficients
"""
if not CANTERA_FLAG:
print('Error: Cantera not installed. Cannot interpret '
'Cantera-format mechanism.')
sys.exit(1)
if filename:
gas = ct.Solution(filename)
elif not gas:
print('Error: need either filename or Cantera Solution object.')
sys.exit(1)
# Elements
elems = gas.element_names
for e, wt in zip(elems, gas.atomic_weights):
if e.lower() not in elem_wt:
elem_wt[e.lower()] = wt
# Species
specs = []
for i, sp in enumerate(gas.species_names):
spec = chem.SpecInfo(sp)
spec.mw = gas.molecular_weights[i]
# Get Species object
species = gas.species(i)
# Species elemental composition
for e in species.composition:
spec.elem.append([e, species.composition[e]])
# Species thermodynamic properties
coeffs = species.thermo.coeffs
spec.Trange = [species.thermo.min_temp, coeffs[0],
species.thermo.max_temp
]
if isinstance(species.thermo, ct.NasaPoly2):
spec.hi = coeffs[1:8]
spec.lo = coeffs[8:15]
else:
print('Error: unsupported thermo form for species ' + sp)
sys.exit(1)
specs.append(spec)
# Reactions
reacs = []
# Cantera internally uses joules/kmol for activation energy
E_fac = act_energy_fact['joules/kmole']
def handle_effiencies(reac, ct_rxn):
"""Convert Cantera `cantera.Reaction`'s third body efficienicies
to pyJac's internal format, and return updated reaction
Parameters
----------
reac : `ReacInfo`
The pyJac reaction to update
ct_rxn : `Reaction` object
Corresponding cantera reaction to pull the third bodies from
Returns
-------
updated_reac: `ReacInfo`
The updated pyjac reaction with appropriate third body efficiencies
"""
# See if single species acts as third body
if rxn.default_efficiency == 0.0 \
and len(ct_rxn.efficiencies.keys()) == 1\
and list(ct_rxn.efficiencies.values())[0] == 1\
and reac.pdep:
reac.pdep_sp = list(rxn.efficiencies.keys())[0]
else:
for sp in gas.species_names:
if sp in ct_rxn.efficiencies:
reac.thd_body_eff.append([sp, ct_rxn.efficiencies[sp]])
elif ct_rxn.default_efficiency != 1.0:
reac.thd_body_eff.append([sp, ct_rxn.default_efficiency])
return reac
for rxn in gas.reactions():
if isinstance(rxn, ct.ThreeBodyReaction):
# Instantiate internal reaction based on Cantera Reaction data.
reac = chem.ReacInfo(rxn.reversible,
list(rxn.reactants.keys()),
list(rxn.reactants.values()),
list(rxn.products.keys()),
list(rxn.products.values()),
rxn.rate.pre_exponential_factor,
rxn.rate.temperature_exponent,
rxn.rate.activation_energy * E_fac
)
reac.thd_body = True
reac = handle_effiencies(reac, rxn)
elif isinstance(rxn, ct.FalloffReaction) and \
not isinstance(rxn, ct.ChemicallyActivatedReaction):
reac = chem.ReacInfo(rxn.reversible,
list(rxn.reactants.keys()),
list(rxn.reactants.values()),
list(rxn.products.keys()),
list(rxn.products.values()),
rxn.high_rate.pre_exponential_factor,
rxn.high_rate.temperature_exponent,
rxn.high_rate.activation_energy * E_fac
)
reac.pdep = True
reac = handle_effiencies(reac, rxn)
reac.low = [rxn.low_rate.pre_exponential_factor,
rxn.low_rate.temperature_exponent,
rxn.low_rate.activation_energy * E_fac
]
if rxn.falloff.type == 'Troe':
reac.troe = True
reac.troe_par = rxn.falloff.parameters.tolist()
do_warn = False
if reac.troe_par[1] == 0:
reac.troe_par[1] = 1e-30
do_warn = True
if reac.troe_par[2] == 0:
reac.troe_par[2] = 1e-30
do_warn = True
if do_warn:
logging.warn('Troe parameters in reaction {} modified to avoid'
' division by zero!.'.format(len(reacs)))
elif rxn.falloff.type == 'SRI':
reac.sri = True
reac.sri_par = rxn.falloff.parameters.tolist()
elif isinstance(rxn, ct.ChemicallyActivatedReaction):
reac = chem.ReacInfo(rxn.reversible,
list(rxn.reactants.keys()),
list(rxn.reactants.values()),
list(rxn.products.keys()),
list(rxn.products.values()),
rxn.low_rate.pre_exponential_factor,
rxn.low_rate.temperature_exponent,
rxn.low_rate.activation_energy * E_fac
)
reac.pdep = True
reac = handle_effiencies(reac, rxn)
reac.high = [rxn.high_rate.pre_exponential_factor,
rxn.high_rate.temperature_exponent,
rxn.high_rate.activation_energy * E_fac
]
if rxn.falloff.type == 'Troe':
reac.troe = True
reac.troe_par = rxn.falloff.parameters.tolist()
do_warn = False
if reac.troe_par[1] == 0:
reac.troe_par[1] = 1e-30
do_warn = True
if reac.troe_par[2] == 0:
reac.troe_par[2] = 1e-30
do_warn = True
if do_warn:
logging.warn('Troe parameters in reaction {} modified to avoid'
' division by zero!.'.format(len(reacs)))
elif rxn.falloff.type == 'SRI':
reac.sri = True
reac.sri_par = rxn.falloff.parameters.tolist()
elif isinstance(rxn, ct.PlogReaction):
reac = chem.ReacInfo(rxn.reversible,
list(rxn.reactants.keys()),
list(rxn.reactants.values()),
list(rxn.products.keys()),
list(rxn.products.values()),
0.0, 0.0, 0.0
)
reac.plog = True
reac.plog_par = []
for rate in rxn.rates:
pars = [rate[0], rate[1].pre_exponential_factor,
rate[1].temperature_exponent,
rate[1].activation_energy * E_fac
]
reac.plog_par.append(pars)
elif isinstance(rxn, ct.ChebyshevReaction):
reac = chem.ReacInfo(rxn.reversible,
list(rxn.reactants.keys()),
list(rxn.reactants.values()),
list(rxn.products.keys()),
list(rxn.products.values()),
0.0, 0.0, 0.0
)
reac.cheb = True
reac.cheb_n_temp = rxn.nTemperature
reac.cheb_n_pres = rxn.nPressure
reac.cheb_plim = [rxn.Pmin, rxn.Pmax]
reac.cheb_tlim = [rxn.Tmin, rxn.Tmax]
reac.cheb_par = rxn.coeffs
elif isinstance(rxn, ct.ElementaryReaction):
# Instantiate internal reaction based on Cantera Reaction data.
# Ensure no reactions with zero pre-exponential factor allowed
if rxn.rate.pre_exponential_factor == 0.0:
continue
reac = chem.ReacInfo(rxn.reversible,
list(rxn.reactants.keys()),
list(rxn.reactants.values()),
list(rxn.products.keys()),
list(rxn.products.values()),
rxn.rate.pre_exponential_factor,
rxn.rate.temperature_exponent,
rxn.rate.activation_energy * E_fac
)
else:
print('Error: unsupported reaction.')
sys.exit(1)
reac.dup = rxn.duplicate
# No reverse reactions with explicit coefficients in Cantera.
reacs.append(reac)
return (elems, specs, reacs)