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.
- BUCToolkit/BatchGenerate/CheckStructures.py +198 -0
- BUCToolkit/BatchGenerate/__init__.py +6 -0
- BUCToolkit/BatchGenerate/coords_interp.py +127 -0
- BUCToolkit/BatchGenerate/initial_ads_structures.py +351 -0
- BUCToolkit/BatchGenerate/random_replace.py +88 -0
- BUCToolkit/BatchMC/MetropolisMC.py +210 -0
- BUCToolkit/BatchMC/_BaseMC.py +863 -0
- BUCToolkit/BatchMC/__init__.py +8 -0
- BUCToolkit/BatchMD/BiasedMD.py +276 -0
- BUCToolkit/BatchMD/NVE.py +92 -0
- BUCToolkit/BatchMD/NVT.py +510 -0
- BUCToolkit/BatchMD/_BaseMD.py +1251 -0
- BUCToolkit/BatchMD/__init__.py +18 -0
- BUCToolkit/BatchMD/constrained_md/BiasedMD.py +273 -0
- BUCToolkit/BatchMD/constrained_md/ConstrNVE.py +169 -0
- BUCToolkit/BatchMD/constrained_md/ConstrNVT.py +462 -0
- BUCToolkit/BatchMD/constrained_md/NVE.py +78 -0
- BUCToolkit/BatchMD/constrained_md/NVT.py +346 -0
- BUCToolkit/BatchMD/constrained_md/_BaseMD.py +440 -0
- BUCToolkit/BatchMD/constrained_md/_ConstrBaseMD.py +607 -0
- BUCToolkit/BatchMD/constrained_md/__init__.py +18 -0
- BUCToolkit/BatchOptim/TS/CI_NEB.py +286 -0
- BUCToolkit/BatchOptim/TS/Dimer.py +950 -0
- BUCToolkit/BatchOptim/TS/__init__.py +10 -0
- BUCToolkit/BatchOptim/_BaseOpt.py +702 -0
- BUCToolkit/BatchOptim/__init__.py +22 -0
- BUCToolkit/BatchOptim/_utils/__init__.py +6 -0
- BUCToolkit/BatchOptim/_utils/_line_search.py +972 -0
- BUCToolkit/BatchOptim/_utils/_warnings.py +11 -0
- BUCToolkit/BatchOptim/frequency.py +496 -0
- BUCToolkit/BatchOptim/minimize/CG.py +256 -0
- BUCToolkit/BatchOptim/minimize/FIRE.py +310 -0
- BUCToolkit/BatchOptim/minimize/QN.py +205 -0
- BUCToolkit/BatchOptim/minimize/__init__.py +17 -0
- BUCToolkit/BatchStructures/BatchStructuresBase.py +2261 -0
- BUCToolkit/BatchStructures/StructuresIO.py +1394 -0
- BUCToolkit/BatchStructures/__init__.py +15 -0
- BUCToolkit/Preprocessing/__init__.py +7 -0
- BUCToolkit/Preprocessing/load_files.py +1680 -0
- BUCToolkit/Preprocessing/preprocessing.py +923 -0
- BUCToolkit/Preprocessing/sampling.py +299 -0
- BUCToolkit/Preprocessing/supercells.py +140 -0
- BUCToolkit/Preprocessing/write_files.py +524 -0
- BUCToolkit/StandardTemplate.py +417 -0
- BUCToolkit/__init__.py +33 -0
- BUCToolkit/_version.py +7 -0
- BUCToolkit/api/ConstrainedMolecularDynamics.py +347 -0
- BUCToolkit/api/DataLoaders.py +321 -0
- BUCToolkit/api/Losses.py +170 -0
- BUCToolkit/api/Metrics.py +84 -0
- BUCToolkit/api/ModelOptims.py +296 -0
- BUCToolkit/api/MolecularDynamics.py +282 -0
- BUCToolkit/api/MonteCarlo.py +280 -0
- BUCToolkit/api/NEB.py +325 -0
- BUCToolkit/api/Predictor.py +292 -0
- BUCToolkit/api/StructureOptimization.py +484 -0
- BUCToolkit/api/Trainer.py +635 -0
- BUCToolkit/api/VibrationAnalysis.py +279 -0
- BUCToolkit/api/__init__.py +38 -0
- BUCToolkit/api/_io.py +1026 -0
- BUCToolkit/cli/__init__.py +20 -0
- BUCToolkit/cli/cli.py +934 -0
- BUCToolkit/cli/input_stub.py +140 -0
- BUCToolkit/cli/main.py +368 -0
- BUCToolkit/cli/print_logo.py +149 -0
- BUCToolkit/cli/template_model.py +6 -0
- BUCToolkit/io.py +30 -0
- BUCToolkit/utils/AtomSelector.py +64 -0
- BUCToolkit/utils/AtomicNumber2Properties.py +39 -0
- BUCToolkit/utils/ElemListReduce.py +52 -0
- BUCToolkit/utils/Ewald.py +210 -0
- BUCToolkit/utils/IrregularTensorReformat.py +59 -0
- BUCToolkit/utils/_CheckModules.py +30 -0
- BUCToolkit/utils/_Element_info.py +527 -0
- BUCToolkit/utils/__init__.py +22 -0
- BUCToolkit/utils/_para_flatt_list.py +131 -0
- BUCToolkit/utils/_print_formatter.py +59 -0
- BUCToolkit/utils/check_structures.py +204 -0
- BUCToolkit/utils/function_utils.py +147 -0
- BUCToolkit/utils/grad_functions.py +69 -0
- BUCToolkit/utils/index_ops.py +432 -0
- BUCToolkit/utils/setup_loggers.py +137 -0
- buctoolkit-1.0b0.dist-info/METADATA +28 -0
- buctoolkit-1.0b0.dist-info/RECORD +88 -0
- buctoolkit-1.0b0.dist-info/WHEEL +5 -0
- buctoolkit-1.0b0.dist-info/entry_points.txt +6 -0
- buctoolkit-1.0b0.dist-info/licenses/LICENSE +21 -0
- 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_
|