BUCToolkit 1.0b0__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.
Files changed (88) hide show
  1. BUCToolkit/BatchGenerate/CheckStructures.py +198 -0
  2. BUCToolkit/BatchGenerate/__init__.py +6 -0
  3. BUCToolkit/BatchGenerate/coords_interp.py +127 -0
  4. BUCToolkit/BatchGenerate/initial_ads_structures.py +351 -0
  5. BUCToolkit/BatchGenerate/random_replace.py +88 -0
  6. BUCToolkit/BatchMC/MetropolisMC.py +210 -0
  7. BUCToolkit/BatchMC/_BaseMC.py +863 -0
  8. BUCToolkit/BatchMC/__init__.py +8 -0
  9. BUCToolkit/BatchMD/BiasedMD.py +276 -0
  10. BUCToolkit/BatchMD/NVE.py +92 -0
  11. BUCToolkit/BatchMD/NVT.py +510 -0
  12. BUCToolkit/BatchMD/_BaseMD.py +1251 -0
  13. BUCToolkit/BatchMD/__init__.py +18 -0
  14. BUCToolkit/BatchMD/constrained_md/BiasedMD.py +273 -0
  15. BUCToolkit/BatchMD/constrained_md/ConstrNVE.py +169 -0
  16. BUCToolkit/BatchMD/constrained_md/ConstrNVT.py +462 -0
  17. BUCToolkit/BatchMD/constrained_md/NVE.py +78 -0
  18. BUCToolkit/BatchMD/constrained_md/NVT.py +346 -0
  19. BUCToolkit/BatchMD/constrained_md/_BaseMD.py +440 -0
  20. BUCToolkit/BatchMD/constrained_md/_ConstrBaseMD.py +607 -0
  21. BUCToolkit/BatchMD/constrained_md/__init__.py +18 -0
  22. BUCToolkit/BatchOptim/TS/CI_NEB.py +286 -0
  23. BUCToolkit/BatchOptim/TS/Dimer.py +950 -0
  24. BUCToolkit/BatchOptim/TS/__init__.py +10 -0
  25. BUCToolkit/BatchOptim/_BaseOpt.py +702 -0
  26. BUCToolkit/BatchOptim/__init__.py +22 -0
  27. BUCToolkit/BatchOptim/_utils/__init__.py +6 -0
  28. BUCToolkit/BatchOptim/_utils/_line_search.py +972 -0
  29. BUCToolkit/BatchOptim/_utils/_warnings.py +11 -0
  30. BUCToolkit/BatchOptim/frequency.py +496 -0
  31. BUCToolkit/BatchOptim/minimize/CG.py +256 -0
  32. BUCToolkit/BatchOptim/minimize/FIRE.py +310 -0
  33. BUCToolkit/BatchOptim/minimize/QN.py +205 -0
  34. BUCToolkit/BatchOptim/minimize/__init__.py +17 -0
  35. BUCToolkit/BatchStructures/BatchStructuresBase.py +2261 -0
  36. BUCToolkit/BatchStructures/StructuresIO.py +1394 -0
  37. BUCToolkit/BatchStructures/__init__.py +15 -0
  38. BUCToolkit/Preprocessing/__init__.py +7 -0
  39. BUCToolkit/Preprocessing/load_files.py +1680 -0
  40. BUCToolkit/Preprocessing/preprocessing.py +923 -0
  41. BUCToolkit/Preprocessing/sampling.py +299 -0
  42. BUCToolkit/Preprocessing/supercells.py +140 -0
  43. BUCToolkit/Preprocessing/write_files.py +524 -0
  44. BUCToolkit/StandardTemplate.py +417 -0
  45. BUCToolkit/__init__.py +33 -0
  46. BUCToolkit/_version.py +7 -0
  47. BUCToolkit/api/ConstrainedMolecularDynamics.py +347 -0
  48. BUCToolkit/api/DataLoaders.py +321 -0
  49. BUCToolkit/api/Losses.py +170 -0
  50. BUCToolkit/api/Metrics.py +84 -0
  51. BUCToolkit/api/ModelOptims.py +296 -0
  52. BUCToolkit/api/MolecularDynamics.py +282 -0
  53. BUCToolkit/api/MonteCarlo.py +280 -0
  54. BUCToolkit/api/NEB.py +325 -0
  55. BUCToolkit/api/Predictor.py +292 -0
  56. BUCToolkit/api/StructureOptimization.py +484 -0
  57. BUCToolkit/api/Trainer.py +635 -0
  58. BUCToolkit/api/VibrationAnalysis.py +279 -0
  59. BUCToolkit/api/__init__.py +38 -0
  60. BUCToolkit/api/_io.py +1026 -0
  61. BUCToolkit/cli/__init__.py +20 -0
  62. BUCToolkit/cli/cli.py +934 -0
  63. BUCToolkit/cli/input_stub.py +140 -0
  64. BUCToolkit/cli/main.py +368 -0
  65. BUCToolkit/cli/print_logo.py +149 -0
  66. BUCToolkit/cli/template_model.py +6 -0
  67. BUCToolkit/io.py +30 -0
  68. BUCToolkit/utils/AtomSelector.py +64 -0
  69. BUCToolkit/utils/AtomicNumber2Properties.py +39 -0
  70. BUCToolkit/utils/ElemListReduce.py +52 -0
  71. BUCToolkit/utils/Ewald.py +210 -0
  72. BUCToolkit/utils/IrregularTensorReformat.py +59 -0
  73. BUCToolkit/utils/_CheckModules.py +30 -0
  74. BUCToolkit/utils/_Element_info.py +527 -0
  75. BUCToolkit/utils/__init__.py +22 -0
  76. BUCToolkit/utils/_para_flatt_list.py +131 -0
  77. BUCToolkit/utils/_print_formatter.py +59 -0
  78. BUCToolkit/utils/check_structures.py +204 -0
  79. BUCToolkit/utils/function_utils.py +147 -0
  80. BUCToolkit/utils/grad_functions.py +69 -0
  81. BUCToolkit/utils/index_ops.py +432 -0
  82. BUCToolkit/utils/setup_loggers.py +137 -0
  83. buctoolkit-1.0b0.dist-info/METADATA +28 -0
  84. buctoolkit-1.0b0.dist-info/RECORD +88 -0
  85. buctoolkit-1.0b0.dist-info/WHEEL +5 -0
  86. buctoolkit-1.0b0.dist-info/entry_points.txt +6 -0
  87. buctoolkit-1.0b0.dist-info/licenses/LICENSE +21 -0
  88. buctoolkit-1.0b0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,299 @@
