mgtoolbox-kernel 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.
@@ -0,0 +1,452 @@
1
+
2
+ from itertools import product
3
+ from typing import List, Union
4
+
5
+ import numpy as np
6
+ from spglib import niggli_reduce
7
+
8
+ from mgtoolbox_kernel.util.base import (get_lattice_parameters,
9
+ get_lattice_vectors)
10
+
11
+
12
+ class Cell(object):
13
+ """
14
+ 表示晶体晶格的类。
15
+
16
+ 该类用于存储和操作晶体晶格的参数和基矢。
17
+ """
18
+
19
+ def __init__(self, a: float, b: float, c: float, alpha: float, beta: float, gamma: float, cell_basis_vectors=None):
20
+ """
21
+ 初始化晶体晶格。
22
+
23
+ :param a: 晶格参数 a
24
+ :param b: 晶格参数 b
25
+ :param c: 晶格参数 c
26
+ :param alpha: 晶格角度 alpha
27
+ :param beta: 晶格角度 beta
28
+ :param gamma: 晶格角度 gamma
29
+ :param cell_basis_vectors: 晶格基矢,默认为 None
30
+ """
31
+
32
+ self._cell_param: np.ndarray = np.zeros((2, 3))
33
+ self._cell_param[0] = [a, b, c]
34
+ self._cell_param[1] = [alpha, beta, gamma]
35
+ # self._cell_basis_vectors = None
36
+ if cell_basis_vectors is None:
37
+ self.__set_cell_basis_vectors(a, b, c, alpha, beta, gamma)
38
+ else:
39
+ self._cell_basis_vectors = cell_basis_vectors
40
+
41
+ def __eq__(self, other: 'Cell') -> bool:
42
+ return self is other or np.allclose(self._cell_basis_vectors, other._cell_basis_vectors)
43
+
44
+ def __repr__(self) -> str:
45
+ cstring = {
46
+ 'lattice': self._cell_basis_vectors,
47
+ 'parameters': self._cell_param.reshape(6, )
48
+ }
49
+ return str(cstring)
50
+
51
+ @property
52
+ def abc(self) -> np.ndarray:
53
+ """Get the cell parameters:a,b,c
54
+
55
+ Returns
56
+ -------
57
+ np.ndarray
58
+ cell parameters:a,b,c
59
+ """
60
+ return self._cell_param[0]
61
+
62
+ @abc.setter
63
+ def abc(self, value):
64
+ self._cell_param[0] = value
65
+
66
+ @property
67
+ def angles(self) -> np.ndarray:
68
+ """Get the cell parameters:alpha,beta,gamma
69
+
70
+ Returns
71
+ -------
72
+ numpy.ndarray
73
+ cell parameters:alpha,beta,gamma
74
+ """
75
+ return self._cell_param[1]
76
+
77
+ @angles.setter
78
+ def angles(self, value: Union[np.ndarray, List]):
79
+ self._cell_param[1] = value
80
+
81
+ @property
82
+ def lattice_parameters(self):
83
+ return np.hstack((self.abc, self.angles))
84
+
85
+ @property
86
+ def cell_basis_vectors(self):
87
+ return self._cell_basis_vectors
88
+
89
+ @property
90
+ def volume(self):
91
+ return np.fabs(np.linalg.det(np.transpose(self.cell_basis_vectors)))
92
+
93
+ @property
94
+ def reciprocal_cell_vectors(self):
95
+ '''获取倒易晶格向量,不包括2pi的系数
96
+
97
+ Returns
98
+ -------
99
+ 倒易晶格向量
100
+ '''
101
+ return np.transpose(np.linalg.pinv(self.cell_basis_vectors))
102
+
103
+ def __set_cell_basis_vectors(self, a, b, c, alpha, beta, gamma):
104
+ self._cell_basis_vectors = get_lattice_vectors(a, b, c, alpha, beta, gamma)
105
+
106
+ @staticmethod
107
+ def from_lattice_parameters(a, b, c, alpha, beta, gamma):
108
+ return Cell(a, b, c, alpha, beta, gamma)
109
+
110
+ @staticmethod
111
+ def from_cell_vectors(cell_vectors: np.ndarray, fix_cell_vectors: bool = False):
112
+ (a, b, c, alpha, beta, gamma) = get_lattice_parameters(cell_vectors)
113
+ if not fix_cell_vectors:
114
+ return Cell(a, b, c, alpha, beta, gamma)
115
+ else:
116
+ return Cell(a, b, c, alpha, beta, gamma, cell_vectors)
117
+
118
+ def get_distances(
119
+ self,
120
+ cart_coords1,
121
+ cart_coords2=None,
122
+ mic=True
123
+ ):
124
+ """Get the minimum distance of to list of cartesian coordinates
125
+
126
+ Parameters
127
+ ----------
128
+ list | numpy.ndarray
129
+ cartesian coordinates
130
+ list | numpy.ndarray, optional
131
+ cartesian coordinates, by default None
132
+
133
+ Returns
134
+ -------
135
+ numpy.ndarray, numpy.ndarray
136
+ minimum image distance vectors and its distances
137
+ """
138
+ pbc = [True, True, True]
139
+ cart_coords1 = np.array(cart_coords1)
140
+ if cart_coords2 is None:
141
+ n1 = len(cart_coords1)
142
+ # upper triangular index
143
+ i1, i2 = np.triu_indices(n1, k=1)
144
+ distances_vecters = cart_coords1[i2] - cart_coords1[i1]
145
+ else:
146
+ cart_coords2 = np.array(cart_coords2)
147
+ distances_vecters = (cart_coords2[np.newaxis, :, :] - cart_coords1[:, np.newaxis, :]).reshape((-1, 3))
148
+
149
+ if np.sum(pbc) == 0 or mic == False:
150
+ minimum_vecters = np.asarray(distances_vecters)
151
+ vector_lengths = np.linalg.norm(distances_vecters, axis=1)
152
+ else:
153
+ minimum_vecters, vector_lengths = self.__find_mic_distances(distances_vecters)
154
+
155
+ if cart_coords2 is None:
156
+ Dout = np.zeros((n1, n1, 3))
157
+ Dout[(i1, i2)] = minimum_vecters
158
+ Dout -= np.transpose(Dout, axes=(1, 0, 2))
159
+
160
+ Dout_len = np.zeros((n1, n1))
161
+ Dout_len[(i1, i2)] = vector_lengths
162
+ Dout_len += Dout_len.T
163
+ return Dout, Dout_len
164
+
165
+ minimum_vecters.shape = (-1, len(cart_coords2), 3)
166
+ vector_lengths.shape = (-1, len(cart_coords2))
167
+
168
+ return minimum_vecters, vector_lengths
169
+
170
+ def __find_mic_distances(self, vecters):
171
+ """Get the minimum image distance of each atoms
172
+
173
+ Parameters
174
+ ----------
175
+ numpy.ndarray
176
+ distance vectors
177
+
178
+ Returns
179
+ -------
180
+ numpy.ndarray, numpy.ndarray
181
+ minimum image distance vectors and its distances
182
+ """
183
+ pbc = [True, True, True]
184
+ n = np.sum(pbc)
185
+ minimum_vecters = []
186
+ vector_lengths = []
187
+ for v in vecters:
188
+ mic_flag = False
189
+ if n == 3:
190
+ minimum_vecter, vector_length = self.__direct_find_mic(v)
191
+ if (vector_length < 0.5 * min(self.abc)):
192
+ mic_flag = True
193
+ minimum_vecters.append(minimum_vecter)
194
+ vector_lengths.append(vector_length)
195
+ continue
196
+ else:
197
+ mic_flag = False
198
+ minimum_vecters = []
199
+ vector_lengths = []
200
+ break
201
+ minimum_vecters = np.array(minimum_vecters)
202
+ vector_lengths = np.array(vector_lengths)
203
+ if not mic_flag:
204
+ minimum_vecters, vector_lengths = self.__reduce_find_mic(vecters)
205
+
206
+ return minimum_vecters, vector_lengths
207
+
208
+ def __direct_find_mic(self, vecter):
209
+ """Calculate the minimum image distances,and use the result when the minimum image convention is satisfied.The minimum image convention i.e distance < min(a,b,c)/2
210
+
211
+ Parameters
212
+ ----------
213
+ numpy.ndarray
214
+ distance vector
215
+
216
+ Returns
217
+ -------
218
+ numpy.ndarray, numpy.ndarray
219
+ minimum image distance vector and its distance
220
+ """
221
+ # convert to fractional coordinates
222
+ frac_coord = self.get_fractional_coordinates(vecter)
223
+ # Control the fractional coordinate range to (-0.5,0.5)
224
+ frac_coord -= np.floor(frac_coord + 0.5)
225
+ minimum_vecter = self.get_cartesian_coords(frac_coord)
226
+ vector_length = np.linalg.norm(minimum_vecter)
227
+ # Returns the shortest vector and its length
228
+ return minimum_vecter, vector_length
229
+
230
+ def __reduce_find_mic(self, vecters):
231
+
232
+ """If the minimum image convention is not satisfied, reduce the cell to re-calculate the minimum image distances
233
+
234
+ Parameters
235
+ ----------
236
+ vecters : numpy.ndarray
237
+ distance vectors
238
+
239
+ Returns
240
+ -------
241
+ numpy.ndarray, numpy.ndarray
242
+ minimum image distance vectors and its distances
243
+ """
244
+ pbc = [True, True, True]
245
+ cart_coords = self.__wrap_atoms(vecters)
246
+ # set the periodic range of three vector directions according to the periodic boundary conditions. (-1,1)
247
+ periodic_range = [np.arange(-1 * p, p + 1) for p in pbc]
248
+ hkl_range = list(product(*periodic_range))
249
+ vecter_range = np.dot(hkl_range, self.cell_basis_vectors)
250
+ # get all atoms' coordinates in the cell and its mirror cells
251
+ expanded_cart_coords = cart_coords + vecter_range[:, None]
252
+ # calculate the minimum image distance and get its index
253
+ lengths = np.linalg.norm(expanded_cart_coords, axis=2)
254
+ indices = np.argmin(lengths, axis=0)
255
+ minimum_vecters = expanded_cart_coords[indices, np.arange(len(cart_coords)), :]
256
+ vector_lengths = lengths[indices, np.arange(len(cart_coords))]
257
+ return minimum_vecters, vector_lengths
258
+
259
+ def __wrap_atoms(self, cart_coord):
260
+ """If there is periodicity in this direction, wrap the specified component of the coordinate into the cell
261
+ Parameters
262
+ ----------
263
+ numpy.ndarray
264
+ cartesian coordinate
265
+
266
+ Returns
267
+ -------
268
+ numpy.ndarray
269
+ cartesian coordinate
270
+ """
271
+ pbc = [True, True, True]
272
+ frac_coord = self.get_fractional_coordinates(cart_coord)
273
+ for i, periodic in enumerate(pbc):
274
+ if periodic:
275
+ frac_coord[:, i] %= 1.0
276
+ return self.get_cartesian_coords(frac_coord)
277
+
278
+ def distance(self, coord1, coord2=None, mic: bool = True):
279
+ """Get the minimum distance of to list of cartesian coordinates
280
+
281
+ Parameters
282
+ ----------
283
+ coord1 : list | numpy.ndarray
284
+ 笛卡尔坐标
285
+ coord2 : list | numpy.ndarray
286
+ 笛卡尔坐标,by default None,即coord1为距离向量
287
+ mic : bool
288
+ 最小像距离,by default True
289
+
290
+ Returns
291
+ -------
292
+ numpy.ndarray, numpy.ndarray
293
+ 最小像距离向量,向量长度
294
+ """
295
+ if coord2 is None:
296
+ distance_vector = np.array(coord1)
297
+ else:
298
+ cart_coord1 = np.array(coord1)
299
+ cart_coord2 = np.array(coord2)
300
+ distance_vector = cart_coord2 - cart_coord1
301
+ if not mic:
302
+ minimum_vector = distance_vector
303
+ vector_length = np.linalg.norm(minimum_vector)
304
+ else:
305
+ minimum_vector, vector_length = self.find_mic_distance(distance_vector)
306
+ return minimum_vector, vector_length
307
+
308
+ def find_mic_distance(self, vector: np.ndarray):
309
+ """Get the minimum image distance of specific two sites
310
+
311
+ Parameters
312
+ ----------
313
+ vector : numpy.ndarray
314
+ 距离向量
315
+
316
+ Returns
317
+ -------
318
+ numpy.ndarray, numpy.ndarray
319
+ minimum image distance vector and its distance
320
+ """
321
+ minimum_vector, vector_length = self.direct_find_mic(vector)
322
+ if vector_length < 0.5 * min(self.abc):
323
+ mic_flag = True
324
+ else:
325
+ mic_flag = False
326
+ minimum_vector = np.array(minimum_vector)
327
+ vector_length = np.array(vector_length)
328
+ if not mic_flag:
329
+ minimum_vector, vector_length = self.reduce_find_mic(vector)
330
+ return minimum_vector, vector_length
331
+
332
+ def direct_find_mic(self, vector: np.ndarray):
333
+ """Calculate the minimum image distances,and use the result when the minimum image convention is satisfied.The minimum image convention i.e distance < min(a,b,c)/2
334
+
335
+ Parameters
336
+ ----------
337
+ vector : numpy.ndarray
338
+ 距离向量
339
+
340
+ Returns
341
+ -------
342
+ numpy.ndarray, numpy.ndarray
343
+ minimum image distance vector and its distance
344
+ """
345
+
346
+ # convert to fractional coordinates
347
+ frac_coord = self.get_fractional_coordinates(vector)
348
+ # Control the fractional coordinate range to (-0.5,0.5)
349
+ frac_coord -= np.floor(frac_coord + 0.5)
350
+ minimum_vector = self.get_cartesian_coords(frac_coord)
351
+ vector_length = np.linalg.norm(minimum_vector)
352
+ # Returns the shortest vector and its length
353
+ return minimum_vector, vector_length
354
+
355
+ def reduce_find_mic(self, vector: np.ndarray):
356
+ """If the minimum image convention is not satisfied, reduce the cell to re-calculate the minimum image distance
357
+
358
+ Parameters
359
+ ----------
360
+ vector : numpy.ndarray
361
+ 距离向量
362
+
363
+ Returns
364
+ -------
365
+ numpy.ndarray, numpy.ndarray
366
+ minimum image distance vectors and its distances
367
+ """
368
+ pbc = [True, True, True]
369
+ cart_coord = self.wrap_atoms(vector)
370
+ # set the periodic range of three vector directions according to the periodic boundary conditions. (-1,1)
371
+ periodic_range = [np.arange(-1 * p, p + 1) for p in pbc]
372
+ hkl_range = list(product(*periodic_range))
373
+ vector_range = np.dot(hkl_range, self.cell_basis_vectors)
374
+ # get all atoms' coordinates in the cell and its mirror cells
375
+ expanded_cart_coords = []
376
+ for v in vector_range:
377
+ expanded_cart_coords.append(cart_coord + v)
378
+ # calculate the minimum image distance and get its index
379
+ lengths = np.linalg.norm(expanded_cart_coords, axis=1)
380
+ index = np.argmin(lengths, axis=0)
381
+ minimum_vector = expanded_cart_coords[index]
382
+ vector_length = lengths[index]
383
+ return np.array(minimum_vector), np.array(vector_length)
384
+
385
+ def wrap_atoms(self, cart_coord):
386
+ """If there is periodicity in this direction, wrap the specified component of the coordinate into the cell
387
+
388
+ Parameters
389
+ ----------
390
+ cart_coord : numpy.ndarray
391
+ cartesian coordinate
392
+
393
+ Returns
394
+ -------
395
+ numpy.ndarray
396
+ cartesian coordinate
397
+ """
398
+ pbc = [True, True, True]
399
+ frac_coord = self.get_fractional_coordinates(cart_coord)
400
+ for i, periodic in enumerate(pbc):
401
+ if periodic:
402
+ frac_coord[i] %= 1.0
403
+ return self.get_cartesian_coords(frac_coord)
404
+
405
+ def get_reduced_cell(self, algorithm: str = 'niggli'):
406
+ """Select the reduce algorithm to reduce the cell,i.e,1.niggli 2.minkowski
407
+
408
+ Parameters
409
+ ----------
410
+ algorithm : str, optional
411
+ choose, by default 'niggli'
412
+
413
+ Returns
414
+ -------
415
+ Cell
416
+ the Cell object after reduced
417
+ """
418
+ if algorithm == 'niggli':
419
+ new_cell_vectors = niggli_reduce(self._cell_basis_vectors)
420
+ else:
421
+ new_cell_vectors = self._cell_basis_vectors
422
+ return self.from_cell_vectors(new_cell_vectors)
423
+
424
+ def get_cartesian_coords(self, frac_coords: np.ndarray) -> np.ndarray:
425
+ """get_cartesian_coords 从分数坐标得到笛卡尔坐标
426
+
427
+ Parameters
428
+ ----------
429
+ frac_coords : np.ndarray
430
+ 分数坐标
431
+
432
+ Returns
433
+ -------
434
+ np.ndarray
435
+ 笛卡尔坐标
436
+ """
437
+ return np.dot(frac_coords, self.cell_basis_vectors)
438
+
439
+ def get_fractional_coordinates(self, cart_coords: np.ndarray) -> np.ndarray:
440
+ """get_Fractional_coordinates 从笛卡尔坐标得到分数坐标
441
+
442
+ Parameters
443
+ ----------
444
+ cart_coords : np.ndarray
445
+ 笛卡尔坐标
446
+
447
+ Returns
448
+ -------
449
+ np.ndarray
450
+ 分数坐标
451
+ """
452
+ return np.dot(cart_coords, np.linalg.inv(self.cell_basis_vectors))
@@ -0,0 +1,8 @@
1
+ from typing import Dict
2
+
3
+
4
+ class MGobject:
5
+ def __init__(self, attributes=None):
6
+ if attributes is None:
7
+ attributes = {}
8
+ self.attributes = attributes
@@ -0,0 +1,81 @@
1
+ import numpy as np
2
+ from scipy.interpolate import RegularGridInterpolator
3
+
4
+
5
+ class PotentialField(object):
6
+
7
+ def __init__(
8
+ self,
9
+ Potential: np.ndarray,
10
+ basis_vector: np.ndarray = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
11
+ ) -> None:
12
+ self.potential = Potential
13
+ self.compute_gradients()
14
+ self.create_interpolat_function()
15
+ self.basis_vector: np.ndarray = np.array([[1, 0, 0], [0, 1, 0],
16
+ [0, 0, 1]])
17
+
18
+ def create_interpolat_function(self):
19
+ data_shape = self.potential.shape
20
+ x = np.linspace(0, 1, data_shape[0])
21
+ y = np.linspace(0, 1, data_shape[1])
22
+ z = np.linspace(0, 1, data_shape[2])
23
+ self.potential_interp_function = RegularGridInterpolator(
24
+ (x, y, z), self.potential)
25
+ self.potential_gradients_interp_function_x = RegularGridInterpolator(
26
+ (x, y, z), self.potential_gradients[0][:][:][:])
27
+ self.potential_gradients_interp_function_y = RegularGridInterpolator(
28
+ (x, y, z), self.potential_gradients[1][:][:][:])
29
+ self.potential_gradients_interp_function_z = RegularGridInterpolator(
30
+ (x, y, z), self.potential_gradients[2][:][:][:])
31
+
32
+ def compute_gradients(self):
33
+ self.potential_gradients = np.gradient(self.potential)
34
+
35
+ def get_gradients(self, coords: np.ndarray):
36
+ return np.array([
37
+ self.potential_gradients_interp_function_x(coords),
38
+ self.potential_gradients_interp_function_y(coords),
39
+ self.potential_gradients_interp_function_z(coords)
40
+ ])
41
+
42
+ def get_potentials(self, coords: np.ndarray):
43
+ return self.potential_interp_function(coords)
44
+
45
+
46
+ class PeriodPotentialField(PotentialField):
47
+
48
+ def __init__(
49
+ self,
50
+ Potential: np.ndarray,
51
+ basis_vector: np.ndarray = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
52
+ ) -> None:
53
+ super().__init__(Potential, basis_vector)
54
+
55
+ def compute_gradients(self):
56
+ super().compute_gradients()
57
+ # period boundary condtion
58
+ self.potential_gradients[0][ 0][ :][ :] \
59
+ = (-self.potential[-2][ :][ :] + self.potential[1][ :][ :]) * 0.5
60
+ self.potential_gradients[0][ -1][ :][ :] \
61
+ = (-self.potential[-2][ :][ :] + self.potential[1][ :][ :]) * 0.5
62
+ self.potential_gradients[1][ :][ 0][ :] \
63
+ = (-self.potential[:][ -2][ :] + self.potential[:][ 1][ :]) * 0.5
64
+ self.potential_gradients[1][ :][ -1][ :] \
65
+ = (-self.potential[:][ -2][ :] + self.potential[:][ 1][ :]) * 0.5
66
+ self.potential_gradients[2][ :][ :][ 0] \
67
+ = (-self.potential[:][ :][ -2] + self.potential[:][ :][ 1]) * 0.5
68
+ self.potential_gradients[2][ :][ :][ -1] \
69
+ = (-self.potential[:][ :][ -2] + self.potential[:][ :][ 1]) * 0.5
70
+
71
+ def get_gradients(self, coords: np.ndarray):
72
+ fcoords = np.mod(coords, 1.0)
73
+ result = np.array([
74
+ self.potential_gradients_interp_function_x(fcoords)[0],
75
+ self.potential_gradients_interp_function_y(fcoords)[0],
76
+ self.potential_gradients_interp_function_z(fcoords)[0]
77
+ ])
78
+ return result
79
+
80
+ def get_potentials(self, coords: np.ndarray):
81
+ return self.potential_interp_function(np.mod(coords, 1.0))