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 CHANGED
@@ -1,5 +1,5 @@
1
- from .hex import hex_kmc
1
+ from .hexa import hexa_kmc
2
2
  from .square import square_kmc
3
3
 
4
- __all__ = ["hex_kmc", "square_kmc"]
5
- __version__ = "0.1.0"
4
+ __all__ = ["hexa_kmc", "square_kmc"]
5
+ __version__ = "0.1.0"
@@ -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, n_steps, defect_type = 1, adsorbates_freq = -1, seed=1):
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.n_steps = n_steps # Number of steps
22
- self.defect_type = defect_type
23
- self.adsorbates_freq = adsorbates_freq
24
-
25
- T = 300 # Temperature in Kelvin
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
- self.len_vertical = 0.38e-3 # in micrometer
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 = 0.46
35
- energy_barrier_south = 0.46
36
- energy_barrier_northeast = 0.65
37
- energy_barrier_northwest = 0.65
38
- energy_barrier_southeast = 0.65
39
- energy_barrier_southwest = 0.65
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 = 1.2
44
- energy_barrier_trapping_defect_south = 1.2
45
- energy_barrier_trapping_defect_east = 1.1
46
- energy_barrier_trapping_defect_west = 1.1
47
- energy_barrier_trapping_defect_northeast = 1.1
48
- energy_barrier_trapping_defect_northwest = 1.1
49
- energy_barrier_trapping_defect_southeast = 1.1
50
- energy_barrier_trapping_defect_southwest = 1.1
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 = 1.2
55
- energy_barrier_blocking_defect_south = 1.2
56
- energy_barrier_blocking_defect_northeast = 1.2
57
- energy_barrier_blocking_defect_northwest = 1.2
58
- energy_barrier_blocking_defect_southeast = 1.2
59
- energy_barrier_blocking_defect_southwest = 1.2
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 = 0.72
63
- energy_barrier_adsorbate_south = 0.72
64
- energy_barrier_adsorbate_northeast = 0.72
65
- energy_barrier_adsorbate_northwest = 0.72
66
- energy_barrier_adsorbate_southeast = 0.72
67
- energy_barrier_adsorbate_southwest = 0.72
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 * T))
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) # Return an empty 2D array when no vacancies are needed
607
+ return np.empty((0, 2), dtype=int)
574
608
 
575
609
  positions_base_defects = set()
576
610
  attempts = 0
577
- max_attempts = 20000 # Allow more attempts to fit more vacancies
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 # Avoid infinite loops
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 # Reject and try again
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=(6, 6))
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') # ensure circles look like circles
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]: # horizontal
690
+ if defect1[1] == defect2[1]:
668
691
  width, height = abs(defect1[0] - defect2[0]) + 1, 1
669
- elif defect1[0] == defect2[0]: # vertical
692
+ elif defect1[0] == defect2[0]:
670
693
  width, height = 1, abs(defect1[1] - defect2[1]) + 1
671
- else: # diagonal/block
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
- # Baby blue gradient: bright center -> slightly darker edge
692
- # Lightsteelblue gradient: center almost white → edge lightsteelblue
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 # (H,W,4)
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
- # Create atom visuals: (circle outline + clipped gradient image) per atom
710
- atom_artists = [] # list of (circle_patch, image_artist)
729
+
730
+ atom_artists = []
711
731
  for _ in range(self.n_atoms):
712
- # Circle outline (above the gradient)
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
- # Gradient image (under the outline), then clip to the circle
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) # critical: clip the image to the 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
- # Transparent numbers centered inside atoms
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
- # Move atoms
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
- # (re)set clip path (safe across some backends)
773
+
754
774
  im.set_clip_path(circle)
755
775
  labels[i].set_position((x, y))
756
776
 
757
- # Title
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
- # Move hydroxyls
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) # 10 sec
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=(12, 6), gridspec_kw={'width_ratios': [2, 1]})
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') # keep circles round
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 # <— tweak this for more/less transparent OV fill
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]: # Horizontal pair
827
+ if defect1[1] == defect2[1]:
813
828
  width, height = abs(defect1[0] - defect2[0]) + 1, 1
814
- elif defect1[0] == defect2[0]: # Vertical pair
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
- # ---- Atoms: lightsteelblue radial gradient + gray outline ----
839
- # Gradient colors: center almost white -> edge lightsteelblue
840
- center_color = np.array([240/255, 245/255, 250/255, 1.0]) # near white
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 # (H,W,4)
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 = [] # list of (circle_patch, image_artist)
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
- # Atom labels: centered, semi-transparent
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
- # Atoms
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
- # Title
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
- # Hydroxyls
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
- # MSD trace
948
+
935
949
  msd_line.set_data(self.time[:frame], self.msd[:frame])
936
950
 
937
- # Move percentages up to current frame
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
- # Linear fit for diffusion slope
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) # 10 sec hold
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 10% of steps
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:.4f} µm²/s",
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
- def msd_histogram(self, n_seeds, msd_folder = "random_seeds/msd",
1012
- time_folder = "random_seeds/time",
1013
- save_folder = "random_seeds/average_msd.png",
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
- # slope, intercept = np.polyfit(time_avg, msd_avg, 1)
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=msd_trajs_color, alpha=0.5, linewidth=1)
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=msd_average_color, linewidth=2, label="Average MSD")
1052
- plt.errorbar(time_avg[indices], msd_avg[indices], yerr=msd_std[indices], fmt='o', color=msd_average_color, capsize=3)
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
- #plt.plot(time_avg, fitted_line, linestyle="--", color="blue", linewidth=2, label=f"Linear Fit: Slope = {slope:.4f} ± {slope_std_err:.2f} (um²/s)")
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("T=300K - Lattice Size =10*10 - OH Coverage=0.5ML - OH Frequency=OFF")
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:.6f} ± {slope_std_err/4:.1e} (µm²/s)", fontsize=12, color="blue", bbox=dict(facecolor="white", alpha=0.5))
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
+