1
+ """
2
+ A module of sampling algorithms.
3
+ """
4
+ # Copyright (c) 2025.7.4, BUCToolkit.
5
+ # Authors: Pu Pengxin, Song Xin
6
+ # Version: 0.9a
7
+ # File: sampling.py
8
+ # Environment: Python 3.12
9
+
10
+ import numpy as np
11
+ import time
12
+ import copy
13
+
14
+
15
+ class SimpRandSamp(object):
16
+ """
17
+ Simple random sampling.
18
+
19
+ Method:
20
+ run: run sampling.
21
+
22
+ Attributes:
23
+ seed: int, the random seed.
24
+ sampling_args: 1darray, the list of sampling points' indexes in X_all.
25
+ sampling_points: 2darray, the sampling points.
26
+ """
27
+
28
+ def __init__(self, size, seed=None):
29
+ """
30
+ Parameters:
31
+ size: int, the number of samples to take.
32
+ seed: int, the random seed.
33
+ """
34
+ self.size = size
35
+ self.seed = seed
36
+
37
+ def run(self, X_all, ):
38
+ """
39
+ Run sampling.
40
+
41
+ parameters:
42
+ X_all: ndarray, the population i.e., the set of all possible points, each point responds to a row of X_all.
43
+ """
44
+ n_samp = len(X_all)
45
+ np.random.seed(self.seed)
46
+ self.sampling_args = np.random.choice(n_samp, self.size, replace=False)
47
+ self.sampling_points = X_all[self.sampling_args]
48
+
49
+
50
+ class FPS(object):
51
+ """
52
+ The Furtherest Points Sampling.
53
+
54
+ Method:
55
+ distace: calculating distance matrix
56
+ run: run sampling.
57
+
58
+ Attributes:
59
+ distance_matrix: 2darray, the distacne matrix of X_all.
60
+ sampling_args: 1darray, the list of sampling points' indexes in X_all.
61
+ sampling_points: 2darray, the sampling points.
62
+ """
63
+
64
+ def __init__(self, size, norm=2):
65
+ """
66
+ Parameters:
67
+ size: int, the number of samples to take.
68
+ norm: positive int|np.inf, the type of calculating distance.
69
+ """
70
+ self.sampling_points = None
71
+ self.size = size
72
+ self.norm = norm
73
+
74
+ def distance(self, X_all):
75
+ """
76
+ Define the distance matrix of X_all.
77
+ Returns: Distance Matrix
78
+ """
79
+ n_samp, n_feat = np.shape(X_all)
80
+ self.distance_matrix = np.linalg.norm(X_all[:, None] - X_all[None, :], ord=self.norm, axis=-1)
81
+ return self.distance_matrix
82
+
83
+ def run(self, X_all: np.ndarray, X_init=None, init_type='points', verbose=2) -> np.ndarray:
84
+ """
85
+ Run sampling.
86
+
87
+ Args:
88
+ X_all: ndarray[N, D], the population i.e., the set of all feasible points, each point responds to a row of X_all.
89
+ X_init: None|ndarray|1darray, the set of initial points (for init_type='points'), or the index set of initial points in X_all (for init_type='index'). If X_init is None, X_init is set to the point in X_all closest to center-of-X_all.
90
+ init_type: 'points' or 'index', assign the type of input X_init.
91
+ verbose: int, control print content. 0 for silence.
92
+
93
+ Returns:
94
+ the sampling points array
95
+ """
96
+ # Define initial varibles
97
+ if X_all.ndim == 1:
98
+ X_all = X_all[:, None]
99
+ elif X_all.ndim != 2:
100
+ raise ValueError(f'Expected X_all is 1 or 2 dimension, but got {X_all.ndim}-D array.')
101
+ n_samp, n_feat = np.shape(X_all)
102
+ all_args = set(range(n_samp))
103
+ self.sampling_points = np.zeros((self.size, n_feat))
104
+ self.sampling_args = np.zeros(self.size, dtype=np.int64)
105
+ L = np.inf
106
+ init_args = 0
107
+
108
+ # If X_init is None, X_init is set to the point in X_all closest to center-of-X_all
109
+ if X_init is None:
110
+ if verbose > 0:
111
+ print('X_init is not read, generating the X_init...\n')
112
+ Xcenter = np.mean(X_all, axis=0)
113
+ for i, feat in enumerate(X_all):
114
+ L_temp = np.linalg.norm(feat - Xcenter, ord=self.norm)
115
+ if L_temp < L:
116
+ L = L_temp
117
+ X_init = copy.deepcopy(feat)
118
+ init_args = i
119
+
120
+ elif init_type == 'index': # X_init is the indices in X_all
121
+ # check input
122
+ X_init = np.asarray(X_init, dtype=np.int64)
123
+ if np.any((X_init < 0) + (X_init > n_samp)):
124
+ raise ValueError('X_init is out of range')
125
+
126
+ init_args = set(X_init.tolist())
127
+ if verbose > 0:
128
+ print('read the initial index in X_all successfully.\n')
129
+
130
+ elif init_type == 'points': # X_init is directly the coords
131
+ # check input
132
+ if len(np.shape(X_init)) == 1:
133
+ X_init = np.array([X_init])
134
+ elif len(np.shape(X_init)) != 2:
135
+ raise ValueError('X_init must be 1D or 2D array')
136
+ if len(X_init[0, :]) != n_feat:
137
+ raise ValueError('Columns number of X_init and X_all does not match')
138
+ #
139
+ init_args = set()
140
+ __init_indx = 0
141
+ # print(X_init,'\n',init_args)
142
+ # find the element args of X_init in X_all, by finding the element in X_init that have minimal distance of element in X_all
143
+ for j, featInit in enumerate(X_init):
144
+ L_temp = np.linalg.norm(X_all - featInit, ord=self.norm, axis=-1)
145
+ __init_indx = np.where(L_temp < 1e-5)[0][0]
146
+ init_args.add(__init_indx)
147
+ if verbose > 0:
148
+ print('read the initial index in X_all successfully.\n')
149
+
150
+ else:
151
+ raise ValueError('Invalid value of init_type. Please set init_type to \'index\' or \'points\'.')
152
+
153
+ if len(init_args) + self.size > n_samp:
154
+ raise ValueError('Sum of initial and added points is greater than X_all')
155
+
156
+ if verbose > 0: print('Variables initialized, starting main loop...\n')
157
+
158
+ # main loop
159
+ # Calculate the distance matrix
160
+ if verbose > 0: print('Calculating the distance matrix...\n')
161
+ distMat = self.distance(X_all)
162
+
163
+ # Find the furthest points
164
+ if verbose > 0: print('Search the furthest points...')
165
+ dist1 = np.inf
166
+ dist2 = -1.
167
+ samp_args = 0
168
+ for k in range(self.size):
169
+ rest_args = all_args - init_args
170
+ for i in rest_args:
171
+ for j in init_args:
172
+ i, j = int(i), int(j)
173
+ # Find the min distance for j
174
+ if distMat[i, j] <= dist1:
175
+ dist1 = distMat[i, j]
176
+ # Find the max distance for i
177
+ if dist1 >= dist2:
178
+ dist2 = dist1
179
+ samp_args = i
180
+ # initialize
181
+ dist1 = np.inf
182
+ self.sampling_args[k] = int(samp_args)
183
+ # initialize
184
+ dist2 = -1.
185
+ init_args.add(int(samp_args))
186
+
187
+ if verbose > 0: print('*****Completed*****')
188
+ self.sampling_points = X_all[self.sampling_args]
189
+ return self.sampling_points
190
+
191
+
192
+ class Uncertainty_Samp(object):
193
+ """
194
+ Run sampling by uncertainty of models. The uncertainty is calculated by
195
+
196
+ Method:
197
+ run: run sampling.
198
+
199
+ Attribute:
200
+ train_mean_MSE: float, the mean value of training-MSE of all split-train-test loops
201
+ valid_mean_MSE: float, the mean value of validation-MSE of all split-train-test loops
202
+ predict_mean: 1darray, the mean values of predictions of X_all.
203
+ uncertain_list = 1darray, the list of each samples' uncertainties given by predicted values' standard deviation of all split-train-test loops.
204
+ uncertain_sort_args: 1darray, the list of indexes that sorted predictions from min to max of samples in X_all.
205
+ sampling_args: 1darray, the list of sampling points' indexes in X_all.
206
+ """
207
+
208
+ def __init__(self, size, model, n_iter=100, seed=None):
209
+ """
210
+ Parameters:
211
+ size: integer, number of features to sampling,
212
+ model: The model (particularly in sklearn) with method `fit` and `predict` that to fit and predict data.
213
+ n_iter: integer, number of iterations.
214
+ seed: integer, rand seed.
215
+ """
216
+
217
+ self.size = size
218
+ self.model = model
219
+ self.n_iter = n_iter
220
+ self.seed = seed
221
+
222
+ def run(self, X, y, X_all, split_size=0.1, n_group=1, verbose=2):
223
+ """
224
+ X: np.ndarray, input features.
225
+ y: np.ndarray, labels of dataset with the same order of y.
226
+ X_all: ����Ԥ���������
227
+ split_size: split size.
228
+ n_group: ϵͳ����ʱƽ�����������
229
+ verbose: Control print content. 0 for scilence.
230
+ """
231
+ # Check var
232
+ n_samp, n_feat = np.shape(X)
233
+ if n_samp != len(y):
234
+ raise ValueError('Sample numbers of X and y must match!!!')
235
+ elif self.size >= len(X):
236
+ raise ValueError('Number of sampling points must less than total samples')
237
+
238
+ if type(n_group) != int:
239
+ raise TypeError('n_group must be an integer!!!')
240
+ elif len(X) % n_group != 0:
241
+ raise ValueError('Number of X cannot be divisible by n_group')
242
+ elif 1 > split_size > 0:
243
+ n_split = round(split_size * n_samp) // n_group
244
+ elif split_size <= 0:
245
+ raise ValueError('split_size must be an integer greater than 1, or a float between 0 and 1!!!')
246
+ elif split_size <= n_group:
247
+ raise ValueError('split_size must greater than n_group!!!')
248
+ elif split_size >= 1:
249
+ n_split = split_size // n_group
250
+
251
+ # Initialize var
252
+ X_pred = X_all
253
+ self.sampling_points = np.zeros((self.size, n_feat))
254
+ self.sampling_args = np.zeros(self.size, dtype=np.int64)
255
+ self.uncertainty = np.zeros(self.size, dtype=np.int64)
256
+ n_samples_in = len(X) // n_group
257
+ n_feat = len(X[0, :])
258
+ train_MSE_list = np.empty((self.n_iter,))
259
+ valid_MSE_list = np.empty((self.n_iter,))
260
+ pred_matrix = np.empty((len(X_pred), self.n_iter))
261
+ if verbose > 0: print('Varibles initialized, entering main loop...\n')
262
+
263
+ # MAIN LOOP
264
+ group = np.reshape(X, (n_group, n_samples_in, n_feat)) # ����X_raw
265
+ y_group = np.reshape(y, (n_group, n_samples_in)) # ����y_raw
266
+ np.random.seed(self.seed)
267
+ seed_list = np.random.randint(10000000, size=self.n_iter)
268
+ for i in range(self.n_iter):
269
+ np.random.seed(seed_list[i])
270
+ arg_va = np.random.choice(n_samples_in, size=n_split, replace=False)
271
+ arg_tr = np.delete(np.array(range(n_samples_in)), arg_va)
272
+ # print(arg_va, '\n', arg_tr)
273
+ X_train = np.empty((0, n_feat))
274
+ X_valid = np.empty((0, n_feat))
275
+ y_train = np.empty((0))
276
+ y_valid = np.empty((0))
277
+ for j in range(n_group):
278
+ X_train = np.r_[X_train, group[j, arg_tr, :]]
279
+ X_valid = np.r_[X_valid, group[j, arg_va, :]]
280
+ y_train = np.r_[y_train, y_group[j, arg_tr]]
281
+ y_valid = np.r_[y_valid, y_group[j, arg_va]]
282
+
283
+ self.model.fit(X_train, y_train) # Train the model
284
+ train_MSE = np.sqrt(np.mean((self.model.predict(X_train) - y_train)**2))
285
+ valid_MSE = np.sqrt(np.mean((self.model.predict(X_valid) - y_valid)**2))
286
+ train_MSE_list[i] = copy.deepcopy(train_MSE)
287
+ valid_MSE_list[i] = copy.deepcopy(valid_MSE)
288
+ if verbose > 0: print('***iteration %i train_MSE = %s val_MSE = %s' % (i + 1, train_MSE, valid_MSE))
289
+ # predict
290
+ pred_matrix[:, i] = self.model.predict(X_pred)
291
+ self.train_mean_MSE = np.mean(train_MSE_list)
292
+ self.valid_mean_MSE = np.mean(valid_MSE_list)
293
+ self.predict_mean = np.mean(pred_matrix, axis=1)
294
+ self.uncertain_list = np.std(pred_matrix, axis=1)
295
+ # sort
296
+ self.uncertain_sort_args = np.argsort(self.uncertain_list)
297
+ self.sampling_args = self.uncertain_sort_args[-self.size:]
298
+
299
+
@@ -0,0 +1,140 @@
1
+ # Copyright (c) 2024-2025.7.4, BUCToolkit.
2
+ # Authors: Pu Pengxin, Song Xin
3
+ # Version: 0.9a
4
+ # File: supercells.py
5
+ # Environment: Python 3.12
6
+
7
+ # Calculate the lattice indices of all cells within given cut-off radius.
8
+
9
+ from typing import List, Tuple
10
+
11
+ import numpy as np
12
+ import torch as th
13
+
14
+
15
+ def supercells_indices_within_cutoff(cell_vectors: th.Tensor, r_cut_off: float, device: str | th.device = 'cpu') -> th.Tensor:
16
+ r"""
17
+ Generate the indices of all lattices within r_cut_off for each input cell.
18
+ return the supercell indices with shape (n_cell, 3) where "3" represents expansion directions.
19
+
20
+ Parameters:
21
+ cell_vectors: Tensor(n_batch, 3, 3), a batch of lattice vectors.
22
+ r_cut_off: float, the cut-off radius.
23
+ device: str, the device that calculation performed.
24
+
25
+ Returns: Tensor(n_cell, 3), the tensor of all supercell indices for the given batch.
26
+ """
27
+ # Volume, Areas, and Height of cells
28
+ V = th.linalg.det(cell_vectors) # (n_batch)
29
+ s_ab = th.linalg.cross(cell_vectors[:, 0, :], cell_vectors[:, 1, :]) # (n_batch, 3)
30
+ s_bc = th.linalg.cross(cell_vectors[:, 1, :], cell_vectors[:, 2, :]) # (n_batch, 3)
31
+ s_ac = th.linalg.cross(cell_vectors[:, 0, :], cell_vectors[:, 2, :]) # (n_batch, 3)
32
+
33
+ #h_vec_ab = V/((s_ab.unsqueeze(1)@s_ab.unsqueeze(-1)).squeeze(-1)) * s_ab
34
+ #h_vec_bc = V/((s_bc.unsqueeze(1)@s_bc.unsqueeze(-1)).squeeze(-1)) * s_bc
35
+ #h_vec_ac = V/((s_ac.unsqueeze(1)@s_ac.unsqueeze(-1)).squeeze(-1)) * s_ac
36
+
37
+ h_ab = V / (th.linalg.norm(s_ab, ord=2, dim=1)) # (n_batch, )
38
+ h_bc = V / (th.linalg.norm(s_bc, ord=2, dim=1)) # (n_batch, )
39
+ h_ac = V / (th.linalg.norm(s_ac, ord=2, dim=1)) # (n_batch, )
40
+
41
+ # repitition times of cell in direction a, b, c
42
+ repitition_a = th.ceil(r_cut_off / h_bc) # (n_batch, )
43
+ repitition_b = th.ceil(r_cut_off / h_ac) # (n_batch, )
44
+ repitition_c = th.ceil(r_cut_off / h_ab) # (n_batch, )
45
+
46
+ # max of repititions
47
+ rep_max_a = int(th.max(repitition_a).item())
48
+ rep_max_b = int(th.max(repitition_b).item())
49
+ rep_max_c = int(th.max(repitition_c).item())
50
+
51
+ # construct supercell
52
+ a_ = th.empty(2 * rep_max_a + 1, 1, dtype=th.float, device=device)
53
+ b_ = th.empty(2 * rep_max_b + 1, 1, dtype=th.float, device=device)
54
+ c_ = th.empty(2 * rep_max_c + 1, 1, dtype=th.float, device=device)
55
+ a_[:rep_max_a + 1, 0] = th.arange(0, rep_max_a + 1, dtype=th.float, device=device)
56
+ a_[rep_max_a + 1:, 0] = th.arange(- rep_max_a, 0, dtype=th.float, device=device)
57
+ b_[:rep_max_b + 1, 0] = th.arange(0, rep_max_b + 1, dtype=th.float, device=device)
58
+ b_[rep_max_b + 1:, 0] = th.arange(- rep_max_b, 0, dtype=th.float, device=device)
59
+ c_[:rep_max_c + 1, 0] = th.arange(0, rep_max_c + 1, dtype=th.float, device=device)
60
+ c_[rep_max_c + 1:, 0] = th.arange(- rep_max_c, 0, dtype=th.float, device=device)
61
+
62
+ supercell_index = th.cat(
63
+ (
64
+ a_.repeat_interleave((2 * rep_max_b + 1) * (2 * rep_max_c + 1), 0),
65
+ th.tile(b_.repeat_interleave((2 * rep_max_c + 1), 0), ((2 * rep_max_a + 1), 1)),
66
+ th.tile(c_, ((2 * rep_max_a + 1) * (2 * rep_max_b + 1), 1))
67
+ )
68
+ , dim=1)
69
+
70
+ return supercell_index
71
+
72
+
73
+ def supercell(supercell_index: Tuple[int, int, int] | List[int],
74
+ Cells: th.Tensor,
75
+ Coords: th.Tensor,
76
+ Elements: List[List[str]],
77
+ Numbers: List[List[int]],
78
+ device: str | th.device = 'cpu'
79
+ ) -> (th.Tensor, th.Tensor, List, List):
80
+ """
81
+ expand given cells to supercell.
82
+ Args:
83
+ supercell_index: (3, )
84
+ Cells: (n_batch, 3, 3)
85
+ Coords: (n_batch, n_atom, 3)
86
+ Elements: (n_batch, n_elem)
87
+ Numbers: (n_batch, n_elem)
88
+ device: the device that program run on.
89
+
90
+ Returns:
91
+ supercell_index
92
+ Cells
93
+ Coords
94
+ Elements
95
+ Numbers
96
+ """
97
+ # check vars
98
+ if not isinstance(Cells, th.Tensor): raise TypeError(f'Cells must be torch.Tensor, but occurred {type(Cells)}')
99
+ if not isinstance(Coords, th.Tensor): raise TypeError(f'Coords must be torch.Tensor, but occurred {type(Coords)}')
100
+ n_batch, n_atom, _ = Coords.shape
101
+ if Cells.shape != (n_batch, 3, 3): raise ValueError(f'Uncorrected Cell shape: {Cells.shape}')
102
+ if (len(Elements) != len(Numbers)) or (len(Elements) != n_batch):
103
+ raise ValueError(f'Uncorrected Elements or Numbers length: {len(Elements), len(Numbers)}')
104
+
105
+ Cells = Cells.to(device)
106
+ Coords = Coords.to(device)
107
+
108
+ # supercells' cell vectors
109
+ sc_id = th.from_numpy(np.asarray(supercell_index, dtype=np.float32)).to(device) # (3, )
110
+ sc = th.diag(sc_id, 0) # (3, 3)
111
+ Cells_ = sc @ Cells # (n_batch, 3, 3)
112
+
113
+ # construct supercells' coordinate
114
+ rep_max_a, rep_max_b, rep_max_c = supercell_index
115
+ a_ = th.empty(rep_max_a, 1, dtype=th.float, device=device)
116
+ b_ = th.empty(rep_max_b, 1, dtype=th.float, device=device)
117
+ c_ = th.empty(rep_max_c, 1, dtype=th.float, device=device)
118
+ a_[:, 0] = th.arange(0, rep_max_a, 1, dtype=th.float, device=device)
119
+ b_[:, 0] = th.arange(0, rep_max_b, 1, dtype=th.float, device=device)
120
+ c_[:, 0] = th.arange(0, rep_max_c, 1, dtype=th.float, device=device)
121
+
122
+ indx = th.cat(
123
+ (
124
+ a_.repeat_interleave(rep_max_b * rep_max_c, 0),
125
+ th.tile(b_.repeat_interleave(rep_max_c, 0), (rep_max_a, 1)),
126
+ th.tile(c_, (rep_max_a * rep_max_b, 1))
127
+ )
128
+ , dim=1
129
+ ) # (n_cells, 3), n_cells = multiple(supercell_index)
130
+ n_cell = len(indx)
131
+
132
+ # (n_batch, 1, n_atom, 3) + (1, n_cells, 1, 3) @ (n_batch, 1, 3, 3) -> (n_batch, n_cells, n_atom, 3)
133
+ Coords_ = Coords.unsqueeze(1) + indx.unsqueeze(0).unsqueeze(-2) @ Cells.unsqueeze(1)
134
+ Coords_ = Coords_.flatten(1, 2) # (n_batch, n_cells * n_atom, 3)
135
+
136
+ # Elements & Numbers
137
+ Elements_ = [Elements[i] * n_cell for i in range(n_batch)]
138
+ Numbers_ = [Numbers[i] * n_cell for i in range(n_batch)]
139
+
140
+ return Cells_, Coords_, Elements_, Numbers_