Example of Kinetic Monte Carlo simulation of reaction in Langmuir binary gas
Download latest version here: test_kmc_binary_gas.py
.
import numpy as np
from susmost.mc import metropolis
from susmost.mc.metropolis import kmc_step, kmc_rate
from susmost import make_single_point_zmatrix, make_square_plane_cell, \
normal_lattice_task, nearest_int, SiteStateType, register_property, sitestate, mc
from time import time
from math import exp
register_property('coverage_A','==', None, sitestate.join_cover) # counter for molecule A coverage
register_property('coverage_B','==', None, sitestate.join_cover) # counter for molecule B coverage
sitestate.SPMI_db['coverage'].merge_func = None # prevent merging of occupied and empty states due to equal lateral interactions
def binary_gas_KMC(rnd_seed, muA, muB, eAA, eBB, diff_Ea, react_Ea, Temperature):
"""
Do KMC simulation for a binary gas with symmetric lateral interactions between nearest neighbors.
Parameters:
:rnd_seed: random seed
:muA: chemical potential of type A molecules
:muB: chemical potential of type B molecules
:eAA: lateral interaction energy between type A molecules
:eBB: lateral interaction energy between type B molecules
:diff_Ea: diffusion barrier for A and B molecules
:react_Ea: activation energy for A + B reaction
:Temperature: temperature in k_B units
Returns:
Tuple of three elements: (A coverage, B coverage, number of reactions)
"""
INF_E = 1E6 # prohibitive energy level
#k_B = 8.3144598E-3 # kJ/(mol*K)
#k_B = 0.000086 # eV
k_B = 1.0
beta = 1.0 / (k_B * Temperature)
a = 1.0 # lattice parameter
r_cut_off= 1.0 + .0001
dop_int = {('molecule_A','molecule_B'):0.0,('molecule_A','molecule_A'):eAA,('molecule_B','molecule_B'):eBB}
lateral_interactions = lambda cc1,cc2: nearest_int(cc1,cc2,dop_int=dop_int)
#cell,atoms = make_struct.make_hexagonal_plane_cell(a)
cell,atoms = make_square_plane_cell(a)
empty_state = SiteStateType('Empty',make_single_point_zmatrix(), 0.0, ['H'], coverage=0., coverage_A=0., coverage_B=0.)
molA_state = SiteStateType('molecule_A', make_single_point_zmatrix(), -1., ['N'], coverage=1., coverage_A=1., coverage_B=0.)
molB_state = SiteStateType('molecule_B', make_single_point_zmatrix(), 0.0, ['O'], coverage=1., coverage_A=0., coverage_B=1.)
lt = normal_lattice_task([empty_state, molA_state, molB_state], cell, atoms, lateral_interactions, r_cut_off, INF_E)
assert lt.states_count == 3, lt
lt.set_ads_energy('molecule_A', muA)
lt.set_ads_energy('molecule_B', muB)
empty_idx = lt.find_state_index('name','Empty')
A_idx = lt.find_state_index('name','molecule_A')
B_idx = lt.find_state_index('name','molecule_B')
m = mc.make_metropolis(lt, 24, Temperature, k_B, seed=int(rnd_seed))
ads_des_rate = kmc_rate(1, 0, 1)
diff_rate = kmc_rate(1, diff_Ea, 1)
react_rate = kmc_rate(1, react_Ea, 0)
all_rates = []
all_rates.append(ads_des_rate.calc(beta, muA)) # dH=muA
all_rates.append(ads_des_rate.calc(beta, -muA)) # dH=muA
all_rates.append(ads_des_rate.calc(beta, muB)) # dH=muB
all_rates.append(ads_des_rate.calc(beta, -muB)) # dH=muB
all_rates.append(diff_rate.calc(beta, 0)) # dH=0
all_rates.append(react_rate.calc(beta, 0)) # dH=0
r0 = max(all_rates)
#print(all_rates, beta)
m.add_singe_site_step(empty_idx, kmc_step(ads_des_rate, A_idx, -1, 0)) # A adsorption
m.add_singe_site_step(empty_idx, kmc_step(ads_des_rate, B_idx, -1, 0)) # B adsorption
m.add_singe_site_step(A_idx, kmc_step(ads_des_rate, empty_idx, -1, 0)) # A desorption
m.add_singe_site_step(B_idx, kmc_step(ads_des_rate, empty_idx, -1, 0)) # B desorption
for ei in m.regr.nn_edges_range(): # nearest neighbors in all directions
m.add_pair_step(empty_idx, ei, A_idx, kmc_step(diff_rate, A_idx, empty_idx, 0)) # A diffusion
m.add_pair_step(A_idx, ei, empty_idx, kmc_step(diff_rate, empty_idx, A_idx, 0)) # A diffusion
m.add_pair_step(empty_idx, ei, B_idx, kmc_step(diff_rate, B_idx, empty_idx, 0)) # B diffusion
m.add_pair_step(B_idx, ei, empty_idx, kmc_step(diff_rate, empty_idx, B_idx, 0)) # B diffusion
m.add_pair_step(A_idx, ei, B_idx, kmc_step(react_rate, empty_idx, empty_idx, 1)) # A + B reaction
m.add_pair_step(B_idx, ei, A_idx, kmc_step(react_rate, empty_idx, empty_idx, 1)) # B + A reaction (same as above)
m.params_log_period = 100 # log parameters each 100 steps
m.setup_kmc(r0)
mc.run(m, 100, 100000) # do 100 iterations of 100000 steps each, saving surface snapshots in traj*.xyz file
#mc.stat_digest(m) # print detailed statistics about logged parameters
assert m.kmc_max_r0 <= r0, f"Selected r0 = {r0} turned to be exceeded by rate = {m.kmc_max_r0}"
A_cov_idx = m.param_idx('coverage_A')
B_cov_idx = m.param_idx('coverage_B')
means = np.mean(m.params_log, axis=0) # mean values of all parameters
return means[A_cov_idx], means[B_cov_idx], m.kmc_counters[1].accept
Ts = np.arange(.1, 3+1e-9, 0.1)
R = []
for T in Ts:
res = binary_gas_KMC(time(), -1., 0.0, 0.0, 1e-9, 0.0, 0.0, T) # 1e-9 - hack to prevent dropping of all lattice edges :-(, TODO: fix it
print (T, *res)
R.append(res)
print ("\nFinal result:\nT covA covB Rate")
for T, res in zip(Ts, R):
print (T, *res)
Result plot: