Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Coverage-dependent thermochemistry for catalysis #2646

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
db12394
Add thermodynamic coverage dependent models in surface reactor
12Chao Mar 10, 2024
7ea7a62
add thermo coverage dependence instance while reading the file
12Chao Mar 11, 2024
f74e099
add import copy package
12Chao Mar 11, 2024
be536f8
add the instances for thermo coverage dependence
12Chao Mar 11, 2024
a443e1e
add polynomial thermodynamic coverage model to Wilhoit, NASA, and the…
12Chao Mar 12, 2024
3586d6e
declare _thermo_coverage_dependence in the pxd files
12Chao Mar 12, 2024
87e393b
fix the typos, and add the missing import command line
12Chao Mar 12, 2024
44e9337
Add thermo_coverage_dependence as new property to NASA
12Chao Mar 12, 2024
340608b
Move thermo_coverage_dependence from NASAPolynomial to NASA
12Chao Mar 13, 2024
93c8cdd
Add unit test for coverage dependent thermodynamics in RMG thermo cla…
12Chao Mar 13, 2024
1e2f79d
check thermo_coverage_dependence is not empty
12Chao Mar 13, 2024
777ab78
Fix the bug for calculating thermo coverage dependent correct values
12Chao Mar 13, 2024
c1a4664
add a big matrix for species Gibbs free energy corrections
12Chao Mar 15, 2024
a11b281
correct Gibbs free energy for each species based on its 3-parameter p…
12Chao Mar 16, 2024
6ed3b7a
use stoichiometric matrix to calculate the correct value for Keq
12Chao Mar 18, 2024
dc3d93b
Add unit tests for thermo coverage dependent models in surface solver…
12Chao Mar 20, 2024
d14a460
use isomorphic check to check if thermo coverage dependent species ar…
12Chao Mar 24, 2024
7487657
delete unused instances
12Chao Mar 25, 2024
5643611
Add solver unit test for thermodynamic coverage dependent models
12Chao Mar 25, 2024
8c91598
save thermo coverage dependent models as numpy arrays instead a list …
12Chao Mar 25, 2024
a406dc7
create a subclass of numpy array overriding __repr__ method
12Chao Mar 25, 2024
c32dac3
create thermo coverage dep polynomial model in numpy array class with…
12Chao Mar 25, 2024
8689c02
use adjacency list as the key of thermo cov dep models
12Chao Mar 25, 2024
1579203
adjust the method to read data from numpy arrays instead list
12Chao Mar 25, 2024
fa8a9ec
Write thermo coverage dependent data to Cantera yaml file
12Chao Mar 28, 2024
e9e2703
Use chemkin strings instead of labels when writing to yaml files
12Chao Mar 29, 2024
8d7756a
Write the thermo coverage dependent data to yaml files in right format
12Chao Mar 30, 2024
5411e20
Add thermo_coverag_dependence as an instance for class RMG
12Chao Apr 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 5 additions & 2 deletions rmgpy/rmg/input.py
Expand Up @@ -105,7 +105,8 @@
def catalyst_properties(bindingEnergies=None,
surfaceSiteDensity=None,
metal=None,
coverageDependence=False):
coverageDependence=False,
thermoCoverageDependence=False,):
"""
Specify the properties of the catalyst.
Binding energies of C,H,O,N atoms, and the surface site density.
Expand Down Expand Up @@ -152,6 +153,7 @@
else:
logging.info("Coverage dependence is turned OFF")
rmg.coverage_dependence = coverageDependence
rmg.thermo_coverage_dependence = thermoCoverageDependence

Check warning on line 156 in rmgpy/rmg/input.py

View check run for this annotation

Codecov / codecov/patch

rmgpy/rmg/input.py#L156

Added line #L156 was not covered by tests

def convert_binding_energies(binding_energies):
"""
Expand Down Expand Up @@ -1062,7 +1064,8 @@
sensitive_species=sensitive_species,
sensitivity_threshold=sensitivityThreshold,
sens_conditions=sens_conditions,
coverage_dependence=rmg.coverage_dependence)
coverage_dependence=rmg.coverage_dependence,
thermo_coverage_dependence=rmg.thermo_coverage_dependence)
rmg.reaction_systems.append(system)
system.log_initial_conditions(number=len(rmg.reaction_systems))

