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,286 @@
|
|
|
1
|
+
# Copyright (c) 2025.7.4, BUCToolkit.
|
|
2
|
+
# Authors: Pu Pengxin, Song Xin
|
|
3
|
+
# Version: 0.9a
|
|
4
|
+
# File: CI_NEB.py
|
|
5
|
+
# Environment: Python 3.12
|
|
6
|
+
|
|
7
|
+
from typing import Iterable, Dict, Any, List, Literal, Optional, Callable, Sequence, Tuple # noqa: F401
|
|
8
|
+
import time
|
|
9
|
+
import warnings
|
|
10
|
+
import logging
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import torch as th
|
|
15
|
+
from torch import nn
|
|
16
|
+
from torch.nn import functional as Fn
|
|
17
|
+
|
|
18
|
+
from BUCToolkit.utils._print_formatter import GLOBAL_SCIENTIFIC_ARRAY_FORMAT, SCIENTIFIC_ARRAY_FORMAT
|
|
19
|
+
from BUCToolkit.BatchGenerate.coords_interp import linear_interpolation_tens
|
|
20
|
+
from ..minimize import CG, QN, FIRE
|
|
21
|
+
|
|
22
|
+
np.set_printoptions(**GLOBAL_SCIENTIFIC_ARRAY_FORMAT)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CI_NEB:
|
|
26
|
+
"""
|
|
27
|
+
Ref. TODO
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
N_images: int,
|
|
34
|
+
spring_const: float = 1.,
|
|
35
|
+
optimizer: Literal['FIRE'] = 'FIRE',
|
|
36
|
+
optimizer_configs: Optional[Dict[str, Any]] = None,
|
|
37
|
+
steplength: float = 0.05,
|
|
38
|
+
E_threshold: float = 1e-3,
|
|
39
|
+
F_threshold: float = 0.05,
|
|
40
|
+
maxiter: int = 100,
|
|
41
|
+
device: str | th.device = 'cpu',
|
|
42
|
+
verbose: int = 2
|
|
43
|
+
):
|
|
44
|
+
if optimizer_configs is None:
|
|
45
|
+
optimizer_configs = dict()
|
|
46
|
+
warnings.filterwarnings('always')
|
|
47
|
+
optimizer_dict = {'FIRE': FIRE}
|
|
48
|
+
if optimizer not in optimizer_dict:
|
|
49
|
+
raise NotImplementedError(f"Optimizer {optimizer} is not implemented yet. Now only `FIRE` is available.")
|
|
50
|
+
self.Optimizer = optimizer_dict[optimizer](
|
|
51
|
+
E_threshold = E_threshold,
|
|
52
|
+
F_threshold = F_threshold,
|
|
53
|
+
maxiter = maxiter,
|
|
54
|
+
steplength = steplength,
|
|
55
|
+
device = device,
|
|
56
|
+
verbose = verbose,
|
|
57
|
+
_hold_samples = True, # important! To ensure that no sample will be removed during NEB process.
|
|
58
|
+
**optimizer_configs
|
|
59
|
+
)
|
|
60
|
+
self.N_images = N_images
|
|
61
|
+
self.spring_const = spring_const
|
|
62
|
+
self.E_threshold = E_threshold
|
|
63
|
+
self.F_threshold = F_threshold
|
|
64
|
+
self.maxiter = maxiter
|
|
65
|
+
self.device = device
|
|
66
|
+
self.verbose = verbose
|
|
67
|
+
# check
|
|
68
|
+
if (not isinstance(self.maxiter, int)) or (self.maxiter <= 0):
|
|
69
|
+
raise ValueError(f'Invalid value of maxiter: {self.maxiter}. It would be an integer greater than 0.')
|
|
70
|
+
if self.N_images < 1:
|
|
71
|
+
raise ValueError(f'`N_images` must be >= 1 for CI-NEB, but got {self.N_images}.')
|
|
72
|
+
|
|
73
|
+
# logger
|
|
74
|
+
self.logger = logging.getLogger('Main.OPT')
|
|
75
|
+
self.logger.setLevel(logging.INFO)
|
|
76
|
+
formatter = logging.Formatter('%(message)s')
|
|
77
|
+
if not self.logger.hasHandlers():
|
|
78
|
+
log_handler = logging.StreamHandler(sys.stdout, )
|
|
79
|
+
log_handler.setLevel(logging.INFO)
|
|
80
|
+
log_handler.setFormatter(formatter)
|
|
81
|
+
self.logger.addHandler(log_handler)
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
class __NebWrapper:
|
|
85
|
+
""" function wrapper for NEB, Adding the elastic potential """
|
|
86
|
+
def __init__(self, f: Callable, gf: Callable, k: float, require_grad: bool, is_grad_contain_y: bool):
|
|
87
|
+
self.ener = None
|
|
88
|
+
self.F_ori = None
|
|
89
|
+
self.f = f
|
|
90
|
+
self.gf = gf
|
|
91
|
+
self.k = k
|
|
92
|
+
self.is_grad_contain_y = is_grad_contain_y
|
|
93
|
+
self.require_grad = require_grad
|
|
94
|
+
|
|
95
|
+
def energy(self, X, *args, **kwargs):
|
|
96
|
+
with th.set_grad_enabled(self.require_grad):
|
|
97
|
+
X.requires_grad_(self.require_grad)
|
|
98
|
+
y = self.f(X, *args, **kwargs)
|
|
99
|
+
self.ener = y
|
|
100
|
+
if self.is_grad_contain_y: # here `X` is actually `y`
|
|
101
|
+
F_ori = - self.gf(y, X, *args, **kwargs) # (n_i, n_atom, 3)
|
|
102
|
+
else:
|
|
103
|
+
F_ori = - self.gf(X, *args, **kwargs) # (n_i, n_atom, 3)
|
|
104
|
+
self.F_ori = F_ori
|
|
105
|
+
return y # the true energy, only for exhibiting system energy, not involving into optimizing.
|
|
106
|
+
|
|
107
|
+
def grad(self, X, *args, **kwargs):
|
|
108
|
+
"""
|
|
109
|
+
Returns the gradient of the energy function
|
|
110
|
+
Args:
|
|
111
|
+
X: It is only a placeholder, a dummy variable.
|
|
112
|
+
*args: args[0] is the true coordinates X
|
|
113
|
+
**kwargs:
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
|
|
117
|
+
"""
|
|
118
|
+
assert self.ener is not None, 'Energy is None now, please calculate energy first.'
|
|
119
|
+
F_ori = self.F_ori # (n_i, n_atom, 3)
|
|
120
|
+
if self.is_grad_contain_y:
|
|
121
|
+
X = args[0]
|
|
122
|
+
else:
|
|
123
|
+
X = X
|
|
124
|
+
|
|
125
|
+
n_i, n_atom, n_dim = X.shape
|
|
126
|
+
|
|
127
|
+
with th.no_grad():
|
|
128
|
+
# projected direction
|
|
129
|
+
_tau = th.diff(X, dim=0) # (n_i - 1, n_atom, 3)
|
|
130
|
+
ediff = th.diff(self.ener) # (n_i - 1, )
|
|
131
|
+
# *_back = E[i] - E[i - 1]
|
|
132
|
+
ediff_back = ediff[:-1].unsqueeze(-1).unsqueeze(-1) # (n_i - 2, 1, 1), E_(n) - E_(n-1), keep the same dim as `_tau`
|
|
133
|
+
tau_back = _tau[:-1] # set of image[0 : n_i - 1], X[i] - X[i - 1]
|
|
134
|
+
# *_front = E[i + 1] - E[i]
|
|
135
|
+
ediff_front = ediff[1:].unsqueeze(-1).unsqueeze(-1) # (n_i - 2, 1, 1), E_(n+1) - E_(n)
|
|
136
|
+
tau_front = _tau[1:] # set of image[1 : n_i], X[i + 1] - X[i]
|
|
137
|
+
tau_front_u = Fn.normalize(tau_front.flatten(-2, -1), dim=-1).reshape(n_i - 2, n_atom, -1)
|
|
138
|
+
tau_back_u = Fn.normalize(tau_back.flatten(-2, -1), dim=-1).reshape(n_i - 2, n_atom, -1)
|
|
139
|
+
# *_front - *_back: image[i + 1] - image[i] & deltaE[i + 1] - deltaE[i] for each i \in [0, n_i - 1]
|
|
140
|
+
delta_Emax = th.where(th.abs(ediff_front) > th.abs(ediff_back), th.abs(ediff_front), th.abs(ediff_back))
|
|
141
|
+
delta_Emin = th.where(th.abs(ediff_front) < th.abs(ediff_back), th.abs(ediff_front), th.abs(ediff_back))
|
|
142
|
+
tau_extreme = th.where(
|
|
143
|
+
ediff_front + ediff_back > 0, # (E_(n+1) > E_(n-1))
|
|
144
|
+
tau_front_u * delta_Emax + tau_back_u * delta_Emin,
|
|
145
|
+
tau_front_u * delta_Emin + tau_back_u * delta_Emax
|
|
146
|
+
)
|
|
147
|
+
tau = th.where(
|
|
148
|
+
(ediff_front > 0) * (ediff_back > 0),
|
|
149
|
+
tau_front_u,
|
|
150
|
+
th.where(
|
|
151
|
+
(ediff_front <= 0) * (ediff_back <= 0),
|
|
152
|
+
tau_back_u,
|
|
153
|
+
tau_extreme
|
|
154
|
+
)
|
|
155
|
+
)# (n_i - 2, n_atom, n_dim)
|
|
156
|
+
tau = Fn.normalize(tau.flatten(-2, -1), dim=-1) # (n_i - 2, n_atom*n_dim)
|
|
157
|
+
|
|
158
|
+
# forces
|
|
159
|
+
F_ori[0] = 0.
|
|
160
|
+
F_ori[-1] = 0.
|
|
161
|
+
F_image = F_ori[1: -1].flatten(-2, -1) # (n_i - 2, n_atom*n_dim)
|
|
162
|
+
# climbing
|
|
163
|
+
max_indx = th.argmax(self.ener[1:-1])
|
|
164
|
+
maxF = F_image[max_indx].clone() # (n_atom * n_dim, )
|
|
165
|
+
maxF -= 2 * ((maxF.unsqueeze(0) @ tau[max_indx].unsqueeze(-1)).squeeze()) * tau[max_indx]
|
|
166
|
+
# other NEB
|
|
167
|
+
F_vert = F_image - ((F_image.unsqueeze(1) @ tau.unsqueeze(-1)).squeeze(-1)) * tau
|
|
168
|
+
F_hori = self.k * (th.linalg.norm(tau_front, dim=(-2, -1)) - th.linalg.norm(tau_back, dim=(-2, -1))).unsqueeze(-1) * tau
|
|
169
|
+
F_ori[1: -1] = (F_vert + F_hori).reshape(n_i-2, n_atom, n_dim)
|
|
170
|
+
#print(f'NEB forces: {F_ori.tolist()}')
|
|
171
|
+
F_ori[max_indx + 1] = maxF.reshape(n_atom, n_dim)
|
|
172
|
+
|
|
173
|
+
return - F_ori
|
|
174
|
+
|
|
175
|
+
def run(
|
|
176
|
+
self,
|
|
177
|
+
func: Any | nn.Module,
|
|
178
|
+
X_init: th.Tensor,
|
|
179
|
+
X_fin: th.Tensor,
|
|
180
|
+
grad_func: Any | nn.Module = None,
|
|
181
|
+
func_args: Tuple = tuple(),
|
|
182
|
+
func_kwargs=None,
|
|
183
|
+
grad_func_args: Tuple = tuple(),
|
|
184
|
+
grad_func_kwargs=None,
|
|
185
|
+
is_grad_func_contain_y: bool = True,
|
|
186
|
+
require_grad = False,
|
|
187
|
+
output_grad: bool = False,
|
|
188
|
+
fixed_atom_tensor: Optional[th.Tensor] = None,
|
|
189
|
+
):
|
|
190
|
+
"""
|
|
191
|
+
run CI-NEB algo.
|
|
192
|
+
Args:
|
|
193
|
+
func: function
|
|
194
|
+
X_init: (n_atom, n_dim), initial structure coordinates
|
|
195
|
+
X_fin: (n_atom, n_dim), finale structure coordinates
|
|
196
|
+
grad_func: function of func's gradient
|
|
197
|
+
func_args: function args
|
|
198
|
+
func_kwargs: function kwargs
|
|
199
|
+
grad_func_args: gradient function args
|
|
200
|
+
grad_func_kwargs: gradient function kwargs
|
|
201
|
+
is_grad_func_contain_y: whether gradient function contains dependent various y
|
|
202
|
+
require_grad: bool, if True, autograd will be turned on for func(X, *func_args, **func_kwargs) calculation.
|
|
203
|
+
output_grad: whether output gradient
|
|
204
|
+
fixed_atom_tensor: (n_atom, n_dim), mask of fixed atoms
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
|
|
208
|
+
"""
|
|
209
|
+
# initialize
|
|
210
|
+
if grad_func_kwargs is None:
|
|
211
|
+
grad_func_kwargs = dict()
|
|
212
|
+
if func_kwargs is None:
|
|
213
|
+
func_kwargs = dict()
|
|
214
|
+
if grad_func is None:
|
|
215
|
+
is_grad_func_contain_y = True
|
|
216
|
+
require_grad = True
|
|
217
|
+
def grad_func_(y, x, grad_shape=None):
|
|
218
|
+
if grad_shape is None:
|
|
219
|
+
grad_shape = th.ones_like(y)
|
|
220
|
+
g = th.autograd.grad(y, x, grad_shape)
|
|
221
|
+
return g[0]
|
|
222
|
+
else:
|
|
223
|
+
grad_func_ = grad_func
|
|
224
|
+
|
|
225
|
+
# check
|
|
226
|
+
if len(X_init.shape) != 2:
|
|
227
|
+
X_init.squeeze_(0)
|
|
228
|
+
X_fin.squeeze_(0)
|
|
229
|
+
if isinstance(func, nn.Module):
|
|
230
|
+
func = func.to(self.device)
|
|
231
|
+
func.zero_grad()
|
|
232
|
+
if isinstance(grad_func_, nn.Module):
|
|
233
|
+
grad_func_ = grad_func_.to(self.device)
|
|
234
|
+
if X_init.shape != X_fin.shape:
|
|
235
|
+
raise ValueError(f'Shape of X_init {X_init.shape} and X_fin{X_fin.shape} does not match.')
|
|
236
|
+
elif len(X_init.shape) != 2:
|
|
237
|
+
raise ValueError(f'Invalid shape of X_init: {X_init.shape}, it mast be (n_atom, 3).')
|
|
238
|
+
X_init = X_init.to(self.device)
|
|
239
|
+
X_fin = X_fin.to(self.device)
|
|
240
|
+
# Selective dynamics for interpolation
|
|
241
|
+
if fixed_atom_tensor is None:
|
|
242
|
+
atom_masks = th.ones_like(X_init, device=self.device)
|
|
243
|
+
elif fixed_atom_tensor.shape == X_init.shape:
|
|
244
|
+
atom_masks = fixed_atom_tensor.to(self.device)
|
|
245
|
+
else:
|
|
246
|
+
raise RuntimeError(f'fixed_atom_tensor (shape: {fixed_atom_tensor.shape}) does not have the same shape of X (shape: {X_init.shape}).')
|
|
247
|
+
|
|
248
|
+
# interpolation images
|
|
249
|
+
X_pnts: th.Tensor = linear_interpolation_tens(X_init, X_fin, self.N_images + 2, atom_masks) # (N_img, n_atom, n_dim)
|
|
250
|
+
#plist = list() # TEST <<<<
|
|
251
|
+
|
|
252
|
+
# Main Loop
|
|
253
|
+
#X_pnts = X_pnts.flatten(0, 1) # (N_img, n_atom, n_dim). NOTE: '_' means the flatten variables.
|
|
254
|
+
f = self.__NebWrapper(func, grad_func_, self.spring_const, require_grad, is_grad_func_contain_y)
|
|
255
|
+
self.Optimizer: FIRE
|
|
256
|
+
with th.no_grad():
|
|
257
|
+
oup = self.Optimizer.run(
|
|
258
|
+
f.energy,
|
|
259
|
+
X_pnts,
|
|
260
|
+
f.grad,
|
|
261
|
+
func_args,
|
|
262
|
+
func_kwargs,
|
|
263
|
+
grad_func_args,
|
|
264
|
+
grad_func_kwargs,
|
|
265
|
+
is_grad_func_contain_y,
|
|
266
|
+
require_grad,
|
|
267
|
+
output_grad,
|
|
268
|
+
atom_masks.broadcast_to(X_pnts.shape)
|
|
269
|
+
)
|
|
270
|
+
if output_grad:
|
|
271
|
+
_energy, _X, out_grad = oup
|
|
272
|
+
else:
|
|
273
|
+
_energy, _X = oup
|
|
274
|
+
# final print
|
|
275
|
+
if self.verbose:
|
|
276
|
+
max_ener, max_indx = th.max(_energy, dim=0)
|
|
277
|
+
self.logger.info(
|
|
278
|
+
f'-----------------------------------------------------\n'
|
|
279
|
+
f'Final NEB path energy: {np.array2string(_energy.numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n'
|
|
280
|
+
f'Final CI-NEB max energy: {max_ener.item():< .8e}\n'
|
|
281
|
+
f'Image index: {max_indx.item():< 3d}\n'
|
|
282
|
+
f'-----------------------------------------------------\n'
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
return oup#, plist # TEST <<<<<<
|
|
286
|
+
|