@@ -63,7 +63,7 @@ class CanteraCondition(object):
6363
6464 """
6565
66- def __init__ (self , reactor_type , reaction_time , mol_frac , T0 = None , P0 = None , V0 = None ):
66+ def __init__ (self , reactor_type , reaction_time , mol_frac , surface_mol_frac = None , T0 = None , P0 = None , V0 = None ):
6767 self .reactor_type = reactor_type
6868 self .reaction_time = Quantity (reaction_time )
6969
@@ -75,6 +75,14 @@ def __init__(self, reactor_type, reaction_time, mol_frac, T0=None, P0=None, V0=N
7575
7676 self .mol_frac = mol_frac
7777
78+ if surface_mol_frac :
79+ # normalize surface mol fractions
80+ if sum (surface_mol_frac .values ()) != 1.00 :
81+ total = sum (surface_mol_frac .values ())
82+ for species , value in surface_mol_frac .items ():
83+ surface_mol_frac [species ] = value / total
84+ self .surface_mol_frac = surface_mol_frac
85+
7886 # Check to see that one of the three attributes T0, P0, and V0 is less unspecified
7987 props = [T0 , P0 , V0 ]
8088 total = 0
@@ -121,7 +129,7 @@ def __str__(self):
121129 return string
122130
123131
124- def generate_cantera_conditions (reactor_type_list , reaction_time_list , mol_frac_list , Tlist = None , Plist = None , Vlist = None ):
132+ def generate_cantera_conditions (reactor_type_list , reaction_time_list , mol_frac_list , surface_mol_frac_list = [ None ], Tlist = None , Plist = None , Vlist = None ):
125133 """
126134 Creates a list of cantera conditions from from the arguments provided.
127135
@@ -137,6 +145,8 @@ def generate_cantera_conditions(reactor_type_list, reaction_time_list, mol_frac_
137145 `reaction_time_list` A tuple object giving the ([list of reaction times], units)
138146 `mol_frac_list` A list of molfrac dictionaries with species object keys
139147 and mole fraction values
148+ `surface_mol_frac_list` A list of molfrac dictionaries with surface species object keys
149+ and mole fraction values
140150 To specify the system for an ideal gas, you must define 2 of the following 3 parameters:
141151 `T0List` A tuple giving the ([list of initial temperatures], units)
142152 'P0List' A tuple giving the ([list of initial pressures], units)
@@ -167,25 +177,28 @@ def generate_cantera_conditions(reactor_type_list, reaction_time_list, mol_frac_
167177 for reactor_type in reactor_type_list :
168178 for reaction_time in reaction_time_list :
169179 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 ))
180+ for surface_mol_frac in surface_mol_frac_list :
181+ for P in Plist :
182+ for V in Vlist :
183+ conditions .append (CanteraCondition (reactor_type , reaction_time , mol_frac , surface_mol_frac = surface_mol_frac , P0 = P , V0 = V ))
173184
174185 elif Plist is None :
175186 for reactor_type in reactor_type_list :
176187 for reaction_time in reaction_time_list :
177188 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 ))
189+ for surface_mol_frac in surface_mol_frac_list :
190+ for T in Tlist :
191+ for V in Vlist :
192+ conditions .append (CanteraCondition (reactor_type , reaction_time , mol_frac , surface_mol_frac = surface_mol_frac , T0 = T , V0 = V ))
181193
182194 elif Vlist is None :
183195 for reactor_type in reactor_type_list :
184196 for reaction_time in reaction_time_list :
185197 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 ))
198+ for surface_mol_frac in surface_mol_frac_list :
199+ for T in Tlist :
200+ for P in Plist :
201+ conditions .append (CanteraCondition (reactor_type , reaction_time , mol_frac , surface_mol_frac = surface_mol_frac , T0 = T , P0 = P ))
189202
190203 else :
191204 raise Exception ("Cantera conditions must leave one of T0, P0, and V0 state variables unspecified" )
@@ -198,7 +211,7 @@ class Cantera(object):
198211 """
199212
200213 def __init__ (self , species_list = None , reaction_list = None , canteraFile = '' , output_directory = '' , conditions = None ,
201- sensitive_species = None , thermo_SA = False ):
214+ sensitive_species = None , thermo_SA = False , surface = False , surface_species_list = None , surface_reaction_list = None ):
202215 """
203216 `species_list`: list of RMG species objects
204217 `reaction_list`: list of RMG reaction objects
@@ -208,24 +221,39 @@ def __init__(self, species_list=None, reaction_list=None, canteraFile='', output
208221 `sensitive_species`: a list of RMG species objects for conductng sensitivity analysis on
209222 `thermo_SA`: a boolean indicating whether or not to run thermo SA. By default, if sensitive_species is given,
210223 only kinetic_SA will be calculated and it must be additionally specified to perform thermo SA.
224+ `surface`: a boolean indicating whether or not to run a surface simulation
211225 """
212226 self .species_list = species_list
213227 self .reaction_list = reaction_list
214228 self .reaction_map = {}
215229 self .model = ct .Solution (canteraFile ) if canteraFile else None
230+ self .surface = surface
231+ self .surface_species_list = surface_species_list
216232 self .output_directory = output_directory if output_directory else os .getcwd ()
217233 self .conditions = conditions if conditions else []
218234 self .sensitive_species = sensitive_species if sensitive_species else []
219235 self .thermo_SA = thermo_SA
220236
237+ # need a way to make a surface simulation... for now, we'll assume there's a Cantera surface yaml file
238+ if surface :
239+ # assert canteraFile, "Cantera file must be specified to run a surface simulation" # or we add the capability to load it later
240+ assert surface_species_list , "Surface species list must be specified to run a surface simulation"
241+ # TODO some kind of assertion for starting surface mol fractions
242+ # self.surface_species_list = surface_species_list
243+
244+ if canteraFile :
245+ self .surface = ct .Interface (canteraFile , 'surface1' )
246+ self .model = self .surface .adjacent ['gas' ]
247+ # also need starting surface mol fractions
248+
221249 # Make output directory if it does not yet exist:
222250 if not os .path .exists (self .output_directory ):
223251 try :
224252 os .makedirs (self .output_directory )
225253 except :
226254 raise Exception ('Cantera output directory could not be created.' )
227255
228- def generate_conditions (self , reactor_type_list , reaction_time_list , mol_frac_list , Tlist = None , Plist = None , Vlist = None ):
256+ def generate_conditions (self , reactor_type_list , reaction_time_list , mol_frac_list , surface_mol_frac_list = [ None ], Tlist = None , Plist = None , Vlist = None ):
229257 """
230258 This saves all the reaction conditions into the Cantera class.
231259 ======================= ====================================================
@@ -240,19 +268,21 @@ def generate_conditions(self, reactor_type_list, reaction_time_list, mol_frac_li
240268 `reaction_time_list` A tuple object giving the ([list of reaction times], units)
241269 `mol_frac_list` A list of molfrac dictionaries with species object keys
242270 and mole fraction values
271+ `surface_mol_frac_list` A list of molfrac dictionaries with surface species object keys and mole fraction values
243272 To specify the system for an ideal gas, you must define 2 of the following 3 parameters:
244273 `T0List` A tuple giving the ([list of initial temperatures], units)
245274 'P0List' A tuple giving the ([list of initial pressures], units)
246275 'V0List' A tuple giving the ([list of initial specific volumes], units)
247276 """
248277
249- self .conditions = generate_cantera_conditions (reactor_type_list , reaction_time_list , mol_frac_list , Tlist , Plist , Vlist )
278+ self .conditions = generate_cantera_conditions (reactor_type_list , reaction_time_list , mol_frac_list , surface_mol_frac_list , Tlist , Plist , Vlist )
250279
251280 def load_model (self ):
252281 """
253282 Load a cantera Solution model from the job's own species_list and reaction_list attributes
254283 """
255-
284+ # TODO add surface version - this is limited by the rmgpy.kinetics.arrhenius.Arrhenius.to_cantera_kinetics()
285+ # function not being able to handle surface reactions yet
256286 ct_species = [spec .to_cantera (use_chemkin_identifier = True ) for spec in self .species_list ]
257287
258288 self .reaction_map = {}
@@ -292,6 +322,7 @@ def load_chemkin_model(self, chemkin_file, transport_file=None, **kwargs):
292322 Convert a chemkin mechanism chem.inp file to a cantera mechanism file chem.yaml
293323 and save it in the output_directory
294324 Then load it into self.model
325+ Note that if this is a surface mechanism, the calling function much include surface_file as a keyword argument
295326 """
296327 from cantera import ck2yaml
297328
@@ -301,9 +332,14 @@ def load_chemkin_model(self, chemkin_file, transport_file=None, **kwargs):
301332 if os .path .exists (out_name ):
302333 os .remove (out_name )
303334 parser = ck2yaml .Parser ()
335+
304336 parser .convert_mech (chemkin_file , transport_file = transport_file , out_name = out_name , ** kwargs )
305337 self .model = ct .Solution (out_name )
306338
339+ if self .surface :
340+ self .surface = ct .Interface (out_name , 'surface1' )
341+ self .model = self .surface .adjacent ['gas' ]
342+
307343 def modify_reaction_kinetics (self , rmg_reaction_index , rmg_reaction ):
308344 """
309345 Modify the corresponding cantera reaction's kinetics to match
@@ -385,6 +421,9 @@ def simulate(self):
385421 species_names_list = [get_species_identifier (species ) for species in self .species_list ]
386422 inert_index_list = [self .species_list .index (species ) for species in self .species_list if species .index == - 1 ]
387423
424+ if self .surface :
425+ surface_species_names_list = [get_species_identifier (species ) for species in self .surface_species_list ]
426+
388427 all_data = []
389428 for condition in self .conditions :
390429
@@ -394,6 +433,12 @@ def simulate(self):
394433 newkey = get_species_identifier (key )
395434 new_mol_frac [newkey ] = value
396435
436+ if self .surface :
437+ new_surface_mol_frac = {}
438+ for key , value in condition .surface_mol_frac .items (): # this needs to be aspecies object
439+ newkey = get_species_identifier (key )
440+ new_surface_mol_frac [newkey ] = value
441+
397442 # Set Cantera simulation conditions
398443 if condition .V0 is None :
399444 self .model .TPX = condition .T0 .value_si , condition .P0 .value_si , new_mol_frac
@@ -413,6 +458,11 @@ def simulate(self):
413458 else :
414459 raise Exception ('Other types of reactor conditions are currently not supported' )
415460
461+ # add the surface/gas adjacent thing...
462+ if self .surface :
463+ rsurf = ct .ReactorSurface (self .surface , cantera_reactor )
464+ self .surface .TPX = condition .T0 .value_si , condition .P0 .value_si , new_surface_mol_frac
465+
416466 # Run this individual condition as a simulation
417467 cantera_simulation = ct .ReactorNet ([cantera_reactor ])
418468
@@ -451,7 +501,12 @@ def simulate(self):
451501 times .append (cantera_simulation .time )
452502 temperature .append (cantera_reactor .T )
453503 pressure .append (cantera_reactor .thermo .P )
454- species_data .append (cantera_reactor .thermo [species_names_list ].X )
504+
505+ if self .surface :
506+ species_data .append (np .concatenate ((cantera_reactor .thermo [species_names_list ].X , rsurf .kinetics .coverages )))
507+ N_gas = len (cantera_reactor .thermo [species_names_list ].X )
508+ else :
509+ species_data .append (cantera_reactor .thermo [species_names_list ].X )
455510
456511 if self .sensitive_species :
457512 # Cantera returns mass-based sensitivities rather than molar concentration or mole fraction based sensitivities.
@@ -531,6 +586,15 @@ def simulate(self):
531586 index = species .index
532587 )
533588 condition_data .append (species_generic_data )
589+ if self .surface :
590+ for index , species in enumerate (self .surface_species_list ):
591+ # Create generic data object that saves the surface species object into the species object.
592+ species_generic_data = GenericData (label = surface_species_names_list [index ],
593+ species = species ,
594+ data = species_data [:, index + N_gas ],
595+ index = species .index + N_gas
596+ )
597+ condition_data .append (species_generic_data )
534598
535599 # save kinetic data as generic data object
536600 reaction_sensitivity_data = []
0 commit comments