Expand Down
41 changes: 41 additions & 0 deletions rmgpy/rmg/main.py
Expand Up @@ -50,6 +50,7 @@
import yaml
from cantera import ck2yaml
from scipy.optimize import brute
import cantera as ct

import rmgpy.util as util
from rmgpy.rmg.model import Species, CoreEdgeReactionModel
Expand Down Expand Up @@ -190,6 +191,7 @@
self.surface_site_density = None
self.binding_energies = None
self.coverage_dependence = False
self.thermo_coverage_dependence = False
self.forbidden_structures = []

self.reaction_model = None
Expand Down Expand Up @@ -274,6 +276,7 @@
self.reaction_model.core.phase_system.phases["Surface"].site_density = self.surface_site_density.value_si
self.reaction_model.edge.phase_system.phases["Surface"].site_density = self.surface_site_density.value_si
self.reaction_model.coverage_dependence = self.coverage_dependence
self.reaction_model.thermo_coverage_dependence = self.thermo_coverage_dependence

self.reaction_model.verbose_comments = self.verbose_comments
self.reaction_model.save_edge_species = self.save_edge_species
Expand Down Expand Up @@ -1179,6 +1182,44 @@
os.path.join(self.output_directory, "chemkin", "chem_annotated-gas.inp"),
surface_file=(os.path.join(self.output_directory, "chemkin", "chem_annotated-surface.inp")),
)
if self.thermo_coverage_dependence:

Check warning on line 1185 in rmgpy/rmg/main.py

View check run for this annotation

Codecov / codecov/patch

rmgpy/rmg/main.py#L1185

Added line #L1185 was not covered by tests
# add thermo coverage dependence to Cantera files
chem_yaml_path = os.path.join(self.output_directory, "cantera", "chem.yaml")
gas = ct.Solution(chem_yaml_path, "gas")
surf = ct.Interface(chem_yaml_path, "surface1", [gas])
with open(chem_yaml_path, 'r') as f:
content = yaml.load(f, Loader=yaml.FullLoader)

Check warning on line 1191 in rmgpy/rmg/main.py

View check run for this annotation

Codecov / codecov/patch

rmgpy/rmg/main.py#L1187-L1191

Added lines #L1187 - L1191 were not covered by tests

content['phases'][1]['reference-state-coverage'] = 0.11
content['phases'][1]['thermo'] = 'coverage-dependent-surface'

Check warning on line 1194 in rmgpy/rmg/main.py

View check run for this annotation

Codecov / codecov/patch

rmgpy/rmg/main.py#L1193-L1194

Added lines #L1193 - L1194 were not covered by tests

for s in self.reaction_model.core.species:
if s.contains_surface_site() and s.thermo.thermo_coverage_dependence:
for dep_sp, parameters in s.thermo.thermo_coverage_dependence.items():
mol = Molecule().from_adjacency_list(dep_sp)
for sp in self.reaction_model.core.species:
if sp.is_isomorphic(mol, strict=False):
parameters['units'] = {'energy':'J', 'quantity':'mol'}
parameters['enthalpy-coefficients'] = [float(value) for value in parameters['enthalpy-coefficients']]
parameters['entropy-coefficients'] = [float(value) for value in parameters['entropy-coefficients']]
try:
content["species"][gas.n_species+surf.species_index(sp.to_chemkin())]['coverage-dependencies'][sp.to_chemkin()] = parameters
except KeyError:
content["species"][gas.n_species+surf.species_index(sp.to_chemkin())]['coverage-dependencies'] = {sp.to_chemkin(): parameters}

Check warning on line 1208 in rmgpy/rmg/main.py

View check run for this annotation

Codecov / codecov/patch

rmgpy/rmg/main.py#L1196-L1208

Added lines #L1196 - L1208 were not covered by tests

annotated_yaml_path = os.path.join(self.output_directory, "cantera", "chem_annotated.yaml")
with open(annotated_yaml_path, 'r') as f:
annotated_content = yaml.load(f, Loader=yaml.FullLoader)

Check warning on line 1212 in rmgpy/rmg/main.py

View check run for this annotation

Codecov / codecov/patch

rmgpy/rmg/main.py#L1210-L1212

Added lines #L1210 - L1212 were not covered by tests

annotated_content['phases'] = content['phases']
annotated_content['species'] = content['species']

Check warning on line 1215 in rmgpy/rmg/main.py

View check run for this annotation

Codecov / codecov/patch

