@@ -43,6 +43,8 @@ cimport rmgpy.constants as constants
4343from rmgpy.quantity import Quantity
4444from rmgpy.quantity cimport ScalarQuantity
4545from rmgpy.solver.base cimport ReactionSystem
46+ import copy
47+ from rmgpy.molecule import Molecule
4648
4749cdef class SurfaceReactor(ReactionSystem):
4850 """
@@ -66,9 +68,13 @@ cdef class SurfaceReactor(ReactionSystem):
6668 cdef public ScalarQuantity surface_site_density
6769 cdef public np.ndarray reactions_on_surface # (catalyst surface, not core/edge surface)
6870 cdef public np.ndarray species_on_surface # (catalyst surface, not core/edge surface)
71+ cdef public np.ndarray thermo_coeff_matrix
72+ cdef public np.ndarray stoi_matrix
6973
7074 cdef public bint coverage_dependence
7175 cdef public dict coverage_dependencies
76+ cdef public bint thermo_coverage_dependence
77+
7278
7379
7480 def __init__ (self ,
@@ -84,6 +90,7 @@ cdef class SurfaceReactor(ReactionSystem):
8490 sensitivity_threshold = 1e-3 ,
8591 sens_conditions = None ,
8692 coverage_dependence = False ,
93+ thermo_coverage_dependence = False ,
8794 ):
8895 ReactionSystem.__init__ (self ,
8996 termination,
@@ -103,6 +110,7 @@ cdef class SurfaceReactor(ReactionSystem):
103110 self .surface_volume_ratio = Quantity(surface_volume_ratio)
104111 self .surface_site_density = Quantity(surface_site_density)
105112 self .coverage_dependence = coverage_dependence
113+ self .thermo_coverage_dependence = thermo_coverage_dependence
106114 self .V = 0 # will be set from ideal gas law in initialize_model
107115 self .constant_volume = True
108116 self .sens_conditions = sens_conditions
@@ -166,6 +174,10 @@ cdef class SurfaceReactor(ReactionSystem):
166174 )
167175 cdef np.ndarray[np.int_t, ndim= 1 ] species_on_surface, reactions_on_surface
168176 cdef Py_ssize_t index
177+ cdef np.ndarray thermo_coeff_matrix = np.zeros((len (self .species_index), len (self .species_index), 6 ), dtype = np.float64)
178+ cdef np.ndarray stoi_matrix = np.zeros((self .reactant_indices.shape[0 ], len (self .species_index)), dtype = np.float64)
179+ if self .thermo_coverage_dependence:
180+ self .thermo_coeff_matrix = thermo_coeff_matrix
169181 # : 1 if it's on a surface, 0 if it's in the gas phase
170182 reactions_on_surface = np.zeros((self .num_core_reactions + self .num_edge_reactions), int )
171183 species_on_surface = np.zeros((self .num_core_species), int )
@@ -195,6 +207,49 @@ cdef class SurfaceReactor(ReactionSystem):
195207 means that Species with index 2 in the current simulation is used in
196208 Reaction 3 with parameters a=0.1, m=-1, E=12 kJ/mol
197209 """
210+ for sp, sp_index in self .species_index.items():
211+ if sp.contains_surface_site():
212+ if self .thermo_coverage_dependence and sp.thermo.thermo_coverage_dependence:
213+ for spec, parameters in sp.thermo.thermo_coverage_dependence.items():
214+ molecule = Molecule().from_adjacency_list(spec)
215+ for species in self .species_index.keys():
216+ if species.is_isomorphic(molecule, strict = False ):
217+ species_index = self .species_index[species]
218+ enthalpy_coeff = np.array([p.value_si for p in parameters[' enthalpy-coefficients' ]])
219+ entropy_coeff = np.array([p.value_si for p in parameters[' entropy-coefficients' ]])
220+ thermo_polynomials = np.concatenate((enthalpy_coeff, entropy_coeff), axis = 0 )
221+ self .thermo_coeff_matrix[sp_index, species_index] = [x for x in thermo_polynomials]
222+ # create a stoichiometry matrix for reaction enthalpy and entropy correction
223+ # due to thermodynamic coverage dependence
224+ if self .thermo_coverage_dependence:
225+ ir = self .reactant_indices
226+ ip = self .product_indices
227+ for rxn_id, rxn_stoi_num in enumerate (stoi_matrix):
228+ if ir[rxn_id, 0 ] >= self .num_core_species or ir[rxn_id, 1 ] >= self .num_core_species or ir[rxn_id, 2 ] >= self .num_core_species:
229+ continue
230+ elif ip[rxn_id, 0 ] >= self .num_core_species or ip[rxn_id, 1 ] >= self .num_core_species or ip[rxn_id, 2 ] >= self .num_core_species:
231+ continue
232+ else :
233+ if ir[rxn_id, 1 ] == - 1 : # only one reactant
234+ rxn_stoi_num[ir[rxn_id, 0 ]] += - 1
235+ elif ir[rxn_id, 2 ] == - 1 : # only two reactants
236+ rxn_stoi_num[ir[rxn_id, 0 ]] += - 1
237+ rxn_stoi_num[ir[rxn_id, 1 ]] += - 1
238+ else : # three reactants
239+ rxn_stoi_num[ir[rxn_id, 0 ]] += - 1
240+ rxn_stoi_num[ir[rxn_id, 1 ]] += - 1
241+ rxn_stoi_num[ir[rxn_id, 2 ]] += - 1
242+ if ip[rxn_id, 1 ] == - 1 : # only one product
243+ rxn_stoi_num[ip[rxn_id, 0 ]] += 1
244+ elif ip[rxn_id, 2 ] == - 1 : # only two products
245+ rxn_stoi_num[ip[rxn_id, 0 ]] += 1
246+ rxn_stoi_num[ip[rxn_id, 1 ]] += 1
247+ else : # three products
248+ rxn_stoi_num[ip[rxn_id, 0 ]] += 1
249+ rxn_stoi_num[ip[rxn_id, 1 ]] += 1
250+ rxn_stoi_num[ip[rxn_id, 2 ]] += 1
251+ self .stoi_matrix = stoi_matrix
252+
198253 self .species_on_surface = species_on_surface
199254 self .reactions_on_surface = reactions_on_surface
200255
@@ -378,9 +433,10 @@ cdef class SurfaceReactor(ReactionSystem):
378433 cdef np.ndarray[np.float64_t, ndim= 2 ] jacobian, dgdk
379434 cdef list list_of_coverage_deps
380435 cdef double surface_site_fraction, total_sites, a, m, E
381-
382-
383-
436+ cdef np.ndarray[np.float64_t, ndim= 1 ] coverages, coverages_squared, temperature_scaled_coverages
437+ cdef np.ndarray[np.float64_t, ndim= 2 ] thermo_dep_coverage
438+ cdef np.ndarray[np.float64_t, ndim= 1 ] free_energy_coverage_corrections, rxns_free_energy_change, corrected_K_eq
439+ cdef double sp_free_energy_correction
384440 ir = self .reactant_indices
385441 ip = self .product_indices
386442 equilibrium_constants = self .Keq
@@ -414,14 +470,33 @@ cdef class SurfaceReactor(ReactionSystem):
414470 V = self .V # constant volume reactor
415471 A = self .V * surface_volume_ratio_si # area
416472 total_sites = self .surface_site_density.value_si * A # todo: double check units
417-
418473 for j in range (num_core_species):
419474 if species_on_surface[j]:
420475 C[j] = (N[j] / V) / surface_volume_ratio_si
421476 else :
422477 C[j] = N[j] / V
423478 # : surface species are in mol/m2, gas phase are in mol/m3
424479 core_species_concentrations[j] = C[j]
480+
481+ # Thermodynamic coverage dependence
482+ if self .thermo_coverage_dependence:
483+ coverages = np.where(species_on_surface, N / total_sites, 0.0 )
484+ coverages_squared = coverages * coverages
485+ temperature_scaled_coverages = - self .T.value_si * coverages
486+ thermo_dep_coverage = np.empty((6 , coverages.shape[0 ]), dtype = np.float64)
487+ thermo_dep_coverage[0 , :] = coverages
488+ thermo_dep_coverage[1 , :] = coverages_squared
489+ thermo_dep_coverage[2 , :] = coverages_squared * coverages
490+ thermo_dep_coverage[3 , :] = temperature_scaled_coverages
491+ thermo_dep_coverage[4 , :] = temperature_scaled_coverages * coverages
492+ thermo_dep_coverage[5 , :] = temperature_scaled_coverages * coverages_squared
493+ free_energy_coverage_corrections = np.empty(len (self .thermo_coeff_matrix), dtype = np.float64)
494+ for i, matrix in enumerate (self .thermo_coeff_matrix):
495+ free_energy_coverage_corrections[i] = np.diag(np.dot(matrix, thermo_dep_coverage)).sum()
496+ rxns_free_energy_change = np.matmul(self .stoi_matrix,free_energy_coverage_corrections)
497+ corrected_K_eq = copy.deepcopy(self .Keq)
498+ corrected_K_eq *= np.exp(- 1 * rxns_free_energy_change / (constants.R * self .T.value_si))
499+ kr = kf / corrected_K_eq
425500
426501 # Coverage dependence
427502 coverage_corrections = np.ones_like(kf, float )
0 commit comments