kmclab 0.1.4__py3-none-any.whl → 0.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kmclab/__init__.py +3 -3
- kmclab/{hex.py → hexa.py} +166 -159
- kmclab/square.py +129 -116
- kmclab-0.1.5.dist-info/METADATA +262 -0
- kmclab-0.1.5.dist-info/RECORD +7 -0
- kmclab-0.1.4.dist-info/METADATA +0 -63
- kmclab-0.1.4.dist-info/RECORD +0 -7
- {kmclab-0.1.4.dist-info → kmclab-0.1.5.dist-info}/WHEEL +0 -0
- {kmclab-0.1.4.dist-info → kmclab-0.1.5.dist-info}/top_level.txt +0 -0
kmclab/__init__.py
CHANGED
kmclab/{hex.py → hexa.py}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
import numpy as np
|
|
3
3
|
from matplotlib.animation import FuncAnimation, PillowWriter
|
|
4
4
|
import matplotlib.cm as cm
|
|
@@ -6,98 +6,131 @@ from numpy.polynomial.polynomial import Polynomial
|
|
|
6
6
|
import matplotlib.pyplot as plt
|
|
7
7
|
import matplotlib.patches as patches
|
|
8
8
|
from scipy.stats import linregress
|
|
9
|
+
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
class hex_kmc:
|
|
11
|
+
class hexa_kmc:
|
|
12
12
|
|
|
13
|
-
def __init__(self, n_atoms, n_defects, n_adsorbates, lattice_size,
|
|
13
|
+
def __init__(self, n_atoms, n_defects, n_adsorbates, lattice_size,
|
|
14
|
+
T= 300,
|
|
15
|
+
defect_type = 1,
|
|
16
|
+
k_0=1,
|
|
17
|
+
seed=1,
|
|
18
|
+
len_vertical = 0.38e-3,
|
|
19
|
+
len_horizontal = 0.51e-3,
|
|
20
|
+
adsorbates_freq = -1,
|
|
21
|
+
energy_barrier_north = 0.46,
|
|
22
|
+
energy_barrier_south = 0.46,
|
|
23
|
+
energy_barrier_northeast = 0.65,
|
|
24
|
+
energy_barrier_northwest = 0.65,
|
|
25
|
+
energy_barrier_southeast = 0.65,
|
|
26
|
+
energy_barrier_southwest = 0.65,
|
|
27
|
+
energy_barrier_trapping_defect_north = 1.2,
|
|
28
|
+
energy_barrier_trapping_defect_south = 1.2,
|
|
29
|
+
energy_barrier_trapping_defect_east = 1.1,
|
|
30
|
+
energy_barrier_trapping_defect_west = 1.1,
|
|
31
|
+
energy_barrier_trapping_defect_northeast = 1.1,
|
|
32
|
+
energy_barrier_trapping_defect_northwest = 1.1,
|
|
33
|
+
energy_barrier_trapping_defect_southeast = 1.1,
|
|
34
|
+
energy_barrier_trapping_defect_southwest = 1.1,
|
|
35
|
+
energy_barrier_blocking_defect_north = 1.2,
|
|
36
|
+
energy_barrier_blocking_defect_south = 1.2,
|
|
37
|
+
energy_barrier_blocking_defect_northeast = 1.2,
|
|
38
|
+
energy_barrier_blocking_defect_northwest = 1.2,
|
|
39
|
+
energy_barrier_blocking_defect_southeast = 1.2,
|
|
40
|
+
energy_barrier_blocking_defect_southwest = 1.2,
|
|
41
|
+
energy_barrier_adsorbate_north = 0.72,
|
|
42
|
+
energy_barrier_adsorbate_south = 0.72,
|
|
43
|
+
energy_barrier_adsorbate_northeast = 0.72,
|
|
44
|
+
energy_barrier_adsorbate_northwest = 0.72,
|
|
45
|
+
energy_barrier_adsorbate_southeast = 0.72,
|
|
46
|
+
energy_barrier_adsorbate_southwest = 0.72,
|
|
47
|
+
):
|
|
48
|
+
|
|
14
49
|
|
|
15
50
|
np.random.seed(seed)
|
|
16
|
-
# what is you desired coverage= n_atom/lattice * lattice
|
|
17
51
|
self.n_atoms = n_atoms # Number of atoms
|
|
18
|
-
self.n_defects = n_defects
|
|
19
|
-
self.n_adsorbates= n_adsorbates
|
|
52
|
+
self.n_defects = n_defects # Number of defects
|
|
53
|
+
self.n_adsorbates= n_adsorbates # Number of adsorbates
|
|
20
54
|
self.lattice_size = lattice_size # Lattice size
|
|
21
|
-
self.
|
|
22
|
-
self.defect_type = defect_type
|
|
23
|
-
self.
|
|
24
|
-
|
|
25
|
-
|
|
55
|
+
self.T = T # Temperature in Kelvin
|
|
56
|
+
self.defect_type = defect_type # blocking = 2 , trapping = 1
|
|
57
|
+
self.k_0 = k_0
|
|
58
|
+
self.len_vertical = len_horizontal
|
|
59
|
+
self.len_horizontal = len_horizontal
|
|
60
|
+
self.adsorbates_freq = adsorbates_freq
|
|
26
61
|
k_B = 8.617e-5 # Boltzmann constant in eV/K
|
|
27
|
-
k_0 = 1
|
|
28
62
|
h = 4.1357e-15 #Planck Constant (eV.s)
|
|
29
|
-
|
|
30
|
-
self.len_horizontal = 0.51e-3 # in micrometer
|
|
31
|
-
|
|
63
|
+
|
|
32
64
|
|
|
33
65
|
# DFT calculated vaues for diffusion on the stoichiometric surface in different directions (eV)
|
|
34
|
-
energy_barrier_north =
|
|
35
|
-
energy_barrier_south =
|
|
36
|
-
energy_barrier_northeast =
|
|
37
|
-
energy_barrier_northwest =
|
|
38
|
-
energy_barrier_southeast =
|
|
39
|
-
energy_barrier_southwest =
|
|
66
|
+
self.energy_barrier_north = energy_barrier_north
|
|
67
|
+
self.energy_barrier_south = energy_barrier_south
|
|
68
|
+
self.energy_barrier_northeast = energy_barrier_northeast
|
|
69
|
+
self.energy_barrier_northwest = energy_barrier_northwest
|
|
70
|
+
self.energy_barrier_southeast = energy_barrier_southeast
|
|
71
|
+
self.energy_barrier_southwest = energy_barrier_southwest
|
|
40
72
|
|
|
41
73
|
|
|
42
74
|
# DFT calculated vaues for diffusion out of trapping deffect in different directions (eV)
|
|
43
|
-
energy_barrier_trapping_defect_north =
|
|
44
|
-
energy_barrier_trapping_defect_south =
|
|
45
|
-
energy_barrier_trapping_defect_east =
|
|
46
|
-
energy_barrier_trapping_defect_west =
|
|
47
|
-
energy_barrier_trapping_defect_northeast =
|
|
48
|
-
energy_barrier_trapping_defect_northwest =
|
|
49
|
-
energy_barrier_trapping_defect_southeast =
|
|
50
|
-
energy_barrier_trapping_defect_southwest =
|
|
75
|
+
self.energy_barrier_trapping_defect_north = energy_barrier_trapping_defect_north
|
|
76
|
+
self.energy_barrier_trapping_defect_south = energy_barrier_trapping_defect_south
|
|
77
|
+
self.energy_barrier_trapping_defect_east = energy_barrier_trapping_defect_east
|
|
78
|
+
self.energy_barrier_trapping_defect_west = energy_barrier_trapping_defect_west
|
|
79
|
+
self.energy_barrier_trapping_defect_northeast = energy_barrier_trapping_defect_northeast
|
|
80
|
+
self.energy_barrier_trapping_defect_northwest = energy_barrier_trapping_defect_northwest
|
|
81
|
+
self.energy_barrier_trapping_defect_southeast = energy_barrier_trapping_defect_southeast
|
|
82
|
+
self.energy_barrier_trapping_defect_southwest = energy_barrier_trapping_defect_southwest
|
|
51
83
|
|
|
52
84
|
|
|
53
85
|
# DFT calculated vaues for diffusion over a blocking deffect in different directions (eV)
|
|
54
|
-
energy_barrier_blocking_defect_north =
|
|
55
|
-
energy_barrier_blocking_defect_south =
|
|
56
|
-
energy_barrier_blocking_defect_northeast =
|
|
57
|
-
energy_barrier_blocking_defect_northwest =
|
|
58
|
-
energy_barrier_blocking_defect_southeast =
|
|
59
|
-
energy_barrier_blocking_defect_southwest =
|
|
86
|
+
self.energy_barrier_blocking_defect_north = energy_barrier_blocking_defect_north
|
|
87
|
+
self.energy_barrier_blocking_defect_south = energy_barrier_blocking_defect_south
|
|
88
|
+
self.energy_barrier_blocking_defect_northeast = energy_barrier_blocking_defect_northeast
|
|
89
|
+
self.energy_barrier_blocking_defect_northwest = energy_barrier_blocking_defect_northwest
|
|
90
|
+
self.energy_barrier_blocking_defect_southeast = energy_barrier_blocking_defect_southeast
|
|
91
|
+
self.energy_barrier_blocking_defect_southwest = energy_barrier_blocking_defect_southwest
|
|
92
|
+
|
|
60
93
|
# DFT calculated vaues for diffusion over an adsorbate in different directions (eV)
|
|
61
94
|
|
|
62
|
-
energy_barrier_adsorbate_north =
|
|
63
|
-
energy_barrier_adsorbate_south =
|
|
64
|
-
energy_barrier_adsorbate_northeast =
|
|
65
|
-
energy_barrier_adsorbate_northwest =
|
|
66
|
-
energy_barrier_adsorbate_southeast =
|
|
67
|
-
energy_barrier_adsorbate_southwest =
|
|
95
|
+
self.energy_barrier_adsorbate_north = energy_barrier_adsorbate_north
|
|
96
|
+
self.energy_barrier_adsorbate_south = energy_barrier_adsorbate_south
|
|
97
|
+
self.energy_barrier_adsorbate_northeast = energy_barrier_adsorbate_northeast
|
|
98
|
+
self.energy_barrier_adsorbate_northwest = energy_barrier_adsorbate_northwest
|
|
99
|
+
self.energy_barrier_adsorbate_southeast = energy_barrier_adsorbate_southeast
|
|
100
|
+
self.energy_barrier_adsorbate_southwest = energy_barrier_adsorbate_southwest
|
|
68
101
|
|
|
69
102
|
|
|
70
103
|
# Calculate rate constants
|
|
71
|
-
rate_north = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_north / (k_B * T))
|
|
72
|
-
rate_south = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_south / (k_B * T))
|
|
73
|
-
rate_northeast = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_northeast / (k_B * T))
|
|
74
|
-
rate_northwest = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_northwest / (k_B * T))
|
|
75
|
-
rate_southeast = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_southeast / (k_B * T))
|
|
76
|
-
rate_southwest = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_southwest / (k_B * T))
|
|
104
|
+
rate_north = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_north / (k_B * self.T))
|
|
105
|
+
rate_south = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_south / (k_B * self.T))
|
|
106
|
+
rate_northeast = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_northeast / (k_B * self.T))
|
|
107
|
+
rate_northwest = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_northwest / (k_B * self.T))
|
|
108
|
+
rate_southeast = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_southeast / (k_B * self.T))
|
|
109
|
+
rate_southwest = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_southwest / (k_B * self.T))
|
|
77
110
|
|
|
78
|
-
rate_trapping_defect_north = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_trapping_defect_north / (k_B * T))
|
|
79
|
-
rate_trapping_defect_south = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_trapping_defect_south / (k_B * T))
|
|
80
|
-
rate_trapping_defect_east = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_trapping_defect_east / (k_B * T))
|
|
81
|
-
rate_trapping_defect_west = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_trapping_defect_west / (k_B * T))
|
|
82
|
-
rate_trapping_defect_northeast = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_trapping_defect_northeast / (k_B * T))
|
|
83
|
-
rate_trapping_defect_northwest = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_trapping_defect_northwest / (k_B * T))
|
|
84
|
-
rate_trapping_defect_southeast = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_trapping_defect_southeast / (k_B *
|
|
85
|
-
rate_trapping_defect_southwest = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_trapping_defect_southwest / (k_B * T))
|
|
111
|
+
rate_trapping_defect_north = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_trapping_defect_north / (k_B * self.T))
|
|
112
|
+
rate_trapping_defect_south = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_trapping_defect_south / (k_B * self.T))
|
|
113
|
+
rate_trapping_defect_east = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_trapping_defect_east / (k_B * self.T))
|
|
114
|
+
rate_trapping_defect_west = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_trapping_defect_west / (k_B * self.T))
|
|
115
|
+
rate_trapping_defect_northeast = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_trapping_defect_northeast / (k_B * self.T))
|
|
116
|
+
rate_trapping_defect_northwest = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_trapping_defect_northwest / (k_B * self.T))
|
|
117
|
+
rate_trapping_defect_southeast = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_trapping_defect_southeast / (k_B *self.T))
|
|
118
|
+
rate_trapping_defect_southwest = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_trapping_defect_southwest / (k_B * self.T))
|
|
86
119
|
|
|
87
120
|
|
|
88
|
-
rate_blocking_defect_north = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_blocking_defect_north / (k_B * T))
|
|
89
|
-
rate_blocking_defect_south = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_blocking_defect_south / (k_B * T))
|
|
90
|
-
rate_blocking_defect_northeast = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_blocking_defect_northeast / (k_B * T))
|
|
91
|
-
rate_blocking_defect_northwest = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_blocking_defect_northwest / (k_B * T))
|
|
92
|
-
rate_blocking_defect_southeast = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_blocking_defect_southeast / (k_B * T))
|
|
93
|
-
rate_blocking_defect_southwest = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_blocking_defect_southwest / (k_B * T))
|
|
121
|
+
rate_blocking_defect_north = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_blocking_defect_north / (k_B * self.T))
|
|
122
|
+
rate_blocking_defect_south = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_blocking_defect_south / (k_B * self.T))
|
|
123
|
+
rate_blocking_defect_northeast = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_blocking_defect_northeast / (k_B * self.T))
|
|
124
|
+
rate_blocking_defect_northwest = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_blocking_defect_northwest / (k_B * self.T))
|
|
125
|
+
rate_blocking_defect_southeast = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_blocking_defect_southeast / (k_B * self.T))
|
|
126
|
+
rate_blocking_defect_southwest = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_blocking_defect_southwest / (k_B * self.T))
|
|
94
127
|
|
|
95
|
-
rate_adsorbate_north = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_adsorbate_north / (k_B * T))
|
|
96
|
-
rate_adsorbate_south = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_adsorbate_south / (k_B * T))
|
|
97
|
-
rate_adsorbate_northeast = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_adsorbate_northeast / (k_B * T))
|
|
98
|
-
rate_adsorbate_northwest = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_adsorbate_northwest / (k_B * T))
|
|
99
|
-
rate_adsorbate_southeast = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_adsorbate_southeast / (k_B * T))
|
|
100
|
-
rate_adsorbate_southwest = k_0 * ((k_B * T)/h) * np.exp(-energy_barrier_adsorbate_southwest / (k_B * T))
|
|
128
|
+
rate_adsorbate_north = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_adsorbate_north / (k_B * self.T))
|
|
129
|
+
rate_adsorbate_south = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_adsorbate_south / (k_B * self.T))
|
|
130
|
+
rate_adsorbate_northeast = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_adsorbate_northeast / (k_B * self.T))
|
|
131
|
+
rate_adsorbate_northwest = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_adsorbate_northwest / (k_B * self.T))
|
|
132
|
+
rate_adsorbate_southeast = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_adsorbate_southeast / (k_B * self.T))
|
|
133
|
+
rate_adsorbate_southwest = self.k_0 * ((k_B * self.T)/h) * np.exp(-self.energy_barrier_adsorbate_southwest / (k_B * self.T))
|
|
101
134
|
|
|
102
135
|
|
|
103
136
|
self.moves = {
|
|
@@ -156,8 +189,10 @@ class hex_kmc:
|
|
|
156
189
|
|
|
157
190
|
self.generate_adsorbates()
|
|
158
191
|
|
|
159
|
-
def run(self):
|
|
192
|
+
def run(self, n_steps):
|
|
193
|
+
|
|
160
194
|
|
|
195
|
+
self.n_steps = n_steps
|
|
161
196
|
self.time = np.zeros(self.n_steps + 1)
|
|
162
197
|
self.msd = np.zeros(self.n_steps)
|
|
163
198
|
self.md = np.zeros(self.n_steps)
|
|
@@ -267,8 +302,7 @@ class hex_kmc:
|
|
|
267
302
|
total_rate += list(self.move_rates_trapping_defects.values())[dir_idx]
|
|
268
303
|
possible_moves.append((atom_idx, dir_idx, list(self.move_rates_trapping_defects.values())[dir_idx], (new_x, new_y), dx, dy))
|
|
269
304
|
|
|
270
|
-
|
|
271
|
-
|
|
305
|
+
|
|
272
306
|
|
|
273
307
|
else:
|
|
274
308
|
|
|
@@ -570,11 +604,11 @@ class hex_kmc:
|
|
|
570
604
|
raise ValueError(f"n_defects ({n_defects}) is too large for lattice_size={lattice_size}!")
|
|
571
605
|
|
|
572
606
|
if n_defects == 0:
|
|
573
|
-
return np.empty((0, 2), dtype=int)
|
|
607
|
+
return np.empty((0, 2), dtype=int)
|
|
574
608
|
|
|
575
609
|
positions_base_defects = set()
|
|
576
610
|
attempts = 0
|
|
577
|
-
max_attempts = 20000
|
|
611
|
+
max_attempts = 20000
|
|
578
612
|
|
|
579
613
|
y_values_even = np.arange(0, lattice_size -1, 1)
|
|
580
614
|
y_values_odd = np.arange(0.5, lattice_size - 0.5, 1)
|
|
@@ -599,10 +633,8 @@ class hex_kmc:
|
|
|
599
633
|
|
|
600
634
|
positions_base_defects = np.array(list(positions_base_defects))
|
|
601
635
|
|
|
602
|
-
# Compute the "above" positions (shifted by one row up)
|
|
603
636
|
positions_above_defects = np.column_stack((positions_base_defects[:, 0], ((positions_base_defects[:, 1] + 1) % lattice_size)))
|
|
604
637
|
|
|
605
|
-
# Combine base and above positions
|
|
606
638
|
positions_defects = np.vstack((positions_base_defects, positions_above_defects))
|
|
607
639
|
|
|
608
640
|
return positions_defects
|
|
@@ -616,43 +648,34 @@ class hex_kmc:
|
|
|
616
648
|
|
|
617
649
|
self.positions_adsorbates = []
|
|
618
650
|
attempts = 0
|
|
619
|
-
max_attempts = 1000
|
|
651
|
+
max_attempts = 1000
|
|
620
652
|
|
|
621
653
|
while len(self.positions_adsorbates) < self.n_adsorbates and attempts < max_attempts:
|
|
622
654
|
|
|
623
|
-
# print(f'attempt = {attempts}')
|
|
624
655
|
attempts += 1
|
|
625
656
|
|
|
626
657
|
i = np.random.choice(len(self.hex_lattice), replace=False)
|
|
627
658
|
pos_adsorbates= self.hex_lattice[i]
|
|
628
|
-
|
|
629
|
-
# Check if hydroxyl overlaps with an oxygen vacancy (not allowed)
|
|
659
|
+
|
|
630
660
|
if pos_adsorbates in map(tuple, self.positions_defects) or pos_adsorbates in map(tuple, self.positions_atoms):
|
|
631
|
-
continue
|
|
661
|
+
continue
|
|
632
662
|
|
|
633
|
-
|
|
634
|
-
# If all conditions are met, accept the position
|
|
635
663
|
self.positions_adsorbates.append(pos_adsorbates)
|
|
636
664
|
|
|
637
665
|
self.positions_adsorbates = np.array(self.positions_adsorbates)
|
|
638
666
|
|
|
639
667
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
def anim1panels(self, filename):
|
|
668
|
+
def anim1panel(self, filename = "movie1panel", figsize = (6,6)):
|
|
644
669
|
|
|
645
|
-
|
|
646
670
|
# ===================== Figure & Axes =====================
|
|
647
|
-
fig, ax_main = plt.subplots(figsize=
|
|
671
|
+
fig, ax_main = plt.subplots(figsize=figsize)
|
|
648
672
|
ax_main.set_xlim(-1, self.lattice_size + 1)
|
|
649
673
|
ax_main.set_ylim(-1, self.lattice_size + 1)
|
|
650
674
|
ax_main.set_xticks(range(self.lattice_size))
|
|
651
675
|
ax_main.set_yticks(range(self.lattice_size))
|
|
652
676
|
ax_main.grid(True, linestyle='--', linewidth=0.5)
|
|
653
|
-
ax_main.set_aspect('equal', adjustable='box')
|
|
677
|
+
ax_main.set_aspect('equal', adjustable='box')
|
|
654
678
|
|
|
655
|
-
# Background lattice (optional)
|
|
656
679
|
if hasattr(self, "hex_lattice") and len(self.hex_lattice) > 0:
|
|
657
680
|
hx, hy = zip(*self.hex_lattice)
|
|
658
681
|
ax_main.scatter(hx, hy, s=20, color="gray", alpha=0.5, label="Lattice Sites")
|
|
@@ -664,11 +687,11 @@ class hex_kmc:
|
|
|
664
687
|
for defect1, defect2 in self.defects_pairs:
|
|
665
688
|
x_min = min(defect1[0], defect2[0])
|
|
666
689
|
y_min = min(defect1[1], defect2[1])
|
|
667
|
-
if defect1[1] == defect2[1]:
|
|
690
|
+
if defect1[1] == defect2[1]:
|
|
668
691
|
width, height = abs(defect1[0] - defect2[0]) + 1, 1
|
|
669
|
-
elif defect1[0] == defect2[0]:
|
|
692
|
+
elif defect1[0] == defect2[0]:
|
|
670
693
|
width, height = 1, abs(defect1[1] - defect2[1]) + 1
|
|
671
|
-
else:
|
|
694
|
+
else:
|
|
672
695
|
width = abs(defect1[0] - defect2[0]) + 1
|
|
673
696
|
height = abs(defect1[1] - defect2[1]) + 1
|
|
674
697
|
|
|
@@ -688,16 +711,14 @@ class hex_kmc:
|
|
|
688
711
|
adsorbates_circles.append(c)
|
|
689
712
|
|
|
690
713
|
# ===================== Atom Gradient Setup =====================
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
center_color = np.array([240/255, 245/255, 250/255, 1.0]) # very light (near white)
|
|
694
|
-
edge_color = np.array([176/255, 196/255, 222/255, 1.0]) #
|
|
714
|
+
center_color = np.array([240/255, 245/255, 250/255, 1.0])
|
|
715
|
+
edge_color = np.array([176/255, 196/255, 222/255, 1.0])
|
|
695
716
|
|
|
696
717
|
def radial_gradient_image(resolution=256):
|
|
697
718
|
y, x = np.ogrid[-1:1:complex(0, resolution), -1:1:complex(0, resolution)]
|
|
698
719
|
r = np.sqrt(x*x + y*y)
|
|
699
720
|
r = np.clip(r, 0, 1)
|
|
700
|
-
grad = (1 - r)[..., None] * center_color + r[..., None] * edge_color
|
|
721
|
+
grad = (1 - r)[..., None] * center_color + r[..., None] * edge_color
|
|
701
722
|
return grad.astype(float)
|
|
702
723
|
|
|
703
724
|
grad_img = radial_gradient_image(256)
|
|
@@ -705,11 +726,10 @@ class hex_kmc:
|
|
|
705
726
|
atom_radius = 0.25
|
|
706
727
|
outline_color = 'midnightblue'
|
|
707
728
|
outline_width = 1.5
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
atom_artists = [] # list of (circle_patch, image_artist)
|
|
729
|
+
|
|
730
|
+
atom_artists = []
|
|
711
731
|
for _ in range(self.n_atoms):
|
|
712
|
-
|
|
732
|
+
|
|
713
733
|
circle = patches.Circle(
|
|
714
734
|
(0, 0), radius=atom_radius,
|
|
715
735
|
edgecolor=outline_color, facecolor='none',
|
|
@@ -717,7 +737,7 @@ class hex_kmc:
|
|
|
717
737
|
)
|
|
718
738
|
ax_main.add_patch(circle)
|
|
719
739
|
|
|
720
|
-
|
|
740
|
+
|
|
721
741
|
im = ax_main.imshow(
|
|
722
742
|
grad_img,
|
|
723
743
|
extent=[-atom_radius, atom_radius, -atom_radius, atom_radius],
|
|
@@ -726,12 +746,12 @@ class hex_kmc:
|
|
|
726
746
|
zorder=6,
|
|
727
747
|
alpha=1.0
|
|
728
748
|
)
|
|
729
|
-
im.set_clip_path(circle)
|
|
749
|
+
im.set_clip_path(circle)
|
|
730
750
|
im.set_clip_on(True)
|
|
731
751
|
|
|
732
752
|
atom_artists.append((circle, im))
|
|
733
753
|
|
|
734
|
-
|
|
754
|
+
|
|
735
755
|
labels = [
|
|
736
756
|
ax_main.text(
|
|
737
757
|
0, 0, str(i),
|
|
@@ -745,30 +765,30 @@ class hex_kmc:
|
|
|
745
765
|
|
|
746
766
|
# ===================== Update Function =====================
|
|
747
767
|
def update(frame):
|
|
748
|
-
|
|
768
|
+
|
|
749
769
|
for i, (circle, im) in enumerate(atom_artists):
|
|
750
770
|
x, y = self.positions_over_time[frame][i]
|
|
751
771
|
circle.center = (x, y)
|
|
752
772
|
im.set_extent([x - atom_radius, x + atom_radius, y - atom_radius, y + atom_radius])
|
|
753
|
-
|
|
773
|
+
|
|
754
774
|
im.set_clip_path(circle)
|
|
755
775
|
labels[i].set_position((x, y))
|
|
756
776
|
|
|
757
|
-
|
|
777
|
+
|
|
758
778
|
if hasattr(self, "selected_moves_info") and frame < len(self.selected_moves_info):
|
|
759
779
|
atom_idx, move_name = self.selected_moves_info[frame]
|
|
760
780
|
title_text.set_text(f"next: selected atom is atom_{atom_idx} and selected move is {move_name}")
|
|
761
781
|
else:
|
|
762
782
|
title_text.set_text("")
|
|
763
783
|
|
|
764
|
-
|
|
784
|
+
|
|
765
785
|
if adsorbates_circles:
|
|
766
786
|
for i, pos in enumerate(self.positions_adsorbates_over_time[frame]):
|
|
767
787
|
adsorbates_circles[i].center = (pos[0], pos[1])
|
|
768
788
|
|
|
769
789
|
# ===================== Animate (pause on last frame) =====================
|
|
770
790
|
interval_ms = 100
|
|
771
|
-
pause_frames = int(10_000 / interval_ms)
|
|
791
|
+
pause_frames = int(10_000 / interval_ms)
|
|
772
792
|
def update_with_pause(frame):
|
|
773
793
|
if frame < self.n_steps:
|
|
774
794
|
update(frame)
|
|
@@ -778,23 +798,18 @@ class hex_kmc:
|
|
|
778
798
|
ani = FuncAnimation(fig, update_with_pause, frames=self.n_steps + pause_frames, interval=interval_ms)
|
|
779
799
|
ani.save(f"{filename}.gif", writer=PillowWriter(fps=20))
|
|
780
800
|
plt.show()
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
801
|
|
|
785
|
-
|
|
786
|
-
def anim2panels(self, filename):
|
|
802
|
+
def anim2panel(self, filename = "movie2panel", figsize = (12, 6)):
|
|
787
803
|
|
|
788
|
-
fig, axes = plt.subplots(1, 2, figsize=
|
|
804
|
+
fig, axes = plt.subplots(1, 2, figsize=figsize, gridspec_kw={'width_ratios': [2, 1]})
|
|
789
805
|
ax_main, ax_msd = axes[0], axes[1]
|
|
790
806
|
|
|
791
|
-
# ---- Main lattice view ----
|
|
792
807
|
ax_main.set_xlim(-1, self.lattice_size + 1)
|
|
793
808
|
ax_main.set_ylim(-1, self.lattice_size + 1)
|
|
794
809
|
ax_main.set_xticks(range(self.lattice_size))
|
|
795
810
|
ax_main.set_yticks(range(self.lattice_size))
|
|
796
811
|
ax_main.grid(True, linestyle='--', linewidth=0.5)
|
|
797
|
-
ax_main.set_aspect('equal', adjustable='box')
|
|
812
|
+
ax_main.set_aspect('equal', adjustable='box')
|
|
798
813
|
|
|
799
814
|
if hasattr(self, "hex_lattice") and len(self.hex_lattice) > 0:
|
|
800
815
|
hex_x, hex_y = zip(*self.hex_lattice)
|
|
@@ -803,15 +818,15 @@ class hex_kmc:
|
|
|
803
818
|
title_text = ax_main.set_title("")
|
|
804
819
|
|
|
805
820
|
# ---- OV rectangles: whitesmoke fill with adjustable transparency ----
|
|
806
|
-
defect_alpha = 0.4
|
|
821
|
+
defect_alpha = 0.4
|
|
807
822
|
if hasattr(self, "defects_pairs"):
|
|
808
823
|
for defect1, defect2 in self.defects_pairs:
|
|
809
824
|
x_min = min(defect1[0], defect2[0])
|
|
810
825
|
y_min = min(defect1[1], defect2[1])
|
|
811
826
|
|
|
812
|
-
if defect1[1] == defect2[1]:
|
|
827
|
+
if defect1[1] == defect2[1]:
|
|
813
828
|
width, height = abs(defect1[0] - defect2[0]) + 1, 1
|
|
814
|
-
elif defect1[0] == defect2[0]:
|
|
829
|
+
elif defect1[0] == defect2[0]:
|
|
815
830
|
width, height = 1, abs(defect1[1] - defect2[1]) + 1
|
|
816
831
|
else:
|
|
817
832
|
width = abs(defect1[0] - defect2[0]) + 1
|
|
@@ -835,16 +850,15 @@ class hex_kmc:
|
|
|
835
850
|
ax_main.add_patch(c)
|
|
836
851
|
adsorbates_circles.append(c)
|
|
837
852
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
edge_color = np.array([176/255, 196/255, 222/255, 1.0]) # lightsteelblue (#B0C4DE)
|
|
853
|
+
|
|
854
|
+
center_color = np.array([240/255, 245/255, 250/255, 1.0])
|
|
855
|
+
edge_color = np.array([176/255, 196/255, 222/255, 1.0])
|
|
842
856
|
|
|
843
857
|
def radial_gradient_image(resolution=256):
|
|
844
858
|
y, x = np.ogrid[-1:1:complex(0, resolution), -1:1:complex(0, resolution)]
|
|
845
859
|
r = np.sqrt(x*x + y*y)
|
|
846
860
|
r = np.clip(r, 0, 1)
|
|
847
|
-
grad = (1 - r)[..., None] * center_color + r[..., None] * edge_color
|
|
861
|
+
grad = (1 - r)[..., None] * center_color + r[..., None] * edge_color
|
|
848
862
|
return grad.astype(float)
|
|
849
863
|
|
|
850
864
|
grad_img = radial_gradient_image(256)
|
|
@@ -853,7 +867,7 @@ class hex_kmc:
|
|
|
853
867
|
outline_width = 1.5
|
|
854
868
|
|
|
855
869
|
# Create per-atom (circle outline + clipped gradient image)
|
|
856
|
-
atom_artists = []
|
|
870
|
+
atom_artists = []
|
|
857
871
|
for _ in range(self.n_atoms):
|
|
858
872
|
circle = patches.Circle(
|
|
859
873
|
(0, 0), radius=atom_radius,
|
|
@@ -875,7 +889,7 @@ class hex_kmc:
|
|
|
875
889
|
|
|
876
890
|
atom_artists.append((circle, im))
|
|
877
891
|
|
|
878
|
-
|
|
892
|
+
|
|
879
893
|
labels = [
|
|
880
894
|
ax_main.text(
|
|
881
895
|
0, 0, str(i),
|
|
@@ -915,7 +929,7 @@ class hex_kmc:
|
|
|
915
929
|
|
|
916
930
|
# ---- Frame update ----
|
|
917
931
|
def update(frame):
|
|
918
|
-
|
|
932
|
+
|
|
919
933
|
for i, (circle, im) in enumerate(atom_artists):
|
|
920
934
|
x, y = self.positions_over_time[frame][i]
|
|
921
935
|
circle.center = (x, y)
|
|
@@ -923,18 +937,18 @@ class hex_kmc:
|
|
|
923
937
|
im.set_clip_path(circle) # safe across backends
|
|
924
938
|
labels[i].set_position((x, y))
|
|
925
939
|
|
|
926
|
-
|
|
940
|
+
|
|
927
941
|
atom_idx, selected_move_name = self.selected_moves_info[frame]
|
|
928
942
|
title_text.set_text(f"next: selected atom is atom_{atom_idx} and selected move is {selected_move_name}")
|
|
929
943
|
|
|
930
|
-
|
|
944
|
+
|
|
931
945
|
for i, pos in enumerate(self.positions_adsorbates_over_time[frame]):
|
|
932
946
|
adsorbates_circles[i].center = (pos[0], pos[1])
|
|
933
947
|
|
|
934
|
-
|
|
948
|
+
|
|
935
949
|
msd_line.set_data(self.time[:frame], self.msd[:frame])
|
|
936
950
|
|
|
937
|
-
|
|
951
|
+
|
|
938
952
|
move_counts_up_to_frame = {m: 0 for m in move_labels}
|
|
939
953
|
for _, mname in self.selected_moves_info[:frame]:
|
|
940
954
|
move_counts_up_to_frame[mname] += 1
|
|
@@ -943,7 +957,7 @@ class hex_kmc:
|
|
|
943
957
|
pct = 100.0 * move_counts_up_to_frame[m] / total_moves
|
|
944
958
|
move_texts[i].set_text(f"{m}: {pct:.1f}%")
|
|
945
959
|
|
|
946
|
-
|
|
960
|
+
|
|
947
961
|
if frame > 1:
|
|
948
962
|
x_fit = self.time[:frame]
|
|
949
963
|
y_fit = self.msd[:frame]
|
|
@@ -957,7 +971,7 @@ class hex_kmc:
|
|
|
957
971
|
|
|
958
972
|
# ---- Animate with pause at end ----
|
|
959
973
|
interval_ms = 100
|
|
960
|
-
pause_frames = int(10_000 / interval_ms)
|
|
974
|
+
pause_frames = int(10_000 / interval_ms)
|
|
961
975
|
def update_with_pause(frame):
|
|
962
976
|
if frame < self.n_steps:
|
|
963
977
|
update(frame)
|
|
@@ -967,16 +981,13 @@ class hex_kmc:
|
|
|
967
981
|
ani = FuncAnimation(fig, update_with_pause, frames=self.n_steps + pause_frames, interval=interval_ms)
|
|
968
982
|
ani.save(f"{filename}.gif", writer=PillowWriter(fps=20))
|
|
969
983
|
plt.show()
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
def msdplot(self, filename):
|
|
984
|
+
|
|
985
|
+
def msdplot(self, filename = "msd"):
|
|
975
986
|
|
|
976
987
|
time = self.time[:-1]
|
|
977
988
|
|
|
978
989
|
## Calculate overall diffusion coefficient
|
|
979
|
-
transient_cutoff = int(0 * self.n_steps) # Ignore the first
|
|
990
|
+
transient_cutoff = int(0 * self.n_steps) # Ignore the first 0% of steps
|
|
980
991
|
valid_time = time[transient_cutoff:] # Exclude transient region
|
|
981
992
|
valid_msd = self.msd[transient_cutoff:] # Exclude transient region
|
|
982
993
|
|
|
@@ -992,13 +1003,12 @@ class hex_kmc:
|
|
|
992
1003
|
plt.figure(figsize=(8, 6))
|
|
993
1004
|
plt.plot(time, self.msd, color="#89CFF0", label="Individual MSD Trajectory")
|
|
994
1005
|
plt.plot(valid_time, fit_line, linestyle="--", color="blue", label="Linear Fit")
|
|
995
|
-
#plt.axvline(x=time[transient_cutoff], color='r', linestyle='--', label="Transient cutoff")
|
|
996
1006
|
plt.xlabel("Time (s)")
|
|
997
1007
|
plt.ylabel("MSD (µm²)")
|
|
998
1008
|
plt.legend()
|
|
999
1009
|
plt.title(f"Mean Squared Displacement vs Time - {self.n_steps} steps")
|
|
1000
1010
|
plt.text(0.05 * max(time), 0.8 * max(self.msd), # Adjust placement (x, y) as needed
|
|
1001
|
-
f" Diffusion Coefficient = {diffusion_coefficient_corrected:.
|
|
1011
|
+
f" Diffusion Coefficient = {diffusion_coefficient_corrected:.1e} µm²/s",
|
|
1002
1012
|
fontsize=12, color='blue', bbox=dict(facecolor="white", alpha=0.5))
|
|
1003
1013
|
plt.minorticks_on()
|
|
1004
1014
|
plt.grid(True, which='major', linestyle='-', linewidth=0.6)
|
|
@@ -1006,15 +1016,12 @@ class hex_kmc:
|
|
|
1006
1016
|
plt.savefig(f'{filename}.png', dpi = 600)
|
|
1007
1017
|
plt.show()
|
|
1008
1018
|
|
|
1019
|
+
def msd_histogram(self, filename = "average_msd", n_seeds = 25):
|
|
1009
1020
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
msd_trajs_color = "pink",
|
|
1015
|
-
msd_average_color = "#C71585"):
|
|
1016
|
-
|
|
1017
|
-
|
|
1021
|
+
msd_folder = "random_seeds/msd"
|
|
1022
|
+
time_folder = "random_seeds/time"
|
|
1023
|
+
save_folder = f"random_seeds/{filename}.png"
|
|
1024
|
+
|
|
1018
1025
|
msd_list = []
|
|
1019
1026
|
time_list = []
|
|
1020
1027
|
|
|
@@ -1034,7 +1041,7 @@ class hex_kmc:
|
|
|
1034
1041
|
time_avg = np.mean(time_array, axis=0)
|
|
1035
1042
|
|
|
1036
1043
|
# Fit a linear function: MSD = a * Time + b
|
|
1037
|
-
|
|
1044
|
+
|
|
1038
1045
|
slope, intercept, r_value, p_value, slope_std_err = linregress(time_avg, msd_avg)
|
|
1039
1046
|
fitted_line = slope * time_avg + intercept # Compute the fitted line
|
|
1040
1047
|
|
|
@@ -1045,23 +1052,23 @@ class hex_kmc:
|
|
|
1045
1052
|
# Plot all MSD curves in light pink
|
|
1046
1053
|
plt.figure(figsize=(8, 6))
|
|
1047
1054
|
for i in range(n_seeds):
|
|
1048
|
-
plt.plot(time_array[i], msd_array[i], color=
|
|
1055
|
+
plt.plot(time_array[i], msd_array[i], color="pink", alpha=0.5, linewidth=1)
|
|
1049
1056
|
|
|
1050
1057
|
# Plot average MSD with error bars at 20 selected points in dark pink
|
|
1051
|
-
plt.plot(time_avg, msd_avg, color=
|
|
1052
|
-
plt.errorbar(time_avg[indices], msd_avg[indices], yerr=msd_std[indices], fmt='o', color=
|
|
1058
|
+
plt.plot(time_avg, msd_avg, color="#C71585", linewidth=2, label="Average MSD")
|
|
1059
|
+
plt.errorbar(time_avg[indices], msd_avg[indices], yerr=msd_std[indices], fmt='o', color="#C71585", capsize=3)
|
|
1053
1060
|
|
|
1054
1061
|
# Plot fitted line
|
|
1055
|
-
|
|
1062
|
+
|
|
1056
1063
|
plt.plot(time_avg, fitted_line, linestyle="--", color="blue", linewidth=2, label="Linear Fit")
|
|
1057
1064
|
|
|
1058
1065
|
# Labels and legend
|
|
1059
1066
|
plt.xlabel("Time(s)")
|
|
1060
1067
|
plt.ylabel("MSD (µm²)")
|
|
1061
|
-
plt.title("
|
|
1068
|
+
plt.title("Average Mean Square Displacement vs Time")
|
|
1062
1069
|
|
|
1063
1070
|
# Display slope inside the plot
|
|
1064
|
-
plt.text(0.05 * max(time_avg), 0.8 * max(msd_avg), f"Diffusion Coefficient = {slope/4:.
|
|
1071
|
+
plt.text(0.05 * max(time_avg), 0.8 * max(msd_avg), f"Diffusion Coefficient = {slope/4:.1e} ± {slope_std_err/4:.1e} (µm²/s)", fontsize=12, color="blue", bbox=dict(facecolor="white", alpha=0.5))
|
|
1065
1072
|
plt.legend()
|
|
1066
1073
|
plt.minorticks_on()
|
|
1067
1074
|
plt.grid(True, which='major', linestyle='-', linewidth=0.6)
|
|
@@ -1069,4 +1076,4 @@ class hex_kmc:
|
|
|
1069
1076
|
plt.savefig(save_folder, dpi = 600)
|
|
1070
1077
|
plt.show()
|
|
1071
1078
|
|
|
1072
|
-
|
|
1079
|
+
|