Skip to content

Commit 4aa10bd

Browse files
committed
Add surface mechanisms to regression tool
1 parent 042c944 commit 4aa10bd

File tree

3 files changed

+212
-42
lines changed

3 files changed

+212
-42
lines changed

rmgpy/tools/canteramodel.py

Lines changed: 100 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
###############################################################################
2929

3030
import os.path
31+
import logging
3132

3233
import cantera as ct
3334
import numpy as np
@@ -63,18 +64,45 @@ class CanteraCondition(object):
6364
6465
"""
6566

66-
def __init__(self, reactor_type, reaction_time, mol_frac, T0=None, P0=None, V0=None):
67+
def __init__(self, reactor_type, reaction_time, mol_frac, surface_mol_frac=None, T0=None, P0=None, V0=None):
6768
self.reactor_type = reactor_type
6869
self.reaction_time = Quantity(reaction_time)
6970

7071
# Normalize initialMolFrac if not already done:
7172
if sum(mol_frac.values()) != 1.00:
73+
logging.warning('Initial mole fractions do not sum to one; normalizing.')
74+
logging.info('')
75+
logging.info('Original composition:')
76+
for spec, molfrac in mol_frac.items():
77+
logging.info('{0} = {1}'.format(spec, molfrac))
7278
total = sum(mol_frac.values())
79+
logging.info('')
80+
logging.info('Normalized mole fractions:')
7381
for species, value in mol_frac.items():
7482
mol_frac[species] = value / total
83+
logging.info('{0} = {1}'.format(spec, mol_frac[species]))
84+
logging.info('')
7585

7686
self.mol_frac = mol_frac
7787

88+
if surface_mol_frac:
89+
# normalize surface mol fractions
90+
if sum(surface_mol_frac.values()) != 1.00:
91+
logging.warning('Initial surface mole fractions do not sum to one; normalizing.')
92+
logging.info('')
93+
logging.info('Original composition:')
94+
for spec, molfrac in surface_mol_frac.items():
95+
logging.info('{0} = {1}'.format(spec, molfrac))
96+
97+
logging.info('')
98+
logging.info('Normalized mole fractions:')
99+
total = sum(surface_mol_frac.values())
100+
for species, value in surface_mol_frac.items():
101+
surface_mol_frac[species] = value / total
102+
logging.info('{0} = {1}'.format(species, surface_mol_frac[species]))
103+
logging.info('')
104+
self.surface_mol_frac = surface_mol_frac
105+
78106
# Check to see that one of the three attributes T0, P0, and V0 is less unspecified
79107
props = [T0, P0, V0]
80108
total = 0
@@ -121,7 +149,7 @@ def __str__(self):
121149
return string
122150

123151

124-
def generate_cantera_conditions(reactor_type_list, reaction_time_list, mol_frac_list, Tlist=None, Plist=None, Vlist=None):
152+
def generate_cantera_conditions(reactor_type_list, reaction_time_list, mol_frac_list, surface_mol_frac_list=None, Tlist=None, Plist=None, Vlist=None):
125153
"""
126154
Creates a list of cantera conditions from from the arguments provided.
127155
@@ -137,6 +165,8 @@ def generate_cantera_conditions(reactor_type_list, reaction_time_list, mol_frac_
137165
`reaction_time_list` A tuple object giving the ([list of reaction times], units)
138166
`mol_frac_list` A list of molfrac dictionaries with species object keys
139167
and mole fraction values
168+
`surface_mol_frac_list` A list of molfrac dictionaries with surface species object keys
169+
and mole fraction values
140170
To specify the system for an ideal gas, you must define 2 of the following 3 parameters:
141171
`T0List` A tuple giving the ([list of initial temperatures], units)
142172
'P0List' A tuple giving the ([list of initial pressures], units)
@@ -162,30 +192,35 @@ def generate_cantera_conditions(reactor_type_list, reaction_time_list, mol_frac_
162192
for i in range(len(reaction_time_list.value))]
163193

164194
conditions = []
195+
if surface_mol_frac_list is None:
196+
surface_mol_frac_list = [] # initialize here to avoid mutable default argument
165197

166198
if Tlist is None:
167199
for reactor_type in reactor_type_list:
168200
for reaction_time in reaction_time_list:
169201
for mol_frac in mol_frac_list:
170-
for P in Plist:
171-
for V in Vlist:
172-
conditions.append(CanteraCondition(reactor_type, reaction_time, mol_frac, P0=P, V0=V))
202+
for surface_mol_frac in surface_mol_frac_list:
203+
for P in Plist:
204+
for V in Vlist:
205+
conditions.append(CanteraCondition(reactor_type, reaction_time, mol_frac, surface_mol_frac=surface_mol_frac, P0=P, V0=V))
173206

174207
elif Plist is None:
175208
for reactor_type in reactor_type_list:
176209
for reaction_time in reaction_time_list:
177210
for mol_frac in mol_frac_list:
178-
for T in Tlist:
179-
for V in Vlist:
180-
conditions.append(CanteraCondition(reactor_type, reaction_time, mol_frac, T0=T, V0=V))
211+
for surface_mol_frac in surface_mol_frac_list:
212+
for T in Tlist:
213+
for V in Vlist:
214+
conditions.append(CanteraCondition(reactor_type, reaction_time, mol_frac, surface_mol_frac=surface_mol_frac, T0=T, V0=V))
181215

182216
elif Vlist is None:
183217
for reactor_type in reactor_type_list:
184218
for reaction_time in reaction_time_list:
185219
for mol_frac in mol_frac_list:
186-
for T in Tlist:
187-
for P in Plist:
188-
conditions.append(CanteraCondition(reactor_type, reaction_time, mol_frac, T0=T, P0=P))
220+
for surface_mol_frac in surface_mol_frac_list:
221+
for T in Tlist:
222+
for P in Plist:
223+
conditions.append(CanteraCondition(reactor_type, reaction_time, mol_frac, surface_mol_frac=surface_mol_frac, T0=T, P0=P))
189224

190225
else:
191226
raise Exception("Cantera conditions must leave one of T0, P0, and V0 state variables unspecified")
@@ -198,7 +233,7 @@ class Cantera(object):
198233
"""
199234

200235
def __init__(self, species_list=None, reaction_list=None, canteraFile='', output_directory='', conditions=None,
201-
sensitive_species=None, thermo_SA=False):
236+
sensitive_species=None, thermo_SA=False, surface_species_list=None, surface_reaction_list=None):
202237
"""
203238
`species_list`: list of RMG species objects
204239
`reaction_list`: list of RMG reaction objects
@@ -208,24 +243,36 @@ def __init__(self, species_list=None, reaction_list=None, canteraFile='', output
208243
`sensitive_species`: a list of RMG species objects for conductng sensitivity analysis on
209244
`thermo_SA`: a boolean indicating whether or not to run thermo SA. By default, if sensitive_species is given,
210245
only kinetic_SA will be calculated and it must be additionally specified to perform thermo SA.
246+
`surface_species_list`: list of RMG species objects contianing the surface species. This is necessary for all surface mechanisms to initialize the starting mole fractions.
247+
`surface_reaction_list`: list of RMG reaction objects containing the surface reactions.
248+
For surface mechanisms, either canteraFile must be provided, or load_chemkin_model() must be called later on to create the Cantera file with the surface mechanism.
211249
"""
212250
self.species_list = species_list
213251
self.reaction_list = reaction_list
214252
self.reaction_map = {}
215253
self.model = ct.Solution(canteraFile) if canteraFile else None
254+
self.surface = bool(surface_species_list or surface_reaction_list)
255+
self.surface_species_list = surface_species_list
256+
self.surface_reaction_list = surface_reaction_list
216257
self.output_directory = output_directory if output_directory else os.getcwd()
217258
self.conditions = conditions if conditions else []
218259
self.sensitive_species = sensitive_species if sensitive_species else []
219260
self.thermo_SA = thermo_SA
220261

262+
if self.surface:
263+
assert surface_species_list, "Surface species list must be specified to run a surface simulation"
264+
if canteraFile:
265+
self.surface = ct.Interface(canteraFile, 'surface1')
266+
self.model = self.surface.adjacent['gas']
267+
221268
# Make output directory if it does not yet exist:
222269
if not os.path.exists(self.output_directory):
223270
try:
224271
os.makedirs(self.output_directory)
225272
except:
226273
raise Exception('Cantera output directory could not be created.')
227274

228-
def generate_conditions(self, reactor_type_list, reaction_time_list, mol_frac_list, Tlist=None, Plist=None, Vlist=None):
275+
def generate_conditions(self, reactor_type_list, reaction_time_list, mol_frac_list, surface_mol_frac_list=None, Tlist=None, Plist=None, Vlist=None):
229276
"""
230277
This saves all the reaction conditions into the Cantera class.
231278
======================= ====================================================
@@ -240,19 +287,19 @@ def generate_conditions(self, reactor_type_list, reaction_time_list, mol_frac_li
240287
`reaction_time_list` A tuple object giving the ([list of reaction times], units)
241288
`mol_frac_list` A list of molfrac dictionaries with species object keys
242289
and mole fraction values
290+
`surface_mol_frac_list` A list of molfrac dictionaries with surface species object keys and mole fraction values
243291
To specify the system for an ideal gas, you must define 2 of the following 3 parameters:
244292
`T0List` A tuple giving the ([list of initial temperatures], units)
245293
'P0List' A tuple giving the ([list of initial pressures], units)
246294
'V0List' A tuple giving the ([list of initial specific volumes], units)
247295
"""
248296

249-
self.conditions = generate_cantera_conditions(reactor_type_list, reaction_time_list, mol_frac_list, Tlist, Plist, Vlist)
297+
self.conditions = generate_cantera_conditions(reactor_type_list, reaction_time_list, mol_frac_list, surface_mol_frac_list, Tlist, Plist, Vlist)
250298

251299
def load_model(self):
252300
"""
253301
Load a cantera Solution model from the job's own species_list and reaction_list attributes
254302
"""
255-
256303
ct_species = [spec.to_cantera(use_chemkin_identifier=True) for spec in self.species_list]
257304

258305
self.reaction_map = {}
@@ -292,6 +339,7 @@ def load_chemkin_model(self, chemkin_file, transport_file=None, **kwargs):
292339
Convert a chemkin mechanism chem.inp file to a cantera mechanism file chem.yaml
293340
and save it in the output_directory
294341
Then load it into self.model
342+
Note that if this is a surface mechanism, the calling function much include surface_file as a keyword argument for the parser
295343
"""
296344
from cantera import ck2yaml
297345

@@ -301,9 +349,14 @@ def load_chemkin_model(self, chemkin_file, transport_file=None, **kwargs):
301349
if os.path.exists(out_name):
302350
os.remove(out_name)
303351
parser = ck2yaml.Parser()
352+
304353
parser.convert_mech(chemkin_file, transport_file=transport_file, out_name=out_name, **kwargs)
305354
self.model = ct.Solution(out_name)
306355

356+
if self.surface:
357+
self.surface = ct.Interface(out_name, 'surface1')
358+
self.model = self.surface.adjacent['gas']
359+
307360
def modify_reaction_kinetics(self, rmg_reaction_index, rmg_reaction):
308361
"""
309362
Modify the corresponding cantera reaction's kinetics to match
@@ -385,14 +438,23 @@ def simulate(self):
385438
species_names_list = [get_species_identifier(species) for species in self.species_list]
386439
inert_index_list = [self.species_list.index(species) for species in self.species_list if species.index == -1]
387440

441+
if self.surface:
442+
surface_species_names_list = [get_species_identifier(species) for species in self.surface_species_list]
443+
388444
all_data = []
389445
for condition in self.conditions:
390446

391447
# First translate the mol_frac from species objects to species names
392448
new_mol_frac = {}
393-
for key, value in condition.mol_frac.items():
394-
newkey = get_species_identifier(key)
395-
new_mol_frac[newkey] = value
449+
for rmg_species, mol_frac in condition.mol_frac.items():
450+
species_name = get_species_identifier(rmg_species)
451+
new_mol_frac[species_name] = mol_frac
452+
453+
if self.surface:
454+
new_surface_mol_frac = {}
455+
for rmg_species, mol_frac in condition.surface_mol_frac.items():
456+
species_name = get_species_identifier(rmg_species)
457+
new_surface_mol_frac[species_name] = mol_frac
396458

397459
# Set Cantera simulation conditions
398460
if condition.V0 is None:
@@ -413,6 +475,11 @@ def simulate(self):
413475
else:
414476
raise Exception('Other types of reactor conditions are currently not supported')
415477

478+
# add the surface/gas adjacent thing...
479+
if self.surface:
480+
rsurf = ct.ReactorSurface(self.surface, cantera_reactor)
481+
self.surface.TPX = condition.T0.value_si, condition.P0.value_si, new_surface_mol_frac
482+
416483
# Run this individual condition as a simulation
417484
cantera_simulation = ct.ReactorNet([cantera_reactor])
418485

@@ -451,7 +518,12 @@ def simulate(self):
451518
times.append(cantera_simulation.time)
452519
temperature.append(cantera_reactor.T)
453520
pressure.append(cantera_reactor.thermo.P)
454-
species_data.append(cantera_reactor.thermo[species_names_list].X)
521+
522+
if self.surface:
523+
species_data.append(np.concatenate((cantera_reactor.thermo[species_names_list].X, rsurf.kinetics.coverages)))
524+
N_gas = len(cantera_reactor.thermo[species_names_list].X)
525+
else:
526+
species_data.append(cantera_reactor.thermo[species_names_list].X)
455527

456528
if self.sensitive_species:
457529
# Cantera returns mass-based sensitivities rather than molar concentration or mole fraction based sensitivities.
@@ -531,6 +603,15 @@ def simulate(self):
531603
index=species.index
532604
)
533605
condition_data.append(species_generic_data)
606+
if self.surface:
607+
for index, species in enumerate(self.surface_species_list):
608+
# Create generic data object that saves the surface species object into the species object.
609+
species_generic_data = GenericData(label=surface_species_names_list[index],
610+
species=species,
611+
data=species_data[:, index + N_gas],
612+
index=species.index + N_gas
613+
)
614+
condition_data.append(species_generic_data)
534615

535616
# save kinetic data as generic data object
536617
reaction_sensitivity_data = []

0 commit comments

Comments
 (0)