rmgpy/rmg/main.py#L1214-L1215

Added lines #L1214 - L1215 were not covered by tests

with open(chem_yaml_path, 'w') as output_f:
yaml.dump(content, output_f, sort_keys=False)

Check warning on line 1218 in rmgpy/rmg/main.py

View check run for this annotation

Codecov / codecov/patch

rmgpy/rmg/main.py#L1217-L1218

Added lines #L1217 - L1218 were not covered by tests

with open(annotated_yaml_path, 'w') as output_f:
yaml.dump(annotated_content, output_f, sort_keys=False)

Check warning on line 1221 in rmgpy/rmg/main.py

View check run for this annotation

Codecov / codecov/patch

rmgpy/rmg/main.py#L1220-L1221

Added lines #L1220 - L1221 were not covered by tests

else: # gas phase only
self.generate_cantera_files(os.path.join(self.output_directory, "chemkin", "chem.inp"))
self.generate_cantera_files(os.path.join(self.output_directory, "chemkin", "chem_annotated.inp"))
Expand Down
77 changes: 73 additions & 4 deletions rmgpy/solver/surface.pyx
Expand Up @@ -43,6 +43,8 @@ cimport rmgpy.constants as constants
from rmgpy.quantity import Quantity
from rmgpy.quantity cimport ScalarQuantity
from rmgpy.solver.base cimport ReactionSystem
import copy
from rmgpy.molecule import Molecule

cdef class SurfaceReactor(ReactionSystem):
"""
Expand All @@ -66,9 +68,13 @@ cdef class SurfaceReactor(ReactionSystem):
cdef public ScalarQuantity surface_site_density
cdef public np.ndarray reactions_on_surface # (catalyst surface, not core/edge surface)
cdef public np.ndarray species_on_surface # (catalyst surface, not core/edge surface)
cdef public np.ndarray thermo_coeff_matrix
cdef public np.ndarray stoi_matrix

cdef public bint coverage_dependence
cdef public dict coverage_dependencies
cdef public bint thermo_coverage_dependence



def __init__(self,
Expand All @@ -84,6 +90,7 @@ cdef class SurfaceReactor(ReactionSystem):
sensitivity_threshold=1e-3,
sens_conditions=None,
coverage_dependence=False,
thermo_coverage_dependence=False,
):
ReactionSystem.__init__(self,
termination,
Expand All @@ -103,6 +110,7 @@ cdef class SurfaceReactor(ReactionSystem):
self.surface_volume_ratio = Quantity(surface_volume_ratio)
self.surface_site_density = Quantity(surface_site_density)
self.coverage_dependence = coverage_dependence
self.thermo_coverage_dependence = thermo_coverage_dependence
self.V = 0 # will be set from ideal gas law in initialize_model
self.constant_volume = True
self.sens_conditions = sens_conditions
Expand Down Expand Up @@ -166,6 +174,10 @@ cdef class SurfaceReactor(ReactionSystem):
)
cdef np.ndarray[np.int_t, ndim=1] species_on_surface, reactions_on_surface
cdef Py_ssize_t index
cdef np.ndarray thermo_coeff_matrix = np.zeros((len(self.species_index), len(self.species_index), 6), dtype=np.float64)
cdef np.ndarray stoi_matrix = np.zeros((self.reactant_indices.shape[0], len(self.species_index)), dtype=np.float64)
if self.thermo_coverage_dependence:
self.thermo_coeff_matrix = thermo_coeff_matrix
#: 1 if it's on a surface, 0 if it's in the gas phase
reactions_on_surface = np.zeros((self.num_core_reactions + self.num_edge_reactions), int)
species_on_surface = np.zeros((self.num_core_species), int)
Expand Down Expand Up @@ -195,6 +207,47 @@ cdef class SurfaceReactor(ReactionSystem):
means that Species with index 2 in the current simulation is used in
Reaction 3 with parameters a=0.1, m=-1, E=12 kJ/mol
"""
for sp, sp_index in self.species_index.items():
if sp.contains_surface_site():
if self.thermo_coverage_dependence and sp.thermo.thermo_coverage_dependence:
for spec, parameters in sp.thermo.thermo_coverage_dependence.items():
molecule = Molecule().from_adjacency_list(spec)
for species in self.species_index.keys():
if species.is_isomorphic(molecule, strict=False):
species_index = self.species_index[species]
thermo_polynomials = np.concatenate((parameters['enthalpy-coefficients'], parameters['entropy-coefficients']), axis=0)
self.thermo_coeff_matrix[sp_index, species_index] = [x for x in thermo_polynomials]
# create a stoichiometry matrix for reaction enthalpy and entropy correction
# due to thermodynamic coverage dependence
if self.thermo_coverage_dependence:
ir = self.reactant_indices
ip = self.product_indices
for rxn_id, rxn_stoi_num in enumerate(stoi_matrix):
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:
continue
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:
continue
else:
if ir[rxn_id, 1] == -1: # only one reactant
rxn_stoi_num[ir[rxn_id, 0]] += -1
elif ir[rxn_id, 2] == -1: # only two reactants
rxn_stoi_num[ir[rxn_id, 0]] += -1
rxn_stoi_num[ir[rxn_id, 1]] += -1
else: # three reactants
rxn_stoi_num[ir[rxn_id, 0]] += -1
rxn_stoi_num[ir[rxn_id, 1]] += -1
rxn_stoi_num[ir[rxn_id, 2]] += -1
if ip[rxn_id, 1] == -1: # only one product
rxn_stoi_num[ip[rxn_id, 0]] += 1
elif ip[rxn_id, 2] == -1: # only two products
rxn_stoi_num[ip[rxn_id, 0]] += 1
rxn_stoi_num[ip[rxn_id, 1]] += 1
else: # three products
rxn_stoi_num[ip[rxn_id, 0]] += 1
rxn_stoi_num[ip[rxn_id, 1]] += 1
rxn_stoi_num[ip[rxn_id, 2]] += 1
self.stoi_matrix = stoi_matrix

