kmcpy 0.1.0__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.
kmcpy/__init__.py ADDED
@@ -0,0 +1,64 @@
1
+ """Main kMCpy module."""
2
+ import datetime
3
+ import logging
4
+ import os
5
+
6
+ from ._version import __version__
7
+
8
+ __author__ = "Zeyu Deng"
9
+ __author_email__ = "dengzeyu@gmail.com"
10
+
11
+ # Expose the version and the logo function as the public API for this module.
12
+ __all__ = ["__version__", "get_logo"]
13
+
14
+
15
+ # 2. LOGGING SETUP: This is perfect.
16
+ logging.getLogger(__name__).addHandler(logging.NullHandler())
17
+
18
+
19
+ # 3. LOGO FUNCTION: A stateless function to generate and print the logo.
20
+
21
+ def get_logo():
22
+ """
23
+ Prints the kMCpy ASCII logo and version/citation information to the console.
24
+
25
+ This function can be called multiple times. It's recommended to call
26
+ it once at the start of an application's main entry point.
27
+ """
28
+ # Ansi color codes for a professional look.
29
+ class Colors:
30
+ CYAN = '\033[96m'
31
+ YELLOW = '\033[93m'
32
+ BOLD = '\033[1m'
33
+ END = '\033[0m'
34
+
35
+ # The logo art. Using a raw string (r"...") is good practice.
36
+ logo_art = r"""
37
+ _ __ __ _____
38
+ | | | \/ |/ ____|
39
+ | | _| \ / | | _ __ _ _
40
+ | |/ / |\/| | | | '_ \| | | |
41
+ | <| | | | |____| |_) | |_| |
42
+ |_|\_\_| |_|\_____| .__/ \__, |
43
+ | | __/ |
44
+ |_| |___/
45
+ """
46
+
47
+ # Using os.uname() is not cross-platform (it fails on Windows).
48
+ # platform.node() is a more robust alternative.
49
+ import platform
50
+ hostname = platform.node()
51
+
52
+ # Assemble the final message string inside the function.
53
+ # This ensures all information (like datetime) is current.
54
+ message = (
55
+ f"{Colors.CYAN}{Colors.BOLD}{logo_art}{Colors.END}"
56
+ f"kMCpy: A python package to simulate transport properties in solids with kinetic Monte Carlo\n"
57
+ f"{Colors.YELLOW}Please cite:{Colors.END} DOI: 10.1016/j.commatsci.2023.112394 and 10.1038/s41467-022-32190-7 \n"
58
+ f"{Colors.YELLOW}Author:{Colors.END} {__author__} <{__author_email__}>\n"
59
+ f"Version: {__version__}\n"
60
+ f"Timestamp: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
61
+ f"Host: {hostname}\n"
62
+ )
63
+
64
+ return message
kmcpy/_version.py ADDED
@@ -0,0 +1,3 @@
1
+ __all__ = ["__version__"]
2
+
3
+ __version__ = "0.1dev"
kmcpy/event.py ADDED
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ This module defines the Event class, which encapsulates the information and methods required to represent and analyze a migration event in a lattice-based simulation, such as those used in kinetic Monte Carlo (kMC) studies. The Event class manages indices of mobile ions, local environments, sublattice information, and provides methods for initializing, updating, and serializing event data. It also includes methods for calculating occupation, correlation, migration barriers, and probabilities associated with migration events.
4
+ """
5
+
6
+ import numpy as np
7
+ import numba as nb
8
+ from copy import deepcopy
9
+ import json
10
+ from kmcpy.io import convert
11
+ import logging
12
+
13
+ logger = logging.getLogger(__name__)
14
+ logging.getLogger('numba').setLevel(logging.WARNING)
15
+
16
+ class Event:
17
+ """
18
+ mobile_ion_specie_1_index
19
+ mobile_ion_specie_2_index
20
+ local_env_indices_list
21
+ """
22
+
23
+ def __init__(self):
24
+ pass
25
+
26
+ def initialization(
27
+ self,
28
+ mobile_ion_specie_1_index=12,
29
+ mobile_ion_specie_2_index=15,
30
+ local_env_indices_list=[1, 2, 3, 4, 5],
31
+ ):
32
+ """3rd version of initialization. The input local_env_indices_list is already sorted. Center atom is equivalent to the Na1 in the 1st version and mobile_ion_specie_2_index is equivalent to the Na2 in the 1st version
33
+
34
+ Args:
35
+ mobile_ion_specie_1_index (int, optional): the global index (index in supercell) of the center atom. Defaults to 12.
36
+ mobile_ion_specie_2_index (int, optional): the global index of the atom that the center atom is about to diffuse to. Defaults to 15.
37
+ local_env_indices_list (list, optional): list of integers, which is a list of indices of the neighboring sites in supercell, and is already sorted. Defaults to [1,2,3,4,5].
38
+ """
39
+ self.mobile_ion_specie_1_index = mobile_ion_specie_1_index
40
+ self.mobile_ion_specie_2_index = mobile_ion_specie_2_index
41
+
42
+ self.local_env_indices_list = local_env_indices_list
43
+ self.local_env_indices_list_site = local_env_indices_list
44
+
45
+ def set_sublattice_indices(self, sublattice_indices, sublattice_indices_site):
46
+ self.sublattice_indices = sublattice_indices # this stores the site indices from local_cluster_expansion object
47
+ self.sublattice_indices_site = sublattice_indices_site # this stores the site indices from local_cluster_expansion object
48
+
49
+ def show_info(self):
50
+ logger.info(
51
+ "Event: mobile_ion(1)[%s]<--> mobile_ion(2)[%s]",
52
+ self.mobile_ion_specie_1_index,
53
+ self.mobile_ion_specie_2_index,
54
+ )
55
+ logger.debug('Global sites indices are (excluding O and Zr): %s', self.local_env_indices_list)
56
+ logger.debug('Local template structure: %s', getattr(self, 'sorted_local_structure', None))
57
+
58
+ try:
59
+ logger.info("occ_sublat\tE_KRA\tProbability")
60
+ logger.info("%s\t%s\t%s", self.occ_sublat, self.ekra, self.probability)
61
+ except Exception:
62
+ pass
63
+
64
+ def clear_property(self):
65
+ pass
66
+
67
+ def analyze_local_structure(self, local_env_info):
68
+ #
69
+ indices_sites_group = [(s["site_index"], s["site"]) for s in local_env_info]
70
+
71
+ # this line is to sort the neighbors. First sort by x coordinate, and then sort by specie (Na, then Si/P)
72
+ # the sorted list, store the sequence of hash.
73
+ # for other materials, need to find another method to sort.
74
+ # this sort only works for the NaSICON!
75
+ indices_sites_group_sorted = sorted(
76
+ sorted(indices_sites_group, key=lambda x: x[1].coords[0]),
77
+ key=lambda x: x[1].specie,
78
+ )
79
+
80
+ local_env_indices_list = [s[0] for s in indices_sites_group_sorted]
81
+ return local_env_indices_list
82
+
83
+ # @profile
84
+ def set_occ(self, occ_global):
85
+ self.occ_sublat = deepcopy(
86
+ occ_global[self.local_env_indices_list]
87
+ ) # occ is an 1D numpy array
88
+
89
+ # @profile
90
+ def initialize_corr(self):
91
+ self.corr = np.empty(shape=len(self.sublattice_indices))
92
+ self.corr_site = np.empty(shape=len(self.sublattice_indices_site))
93
+
94
+ # @profile
95
+ def set_corr(self):
96
+ _set_corr(self.corr, self.occ_sublat, self.sublattice_indices)
97
+ _set_corr(self.corr_site, self.occ_sublat, self.sublattice_indices_site)
98
+
99
+ # @profile
100
+ def set_ekra(
101
+ self, keci, empty_cluster, keci_site, empty_cluster_site
102
+ ): # input is the keci and empty_cluster; ekra = corr*keci + empty_cluster
103
+ self.ekra = np.inner(self.corr, keci) + empty_cluster
104
+ self.esite = np.inner(self.corr_site, keci_site) + empty_cluster_site
105
+
106
+ # @profile
107
+ def set_probability(
108
+ self, occ_global, v, T
109
+ ): # calc_probability() will evaluate migration probability for this event, should be updated everytime when change occupation
110
+ k = 8.617333262145 * 10 ** (-2) # unit in meV/K
111
+ direction = (
112
+ occ_global[self.mobile_ion_specie_2_index]
113
+ - occ_global[self.mobile_ion_specie_1_index]
114
+ ) / 2 # 1 if na1 -> na2, -1 if na2 -> na1
115
+ self.barrier = self.ekra + direction * self.esite / 2 # ekra
116
+ self.probability = abs(direction) * v * np.exp(-1 * (self.barrier) / (k * T))
117
+
118
+ # @profile
119
+ def update_event(
120
+ self, occ_global, v, T, keci, empty_cluster, keci_site, empty_cluster_site
121
+ ):
122
+ self.set_occ(occ_global) # change occupation and correlation for this unit
123
+ self.set_corr()
124
+ self.set_ekra(
125
+ keci, empty_cluster, keci_site, empty_cluster_site
126
+ ) # calculate ekra and probability
127
+ self.set_probability(occ_global, v, T)
128
+
129
+ def as_dict(self):
130
+ d = {
131
+ "mobile_ion_specie_1_index": self.mobile_ion_specie_1_index,
132
+ "mobile_ion_specie_2_index": self.mobile_ion_specie_2_index,
133
+ "local_env_indices_list": self.local_env_indices_list,
134
+ "local_env_indices_list": self.local_env_indices_list,
135
+ "local_env_indices_list_site": self.local_env_indices_list_site,
136
+ }
137
+ return d
138
+
139
+ def to_json(self, fname):
140
+ logger.info("Saving: %s", fname)
141
+ with open(fname, "w") as fhandle:
142
+ d = self.as_dict()
143
+ jsonStr = json.dumps(
144
+ d, indent=4, default=convert
145
+ ) # to get rid of errors of int64
146
+ fhandle.write(jsonStr)
147
+
148
+ @classmethod
149
+ def from_json(self, fname):
150
+ logger.info("Loading: %s", fname)
151
+ with open(fname, "rb") as fhandle:
152
+ objDict = json.load(fhandle)
153
+ obj = Event()
154
+ obj.__dict__ = objDict
155
+ return obj
156
+ @classmethod
157
+ def from_dict(self, event_dict): # convert dict into event object
158
+ event = Event()
159
+ event.__dict__ = event_dict
160
+ return event
161
+
162
+
163
+ @nb.njit
164
+ def _set_corr(corr, occ_latt, sublattice_indices):
165
+ i = 0
166
+ for sublat_ind_orbit in sublattice_indices:
167
+ corr[i] = 0
168
+ for sublat_ind_cluster in sublat_ind_orbit:
169
+ corr_cluster = 1
170
+ for occ_site in sublat_ind_cluster:
171
+ corr_cluster *= occ_latt[occ_site]
172
+ corr[i] += corr_cluster
173
+ i += 1