These are the steps of the simulation I am performing:
1. We assume an infinite reservoir compound of CO and NO molecules with partial pressures 𝑦CO and 𝑦NO(= 1 − 𝑦CO), respectively.
2. The reservoir is in contact with a surface which is simulated by means of a square lattice of 64×64. Periodic boundary conditions are applied to avoid edge effects.
3. The equilibrium coverages are measured as a function of 𝑦CO. To find the critical points, a run each up to 50000 Monte Carlo cycles (MCS) needs to be carried out.If the run completes 50000 MCS without the lattice getting poisoned (surface saturation), the particular point is considered to be within SRS.
4. In order to obtain the coverages of CO, N and O, the initial 20000 MCS are disregarded for equilibrium and averages are taken over the subsequent 30000 MCS. The values of coverages/production rate are obtained after 10 MCS, so that the final values are an average of 3000 configurations.
5. The CO or the NO molecules are selected randomly with relative probabilities 𝑦CO and 𝑦NO, respectively.
6. A surface site is picked up at random and the steps involved in the simulation are as follows:
(a) If the selected molecule is NO and the picked
site is empty, then the four nearest neighbours of the selected site are scanned at random. If all the sites areoccupied, the trial ends. In the case that any adjacent site is vacant, NO dissociates into N and O which occupy the vacant sites with equal probability. The nearest neighbouring sites of adsorbed N and O atoms in step are then searched for the presence of CO and N respectively. If there is a CO (N), then CO2 (N2) is formed, leaving two vacant sites.
(b) In the case that CO is selected, one of the following events occurs: (i) if the picked site is vacant, then CO is adsorbed on the chosen vacant site
as in step. The nearest neighbours of this adsorbed CO molecule are scanned for the presence of an adsorbed O atom in order to complete reaction step. In the case that the O atom is found, CO2 is formed which immediately desorbs leaving two empty sites from the surface and the trial ends. (ii) If the picked site is occupied by the O atom, then CO is co-adsorbed on that O atom with probability 𝑝. This coadsorbed CO then reacts with O at that site with unit probability to form CO2.
What I have tried:
This is the code I have so far:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <assert.h>
#include <stdbool.h>
#include "pcg_basic.h"
#define NMAX 64
#define MMAX 64
#define TIME 50000
#define WARMUP 20000
#define num_simulations 50
#define num_runs 5 // Number of runs for each value of XCO
typedef enum { EMPTY, CO, O, N } LAT_ELEM;
typedef struct {
double CO_coverage;
double O_coverage;
double N_coverage;
} Coverage;
pcg32_random_t rng;
Coverage calculate_coverage(LAT_ELEM lattice[NMAX * MMAX]);
int find_random_site();
void print_lattice(LAT_ELEM lattice[NMAX * MMAX]);
int find_random_neighbor(int site);
void find_nearest_neighbors(int site, int exclude, int neighbors[4]);
void shuffle(int *array, int n);
void add_CO(LAT_ELEM lattice[NMAX * MMAX], int *co2_count, double coadsorption_probability);
void add_NO_dissociate(LAT_ELEM lattice[NMAX * MMAX], int *co2_count, int *n2_count);
void write_lattice_to_csv(int N, int M, LAT_ELEM lattice[N * M], double XCO);
double mean(double *array, int size);
double std_dev(double *array, int size);
Coverage calculate_coverage(LAT_ELEM lattice[NMAX * MMAX]) {
Coverage coverage = {0.0, 0.0, 0.0};
for (int i = 0; i < NMAX * MMAX; i++) {
if (lattice[i] == CO) coverage.CO_coverage += 1.0;
else if (lattice[i] == O) coverage.O_coverage += 1.0;
else if (lattice[i] == N) coverage.N_coverage += 1.0;
}
coverage.CO_coverage /= (NMAX * MMAX);
coverage.O_coverage /= (NMAX * MMAX);
coverage.N_coverage /= (NMAX * MMAX);
return coverage;
}
int find_random_site() {
return pcg32_boundedrand_r(&rng, NMAX * MMAX);
}
void print_lattice(LAT_ELEM lattice[NMAX * MMAX]){
for (int i = 0; i < NMAX; i++) {
for (int j = 0; j < MMAX; j++) {
printf("%d ", lattice[i * MMAX + j]);
}
printf("\n");
}
}
int find_random_neighbor(int site) {
int x = site / MMAX;
int y = site % MMAX;
int direction = pcg32_boundedrand_r(&rng, 4);
switch (direction) {
case 0:
x = (x - 1 + NMAX) % NMAX;
break;
case 1:
x = (x + 1) % NMAX;
break;
case 2:
y = (y - 1 + MMAX) % MMAX;
break;
default:
y = (y + 1) % MMAX;
}
return x * MMAX + y;
}
void find_nearest_neighbors(int site, int exclude, int neighbors[4]) {
int x = site / MMAX;
int y = site % MMAX;
int x_exclude = exclude / MMAX;
int y_exclude = exclude % MMAX;
neighbors[0] = ((x - 1 + NMAX) % NMAX) * MMAX + y;
neighbors[1] = ((x + 1) % NMAX) * MMAX + y;
neighbors[2] = x * MMAX + (y - 1 + MMAX) % MMAX;
neighbors[3] = x * MMAX + (y + 1) % MMAX;
}
void shuffle(int *array, int n) {
for (int i = n - 1; i > 0; i--) {
int j = pcg32_boundedrand_r(&rng, i + 1);
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
int coadsorption_events = 0;
void add_CO(LAT_ELEM lattice[NMAX * MMAX], int *co2_count, double coadsorption_probability) {
int site = find_random_site();
if (lattice[site] == EMPTY) {
lattice[site] = CO;
int neighbors[4];
find_nearest_neighbors(site, -1, neighbors);
int order[4] = {0, 1, 2, 3};
shuffle(order, 4);
for (int i = 0; i < 4; i++) {
int neighbor_site = neighbors[order[i]];
if (lattice[neighbor_site] == O) {
lattice[site] = EMPTY;
lattice[neighbor_site] = EMPTY;
(*co2_count)++;
return;
}
}
}
else if (lattice[site] == O){
double random_number = ldexp(pcg32_random_r(&rng) & 0x7FFFFFFF, -31);
if(random_number < random_number < coadsorption_probability){
lattice[site] = EMPTY;
(*co2_count)++;
coadsorption_events++;
return;
}
}
}
void add_NO_dissociate(LAT_ELEM lattice[NMAX * MMAX], int *co2_count, int *n2_count) {
int site = find_random_site();
int adj_site = find_random_neighbor(site);
if (lattice[site] == EMPTY && lattice[adj_site] == EMPTY) {
lattice[site] = N;
lattice[adj_site] = O;
bool reacted_with_co = false;
int neighbors_3[4];
find_nearest_neighbors(adj_site, -1, neighbors_3);
int order_3[4] = {0, 1, 2, 3};
shuffle(order_3, 4);
for (int j = 0; j < 4; j++) {
int neighbor_site_3 = neighbors_3[order_3[j]];
if (lattice[neighbor_site_3] == CO) {
lattice[adj_site] = EMPTY;
lattice[neighbor_site_3] = EMPTY;
(*co2_count)++;
reacted_with_co = true;
break;
}
}
int neighbors_1[4];
find_nearest_neighbors(site, adj_site, neighbors_1);
int order_1[4] = {0, 1, 2, 3};
shuffle(order_1, 4);
for (int i = 0; i < 4; i++) {
int neighbor_site = neighbors_1[order_1[i]];
if (lattice[neighbor_site] == N) {
lattice[site] = EMPTY;
lattice[neighbor_site] = EMPTY;
(*n2_count)++;
break;
}
}
return;
}
}
void write_lattice_to_csv(int N, int M, LAT_ELEM lattice[N * M], double XCO) {
char filename[50];
sprintf(filename, "lattice_XCO_%0.2f.csv", XCO);
FILE *file = fopen(filename, "w");
if (file == NULL) {
printf("Error opening file!\n");
return;
}
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
fprintf(file, "%d", lattice[i * M + j]);
if (j < M - 1) {
fprintf(file, ",");
}
}
fprintf(file, "\n");
}
fclose(file);
}
double mean(double *array, int size) {
double sum = 0.0;
for (int i = 0; i < size; i++) {
sum += array[i];
}
return sum / size;
}
double std_dev(double *array, int size) {
double avg = mean(array, size);
double sum_sq_diff = 0.0;
for (int i = 0; i < size; i++) {
double diff = array[i] - avg;
sum_sq_diff += diff * diff;
}
return sqrt(sum_sq_diff / size);
}
int main() {
clock_t start = clock();
pcg32_srandom_r(&rng, time(NULL) ^ (intptr_t)&printf, (intptr_t)&rng);
LAT_ELEM lattice[NMAX * MMAX];
double XCO_values[num_simulations];
double co_coverages_mean[num_simulations];
double o_coverages_mean[num_simulations];
double n_coverages_mean[num_simulations];
double co2_rates_mean[num_simulations];
double n2_rates_mean[num_simulations];
double co_coverages_std_dev[num_simulations];
double o_coverages_std_dev[num_simulations];
double n_coverages_std_dev[num_simulations];
double co2_rates_std_dev[num_simulations];
double n2_rates_std_dev[num_simulations];
double coadsorption_probability = 0.005000;
for (int i = 0; i < num_simulations; i++) {
double XCO = i / (double)(num_simulations - 1);
XCO_values[i] = XCO;
double co_coverages[num_runs];
double o_coverages[num_runs];
double n_coverages[num_runs];
double co2_rates[num_runs];
double n2_rates[num_runs];
for (int run = 0; run < num_runs; run++) {
for (int j = 0; j < 64 * 64; j++) {
lattice[j] = EMPTY;
}
int co2_count = 0, n2_count = 0;
Coverage sum_coverage = {0};
double sum_co2_rate = 0, sum_n2_rate = 0;
int data_points = 0;
for (int t = 0; t < TIME; t++) {
for (int k = 0; k < NMAX * MMAX; k++) {
double r = ldexp(pcg32_random_r(&rng) & 0x7FFFFFFF, -31);
if (r < XCO) {
add_CO(lattice, &co2_count, coadsorption_probability);
} else {
add_NO_dissociate(lattice, &co2_count, &n2_count);
}
}
if (t >= WARMUP && (t - WARMUP + 1) % 10 == 0) {
Coverage current_coverage = calculate_coverage(lattice);
sum_coverage.CO_coverage += current_coverage.CO_coverage;
sum_coverage.O_coverage += current_coverage.O_coverage;
sum_coverage.N_coverage += current_coverage.N_coverage;
sum_co2_rate += co2_count;
sum_n2_rate += n2_count;
data_points++;
}
}
co_coverages[run] = sum_coverage.CO_coverage / data_points;
o_coverages[run] = sum_coverage.O_coverage / data_points;
n_coverages[run] = sum_coverage.N_coverage / data_points;
co2_rates[run] = sum_co2_rate / data_points;
n2_rates[run] = sum_n2_rate / data_points;
}
co_coverages_mean[i] = mean(co_coverages, num_runs);
o_coverages_mean[i] = mean(o_coverages, num_runs);
n_coverages_mean[i] = mean(n_coverages, num_runs);
co2_rates_mean[i] = mean(co2_rates, num_runs);
n2_rates_mean[i] = mean(n2_rates, num_runs);
co_coverages_std_dev[i] = std_dev(co_coverages, num_runs);
o_coverages_std_dev[i] = std_dev(o_coverages, num_runs);
n_coverages_std_dev[i] = std_dev(n_coverages, num_runs);
co2_rates_std_dev[i] = std_dev(co2_rates, num_runs);
n2_rates_std_dev[i] = std_dev(n2_rates, num_runs);
}
FILE *file = fopen("3-coadsorption-disasociative-PCO-0-500.txt", "w");
if (file == NULL) {
printf("Error opening file!\n");
return EXIT_FAILURE;
}
for (int i = 0; i < num_simulations; i++) {
fprintf(file, "%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\n", XCO_values[i], co_coverages_mean[i], o_coverages_mean[i], n_coverages_mean[i], co2_rates_mean[i], n2_rates_mean[i], co_coverages_std_dev[i], o_coverages_std_dev[i], n_coverages_std_dev[i], co2_rates_std_dev[i], n2_rates_std_dev[i]);
}
fclose(file);
printf("The results were printed in the file\n");
clock_t end = clock();
double cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
printf("Total runtime: %f seconds\n", cpu_time_used);
return EXIT_SUCCESS;
}
The standard deviations of the coverage of CO, for low yco values (between 0 and 0.22) are very large and I shouldn't have that. In fact, for a yco value around 0.25, the CO coverage should "jump" from about 0.1 to 0.9 and I don't have that. I don't know if I'm implementing the data collection as indicated in the simulation steps I shared above ("the values of coverages/production rate are obtained after 10 MCS, so that the final values are an average of 3000 configurations").
I there a better way to collect my data and get better results?