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 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
166c13a
Add thermodynamic coverage dependent models in surface reactor
12Chao Mar 10, 2024
e7f6b08
add thermo coverage dependence instance while reading the file
12Chao Mar 11, 2024
27ab4d5
add import copy package
12Chao Mar 11, 2024
9590425
add the instances for thermo coverage dependence
12Chao Mar 11, 2024
dcb95ca
add polynomial thermodynamic coverage model to Wilhoit, NASA, and the…
12Chao Mar 12, 2024
6cd68ea
declare _thermo_coverage_dependence in the pxd files
12Chao Mar 12, 2024
54bbaa3
fix the typos, and add the missing import command line
12Chao Mar 12, 2024
adf8052
Add thermo_coverage_dependence as new property to NASA
12Chao Mar 12, 2024
542857b
Move thermo_coverage_dependence from NASAPolynomial to NASA
12Chao Mar 13, 2024
3de057a
Add unit test for coverage dependent thermodynamics in RMG thermo cla…
12Chao Mar 13, 2024
7d4ede9
check thermo_coverage_dependence is not empty
12Chao Mar 13, 2024
02be956
Fix the bug for calculating thermo coverage dependent correct values
12Chao Mar 13, 2024
b15d065
add a big matrix for species Gibbs free energy corrections
12Chao Mar 15, 2024
cfa2bab
correct Gibbs free energy for each species based on its 3-parameter p…
12Chao Mar 16, 2024
5a2c8d8
use stoichiometric matrix to calculate the correct value for Keq
12Chao Mar 18, 2024
7f2be04
Add unit tests for thermo coverage dependent models in surface solver…
12Chao Mar 20, 2024
4fa7715
use isomorphic check to check if thermo coverage dependent species ar…
12Chao Mar 24, 2024
da3f9e1
delete unused instances
12Chao Mar 25, 2024
9f26e2c
Add solver unit test for thermodynamic coverage dependent models
12Chao Mar 25, 2024
6ca15a1
save thermo coverage dependent models as numpy arrays instead a list …
12Chao Mar 25, 2024
1ab80e3
create a subclass of numpy array overriding __repr__ method
12Chao Mar 25, 2024
80974dc
create thermo coverage dep polynomial model in numpy array class with…
12Chao Mar 25, 2024
fcc050b
use adjacency list as the key of thermo cov dep models
12Chao Mar 25, 2024
3f190d3
adjust the method to read data from numpy arrays instead list
12Chao Mar 25, 2024
d5f5349
Write thermo coverage dependent data to Cantera yaml file
12Chao Mar 28, 2024
1900513
Use chemkin strings instead of labels when writing to yaml files
12Chao Mar 29, 2024
984bc6c
Write the thermo coverage dependent data to yaml files in right format
12Chao Mar 30, 2024
21ad0b0
Add thermo_coverag_dependence as an instance for class RMG
12Chao Apr 2, 2024
e41093e
fix the bug that the coverage kinetics cannot be written to chemkin
12Chao Jul 18, 2024
426af6a
write data as RMG object instead of floats to keep the units consistent
12Chao Oct 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rmgpy/chemkin.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1867,7 +1867,7 @@ def write_kinetics_entry(reaction, species_list, verbose=True, java_library=Fals
for species, cov_params in kinetics.coverage_dependence.items():
label = get_species_identifier(species)
string += f' COV / {label:<41} '
string += f"{cov_params['a']:<9.3g} {cov_params['m']:<9.3g} {cov_params['E'].value_si/4184.:<9.3f} /\n"
string += f"{cov_params['a'].value_si:<9.3g} {cov_params['m'].value_si:<9.3g} {cov_params['E'].value_si/4184.:<9.3f} /\n"

if isinstance(kinetics, (_kinetics.ThirdBody, _kinetics.Lindemann, _kinetics.Troe)):
# Write collider efficiencies
Expand Down
7 changes: 5 additions & 2 deletions rmgpy/rmg/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ def database(
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 @@ def catalyst_properties(bindingEnergies=None,
else:
logging.info("Coverage dependence is turned OFF")
rmg.coverage_dependence = coverageDependence
rmg.thermo_coverage_dependence = thermoCoverageDependence

def convert_binding_energies(binding_energies):
"""
Expand Down Expand Up @@ -1062,7 +1064,8 @@ def surface_reactor(temperature,
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
Original file line number Diff line number Diff line change
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 @@ def clear(self):
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 @@ def load_input(self, path=None):
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 @@ def execute(self, initialize=True, **kwargs):
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:
# 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)

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

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'] = [value.value_si for value in parameters['enthalpy-coefficients']]
parameters['entropy-coefficients'] = [value.value_si 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}

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)

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

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

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

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
79 changes: 75 additions & 4 deletions rmgpy/solver/surface.pyx
Original file line number Diff line number Diff line change
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,49 @@ 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]
enthalpy_coeff = np.array([p.value_si for p in parameters['enthalpy-coefficients']])
entropy_coeff = np.array([p.value_si for p in parameters['entropy-coefficients']])
thermo_polynomials = np.concatenate((enthalpy_coeff, entropy_coeff), 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 +433,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 +466,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
Original file line number Diff line number Diff line change
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
Loading
Loading