self.species_on_surface = species_on_surface
self.reactions_on_surface = reactions_on_surface

Expand Down Expand Up @@ -378,9 +431,6 @@ cdef class SurfaceReactor(ReactionSystem):
cdef np.ndarray[np.float64_t, ndim=2] jacobian, dgdk
cdef list list_of_coverage_deps
cdef double surface_site_fraction, total_sites, a, m, E



ir = self.reactant_indices
ip = self.product_indices
equilibrium_constants = self.Keq
Expand Down Expand Up @@ -414,14 +464,33 @@ cdef class SurfaceReactor(ReactionSystem):
V = self.V # constant volume reactor
A = self.V * surface_volume_ratio_si # area
total_sites = self.surface_site_density.value_si * A # todo: double check units

for j in range(num_core_species):
if species_on_surface[j]:
C[j] = (N[j] / V) / surface_volume_ratio_si
else:
C[j] = N[j] / V
#: surface species are in mol/m2, gas phase are in mol/m3
core_species_concentrations[j] = C[j]

# Thermodynamic coverage dependence
if self.thermo_coverage_dependence:
coverages = []
for i in range(len(N)):
if species_on_surface[i]:
surface_site_fraction = N[i] / total_sites
else:
surface_site_fraction = 0
coverages.append(surface_site_fraction)
coverages = np.array(coverages)
thermo_dep_coverage = np.stack([coverages, coverages**2, coverages**3, -self.T.value_si*coverages, -self.T.value_si*coverages**2, -self.T.value_si*coverages**3])
free_energy_coverage_corrections = []
for matrix in self.thermo_coeff_matrix:
sp_free_energy_correction = np.diag(np.dot(matrix, thermo_dep_coverage)).sum()
free_energy_coverage_corrections.append(sp_free_energy_correction)
rxns_free_energy_change = np.diag(np.dot(self.stoi_matrix, np.transpose(np.array([free_energy_coverage_corrections]))))
corrected_K_eq = copy.deepcopy(self.Keq)
corrected_K_eq *= np.exp(-1 * rxns_free_energy_change / (constants.R * self.T.value_si))
kr = kf / corrected_K_eq

# Coverage dependence
coverage_corrections = np.ones_like(kf, float)
Expand Down
1 change: 1 addition & 0 deletions rmgpy/thermo/nasa.pxd
Expand Up @@ -56,6 +56,7 @@ cdef class NASAPolynomial(HeatCapacityModel):
cdef class NASA(HeatCapacityModel):

cdef public NASAPolynomial poly1, poly2, poly3
cdef public dict _thermo_coverage_dependence

cpdef NASAPolynomial select_polynomial(self, double T)

Expand Down