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,256 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Conjugate Gradient Algorithm for optimization.
|
|
3
|
+
"""
|
|
4
|
+
# Copyright (c) 2024-2025.7.4, BUCToolkit.
|
|
5
|
+
# Authors: Pu Pengxin, Song Xin
|
|
6
|
+
# Version: 0.9a
|
|
7
|
+
# File: CG.py
|
|
8
|
+
# Environment: Python 3.12
|
|
9
|
+
|
|
10
|
+
from typing import Literal
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
import torch as th
|
|
14
|
+
|
|
15
|
+
from BUCToolkit.BatchOptim._BaseOpt import _BaseOpt
|
|
16
|
+
from BUCToolkit.utils.index_ops import index_inner_product
|
|
17
|
+
|
|
18
|
+
os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CG(_BaseOpt):
|
|
22
|
+
"""
|
|
23
|
+
Conjugate Gradient Algo.
|
|
24
|
+
Args:
|
|
25
|
+
iter_scheme: Literal['PR+', 'FR', 'SD'],
|
|
26
|
+
E_threshold: float = 1e-3,
|
|
27
|
+
F_threshold: float = 0.05,
|
|
28
|
+
maxiter: int = 100,
|
|
29
|
+
linesearch: Literal['Backtrack', 'Wolfe', 'NWolfe', '2PT', '3PT', 'Golden', 'Newton', 'None'] = 'Backtrack',
|
|
30
|
+
linesearch_maxiter: int = 10,
|
|
31
|
+
linesearch_thres: float = 0.02,
|
|
32
|
+
linesearch_factor: float = 0.6,
|
|
33
|
+
steplength: float = 0.5,
|
|
34
|
+
use_bb: whether to use Barzilai-Borwein steplength (BB1 or long BB) as initial steplength instead of fixed one.
|
|
35
|
+
device: str | th.device = 'cpu',
|
|
36
|
+
verbose: int = 2
|
|
37
|
+
|
|
38
|
+
Methods:
|
|
39
|
+
run: launch the optimization.
|
|
40
|
+
set_update_batch: setting the method to update the taget function when variables change.
|
|
41
|
+
"""
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
iter_scheme: Literal['PR+', 'FR', 'SD'],
|
|
45
|
+
E_threshold: float = 1e-3,
|
|
46
|
+
F_threshold: float = 0.05,
|
|
47
|
+
maxiter: int = 100,
|
|
48
|
+
linesearch: Literal['Backtrack', 'B', 'Wolfe', 'W', 'MT', 'EXACT', 'None', 'N'] = 'Backtrack',
|
|
49
|
+
linesearch_maxiter: int = 10,
|
|
50
|
+
linesearch_thres: float = 0.02,
|
|
51
|
+
linesearch_factor: float = 0.6,
|
|
52
|
+
steplength: float = 0.5,
|
|
53
|
+
use_bb: bool = True,
|
|
54
|
+
output_file: str | None = None,
|
|
55
|
+
device: str | th.device = 'cpu',
|
|
56
|
+
verbose: int = 2
|
|
57
|
+
):
|
|
58
|
+
super().__init__(
|
|
59
|
+
iter_scheme,
|
|
60
|
+
E_threshold,
|
|
61
|
+
F_threshold,
|
|
62
|
+
maxiter,
|
|
63
|
+
linesearch,
|
|
64
|
+
linesearch_maxiter,
|
|
65
|
+
linesearch_thres,
|
|
66
|
+
linesearch_factor,
|
|
67
|
+
steplength,
|
|
68
|
+
use_bb,
|
|
69
|
+
output_file,
|
|
70
|
+
device,
|
|
71
|
+
verbose
|
|
72
|
+
)
|
|
73
|
+
self.__update_scheme = None
|
|
74
|
+
self.MIN_CACHE = th.scalar_tensor(1.e-20, device=self.device)
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def __check_restart(gg, ggo, g, p, beta):
|
|
78
|
+
"""
|
|
79
|
+
check if restarting is needed.
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
ortho_check = th.abs(ggo) > 0.1 * gg
|
|
83
|
+
return ortho_check
|
|
84
|
+
|
|
85
|
+
def __PRP_irreg(
|
|
86
|
+
self,
|
|
87
|
+
g: th.Tensor,
|
|
88
|
+
g_old: th.Tensor,
|
|
89
|
+
p: th.Tensor,
|
|
90
|
+
X: th.Tensor,
|
|
91
|
+
batch_scatter_indices: th.Tensor | None,
|
|
92
|
+
) -> th.Tensor:
|
|
93
|
+
"""
|
|
94
|
+
POLAK-RIBIERE CG ALGORITHM for irregular batch.
|
|
95
|
+
"""
|
|
96
|
+
gogo = th.sum(index_inner_product(g_old, g_old, 1, batch_scatter_indices), dim=-1, keepdim=True).clamp_min_(self.MIN_CACHE) # (1, B, 1)
|
|
97
|
+
ggo = th.sum(index_inner_product(g, g_old, 1, batch_scatter_indices), dim=-1, keepdim=True)
|
|
98
|
+
gg = th.sum(index_inner_product(g, g, 1, batch_scatter_indices), dim=-1, keepdim=True)
|
|
99
|
+
beta = (gg - ggo) / gogo # (1, B, 1)
|
|
100
|
+
beta.clamp_min_(self.MIN_CACHE)
|
|
101
|
+
# Restart
|
|
102
|
+
is_restart = self.__check_restart(gg, ggo, g, p, beta)
|
|
103
|
+
beta.masked_fill_(is_restart, 0.)
|
|
104
|
+
beta = beta.index_select(1, batch_scatter_indices)
|
|
105
|
+
if self.verbose > 1:
|
|
106
|
+
self.logger.info(f" Restart: {is_restart.flatten().cpu().numpy()}")
|
|
107
|
+
# update directions
|
|
108
|
+
#p = -g + beta * p # (n_batch, n_atom, n_dim)
|
|
109
|
+
th.addcmul(-g, beta, p, out=p)
|
|
110
|
+
|
|
111
|
+
return p
|
|
112
|
+
|
|
113
|
+
def __PRP_reg(
|
|
114
|
+
self ,
|
|
115
|
+
g: th.Tensor,
|
|
116
|
+
g_old: th.Tensor,
|
|
117
|
+
p: th.Tensor,
|
|
118
|
+
X: th.Tensor,
|
|
119
|
+
batch_scatter_
|
|
120
|
+
) -> th.Tensor:
|
|
121
|
+
"""
|
|
122
|
+
POLAK-RIBIERE CG ALGORITHM for regular batch.
|
|
123
|
+
"""
|
|
124
|
+
gogo = th.sum(g_old * g_old, dim=(-2, -1), keepdim=True).clamp_min_(self.MIN_CACHE) # to avoid divide 0
|
|
125
|
+
ggo = th.sum(g * g_old, dim=(-2, -1), keepdim=True) # (n_batch, 1, 1)
|
|
126
|
+
gg = th.sum(g * g, dim=(-2, -1), keepdim=True) # (n_batch, 1, 1)
|
|
127
|
+
beta = (gg - ggo) / gogo # (1, B, 1)
|
|
128
|
+
beta.clamp_min_(self.MIN_CACHE)
|
|
129
|
+
# Restart
|
|
130
|
+
is_restart = self.__check_restart(gg, ggo, g, p, beta)
|
|
131
|
+
beta.masked_fill_(is_restart, 0.)
|
|
132
|
+
if self.verbose > 1:
|
|
133
|
+
self.logger.info(f" Restart: {is_restart.flatten().cpu().numpy()}")
|
|
134
|
+
# update directions
|
|
135
|
+
#p = -g + beta * p # (n_batch, n_atom, n_dim)
|
|
136
|
+
th.addcmul(-g, beta, p, out=p)
|
|
137
|
+
|
|
138
|
+
return p
|
|
139
|
+
|
|
140
|
+
def __FR_irreg(
|
|
141
|
+
self,
|
|
142
|
+
g: th.Tensor,
|
|
143
|
+
g_old: th.Tensor,
|
|
144
|
+
p: th.Tensor,
|
|
145
|
+
X: th.Tensor,
|
|
146
|
+
batch_scatter_indices
|
|
147
|
+
) -> th.Tensor:
|
|
148
|
+
""" FLETCHER-REEVES ALGO. for irregular batch. """
|
|
149
|
+
gogo = th.sum(index_inner_product(g_old, g_old, 1, batch_scatter_indices), dim=-1, keepdim=True).clamp_min_(self.MIN_CACHE) # (1, B, 1)
|
|
150
|
+
ggo = th.sum(index_inner_product(g, g_old, 1, batch_scatter_indices), dim=-1, keepdim=True)
|
|
151
|
+
gg = th.sum(index_inner_product(g, g, 1, batch_scatter_indices), dim=-1, keepdim=True)
|
|
152
|
+
beta = gg / gogo
|
|
153
|
+
# Restart
|
|
154
|
+
is_restart = self.__check_restart(gg, ggo, g, p, beta)
|
|
155
|
+
beta.masked_fill_(is_restart, 0.) # (1, B, 1)
|
|
156
|
+
beta = beta.index_select(1, batch_scatter_indices) # (1, sumN, 1)
|
|
157
|
+
if self.verbose > 1:
|
|
158
|
+
self.logger.info(f" Restart: {is_restart.flatten().cpu().numpy()}")
|
|
159
|
+
# update directions
|
|
160
|
+
th.addcmul(-g, beta, p, out=p)
|
|
161
|
+
|
|
162
|
+
return p
|
|
163
|
+
|
|
164
|
+
def __FR_reg(
|
|
165
|
+
self,
|
|
166
|
+
g: th.Tensor,
|
|
167
|
+
g_old: th.Tensor,
|
|
168
|
+
p: th.Tensor,
|
|
169
|
+
X: th.Tensor,
|
|
170
|
+
batch_scatter_
|
|
171
|
+
) -> th.Tensor:
|
|
172
|
+
""" FLETCHER-REEVES ALGO. for regular batch. """
|
|
173
|
+
gogo = th.sum(g_old * g_old, dim=(-2, -1), keepdim=True).clamp_min_(self.MIN_CACHE) # to avoid divide 0
|
|
174
|
+
ggo = th.sum(g * g_old, dim=(-2, -1), keepdim=True) # (n_batch, 1, 1)
|
|
175
|
+
gg = th.sum(g * g, dim=(-2, -1), keepdim=True) # (n_batch, 1, 1)
|
|
176
|
+
beta = gg / gogo
|
|
177
|
+
# Restart
|
|
178
|
+
is_restart = self.__check_restart(gg, ggo, g, p, beta)
|
|
179
|
+
beta.masked_fill_(is_restart, 0.)
|
|
180
|
+
if self.verbose > 1:
|
|
181
|
+
self.logger.info(f" Restart: {is_restart.flatten().cpu().numpy()}")
|
|
182
|
+
# update directions
|
|
183
|
+
th.addcmul(-g, beta, p, out=p) # (n_batch, n_atom, n_dim)
|
|
184
|
+
|
|
185
|
+
return p
|
|
186
|
+
|
|
187
|
+
@staticmethod
|
|
188
|
+
def __SD(g: th.Tensor, g_old: th.Tensor, p: th.Tensor, X: th.Tensor, batch_scatter_) -> th.Tensor:
|
|
189
|
+
""" Steepest descent """
|
|
190
|
+
return -g
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def initialize_algo_param(self):
|
|
194
|
+
"""
|
|
195
|
+
Override this method to initialize attribute variables for self._update_direction.
|
|
196
|
+
|
|
197
|
+
Returns: None
|
|
198
|
+
"""
|
|
199
|
+
if self.iterform == "SD":
|
|
200
|
+
self.__update_scheme = self.__SD
|
|
201
|
+
return
|
|
202
|
+
if self.is_concat_X:
|
|
203
|
+
if self.iterform == "PR+":
|
|
204
|
+
self.__update_scheme = self.__PRP_irreg
|
|
205
|
+
elif self.iterform == "FR":
|
|
206
|
+
self.__update_scheme = self.__FR_irreg
|
|
207
|
+
else:
|
|
208
|
+
raise NotImplementedError("Unknown Iterative Scheme.")
|
|
209
|
+
else:
|
|
210
|
+
if self.iterform == "PR+":
|
|
211
|
+
self.__update_scheme = self.__PRP_reg
|
|
212
|
+
elif self.iterform == "FR":
|
|
213
|
+
self.__update_scheme = self.__FR_reg
|
|
214
|
+
else:
|
|
215
|
+
raise NotImplementedError("Unknown Iterative Scheme.")
|
|
216
|
+
|
|
217
|
+
def _update_direction(
|
|
218
|
+
self,
|
|
219
|
+
g: th.Tensor,
|
|
220
|
+
g_old: th.Tensor,
|
|
221
|
+
p: th.Tensor,
|
|
222
|
+
X: th.Tensor,
|
|
223
|
+
batch_scatter_: th.Tensor | None,
|
|
224
|
+
) -> th.Tensor:
|
|
225
|
+
"""
|
|
226
|
+
Override this method to implement X update algorithm.
|
|
227
|
+
Args:
|
|
228
|
+
g: (n_batch, n_atom, n_dim), the gradient of X at this step
|
|
229
|
+
g_old: (n_batch, n_atom, n_dim), the gradient of X at last step
|
|
230
|
+
p: (n_batch, n_atom, n_dim), the update direction of X at last step
|
|
231
|
+
X: (n_batch, n_atom, n_dim), the independent vars X.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
p: th.Tensor, the new update direction of X.
|
|
235
|
+
"""
|
|
236
|
+
p = self.__update_scheme(g, g_old, p, X, batch_scatter_)
|
|
237
|
+
|
|
238
|
+
return p
|
|
239
|
+
|
|
240
|
+
def _update_algo_param(
|
|
241
|
+
self,
|
|
242
|
+
select_mask: th.Tensor,
|
|
243
|
+
select_mask_short,
|
|
244
|
+
batch_scatter_indices,
|
|
245
|
+
g: th.Tensor,
|
|
246
|
+
g_old: th.Tensor,
|
|
247
|
+
p: th.Tensor,
|
|
248
|
+
displace: th.Tensor
|
|
249
|
+
) -> None:
|
|
250
|
+
"""
|
|
251
|
+
Override this method to update the parameters of X update algorithm i.e., self.iterform.
|
|
252
|
+
|
|
253
|
+
Returns: None
|
|
254
|
+
"""
|
|
255
|
+
pass
|
|
256
|
+
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FIRE Optimization Algorithm. Phys. Rev. Lett., 2006, 97:170201.
|
|
3
|
+
"""
|
|
4
|
+
import copy
|
|
5
|
+
# Copyright (c) 2024-2025.7.4, BUCToolkit.
|
|
6
|
+
# Authors: Pu Pengxin, Song Xin
|
|
7
|
+
# Version: 0.9a
|
|
8
|
+
# File: FIRE.py
|
|
9
|
+
# Environment: Python 3.12
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from typing import Iterable, Dict, Any, List, Literal, Optional, Callable, Sequence, Tuple # noqa: F401
|
|
13
|
+
|
|
14
|
+
import torch as th
|
|
15
|
+
from torch import nn
|
|
16
|
+
|
|
17
|
+
from BUCToolkit.utils._Element_info import MASS, N_MASS
|
|
18
|
+
from .._BaseOpt import _BaseOpt
|
|
19
|
+
from BUCToolkit.utils.index_ops import index_inner_product
|
|
20
|
+
|
|
21
|
+
os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FIRE(_BaseOpt):
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
E_threshold: float = 1e-3,
|
|
28
|
+
F_threshold: float = 0.05,
|
|
29
|
+
maxiter: int = 100,
|
|
30
|
+
steplength: float = 1.,
|
|
31
|
+
alpha: float = 0.1,
|
|
32
|
+
alpha_fac: float = 0.99,
|
|
33
|
+
fac_inc: float = 1.1,
|
|
34
|
+
fac_dec: float = 0.5,
|
|
35
|
+
N_min: int = 5,
|
|
36
|
+
output_file: str | None = None,
|
|
37
|
+
device: str | th.device = 'cpu',
|
|
38
|
+
verbose: int = 2,
|
|
39
|
+
_hold_samples: bool = False,
|
|
40
|
+
**kwargs
|
|
41
|
+
):
|
|
42
|
+
r"""
|
|
43
|
+
FIRE Algorithm for optimization.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
E_threshold: float, threshold of difference of func between 2 iterations.
|
|
47
|
+
F_threshold: float, threshold of gradient of func.
|
|
48
|
+
maxiter: int, the maximum iteration steps.
|
|
49
|
+
steplength: The initial step length, i.e. the BatchMD time step.
|
|
50
|
+
alpha: FIRE arg. alpha.
|
|
51
|
+
alpha_fac: FIRE arg. the factor of alpha change.
|
|
52
|
+
fac_inc: FIRE arg. the increment factor.
|
|
53
|
+
fac_dec: FIRE arg. the decrement factor.
|
|
54
|
+
N_min: FIRE arg. the minimum number that keeps steplength static.
|
|
55
|
+
device: The device that program runs on.
|
|
56
|
+
verbose: amount of print information.
|
|
57
|
+
_hold_samples: ONLY FOR SPECIAL USE (e.g., CI-NEB or DEBUG).
|
|
58
|
+
If True, FIRE optimizer will not remove any sample in a batch even if the sample has converged.
|
|
59
|
+
|
|
60
|
+
Method:
|
|
61
|
+
run: running the main optimization program.
|
|
62
|
+
"""
|
|
63
|
+
super().__init__(
|
|
64
|
+
'FIRE',
|
|
65
|
+
E_threshold,
|
|
66
|
+
F_threshold,
|
|
67
|
+
maxiter,
|
|
68
|
+
'None',
|
|
69
|
+
1,
|
|
70
|
+
1.,
|
|
71
|
+
1.,
|
|
72
|
+
1.,
|
|
73
|
+
use_bb = False,
|
|
74
|
+
output_file=output_file,
|
|
75
|
+
device = device,
|
|
76
|
+
verbose = verbose,
|
|
77
|
+
_hold_samples=_hold_samples
|
|
78
|
+
)
|
|
79
|
+
self.MAX_STEPLENGTH = th.scalar_tensor(10 * steplength, device=self.device)
|
|
80
|
+
self.FIRE_t_init = steplength
|
|
81
|
+
if not (0. < alpha_fac < 1.):
|
|
82
|
+
raise ValueError('alpha_fac must between 0 and 1.')
|
|
83
|
+
if not (0. < fac_dec < 1.):
|
|
84
|
+
raise ValueError('fac_dec must between 0 and 1.')
|
|
85
|
+
if fac_inc <= 1.:
|
|
86
|
+
raise ValueError('fac_inc must be greater than 1.')
|
|
87
|
+
|
|
88
|
+
self.alpha = alpha
|
|
89
|
+
self.alpha_fac = alpha_fac
|
|
90
|
+
self.fac_inc = fac_inc
|
|
91
|
+
self.fac_dec = fac_dec
|
|
92
|
+
self.N_min = N_min
|
|
93
|
+
|
|
94
|
+
self.v: th.Tensor | None = None # shape as X
|
|
95
|
+
self.v_: th.Tensor | None = None #
|
|
96
|
+
self.masses: th.Tensor | None = None # shape as X
|
|
97
|
+
self.masses_: th.Tensor | None = None #
|
|
98
|
+
self.t: th.Tensor | None = None # (1, sumN, 1) or (B, 1, 1)
|
|
99
|
+
self.t_: th.Tensor | None = None
|
|
100
|
+
self.a: th.Tensor | None = None # (1, sumN, 1) or (B, 1, 1)
|
|
101
|
+
self.n_count: th.Tensor | None = None # (1, sumN, 1) or (B, 1, 1)
|
|
102
|
+
|
|
103
|
+
def initialize_algo_param(self):
|
|
104
|
+
""" """
|
|
105
|
+
if self.is_concat_X:
|
|
106
|
+
self.t = th.full((1, self.n_atom, 1), self.FIRE_t_init, device=self.device)
|
|
107
|
+
self.a = th.full((1, self.n_atom, 1), self.alpha, device=self.device)
|
|
108
|
+
self.n_count = th.zeros((1, self.n_atom, 1), dtype=th.int, device=self.device)
|
|
109
|
+
else:
|
|
110
|
+
self.t = th.full((self.n_true_batch, 1, 1), self.FIRE_t_init, device=self.device)
|
|
111
|
+
self.a = th.full((self.n_true_batch, 1, 1), self.alpha, device=self.device)
|
|
112
|
+
self.n_count = th.zeros((self.n_true_batch, 1, 1), dtype=th.int, device=self.device)
|
|
113
|
+
|
|
114
|
+
def _update_algo_param(
|
|
115
|
+
self,
|
|
116
|
+
select_mask,
|
|
117
|
+
select_mask_short,
|
|
118
|
+
batch_scatter_indices,
|
|
119
|
+
X_grad_,
|
|
120
|
+
X_grad_old_,
|
|
121
|
+
p,
|
|
122
|
+
displace_
|
|
123
|
+
):
|
|
124
|
+
F = - X_grad_
|
|
125
|
+
if self.is_concat_X:
|
|
126
|
+
self.masses_ = self.masses[:, select_mask, :]
|
|
127
|
+
self.t_: th.Tensor = self.t[:, select_mask, :] # (1, sumN, 1)
|
|
128
|
+
a_ = self.a[:, select_mask, :] # (1, sumN, 1)
|
|
129
|
+
n_count_ = self.n_count[:, select_mask, :]
|
|
130
|
+
self.v_ = self.v[:, select_mask, :]
|
|
131
|
+
# (1, sumN, n_dim)
|
|
132
|
+
F_hat = F / th.sum(index_inner_product(
|
|
133
|
+
F,
|
|
134
|
+
F,
|
|
135
|
+
dim=1,
|
|
136
|
+
batch_indices=batch_scatter_indices
|
|
137
|
+
), dim=-1, keepdim=True).sqrt_().index_select(1, batch_scatter_indices)
|
|
138
|
+
# (1, sumN, 1)
|
|
139
|
+
momenta = th.sum(index_inner_product(
|
|
140
|
+
F,
|
|
141
|
+
self.v_,
|
|
142
|
+
dim=1,
|
|
143
|
+
batch_indices=batch_scatter_indices
|
|
144
|
+
), dim=-1, keepdim=True).index_select(1, batch_scatter_indices)
|
|
145
|
+
# (1, sumN, 1)
|
|
146
|
+
v_norm = th.sum(index_inner_product(
|
|
147
|
+
self.v_,
|
|
148
|
+
self.v_,
|
|
149
|
+
dim=1,
|
|
150
|
+
batch_indices=batch_scatter_indices
|
|
151
|
+
), dim=-1, keepdim=True).sqrt_().index_select(1, batch_scatter_indices)
|
|
152
|
+
# update velocity: v = v * (1 - a) + a * |v| * \hat{F}
|
|
153
|
+
F_hat.mul_(v_norm)
|
|
154
|
+
self.v_.mul_((1. - a_))
|
|
155
|
+
self.v_.addcmul_(a_, F_hat)
|
|
156
|
+
# if P > 0
|
|
157
|
+
n_count_ += th.where(momenta > 0., 1, -n_count_) # (1, sumN, 1)
|
|
158
|
+
is_ncount_gt_Nmin = n_count_ >= self.N_min
|
|
159
|
+
#
|
|
160
|
+
new_t_ = (self.t_ * self.fac_inc).clamp_max_(self.MAX_STEPLENGTH)
|
|
161
|
+
self.t_ = th.where(is_ncount_gt_Nmin, new_t_, self.t_)
|
|
162
|
+
a_ = th.where(is_ncount_gt_Nmin, (a_ * self.alpha_fac), a_)
|
|
163
|
+
# if P <= 0.
|
|
164
|
+
is_p_lt_0 = momenta <= 0.
|
|
165
|
+
self.t_ = th.where(is_p_lt_0, (self.t_ * self.fac_dec), self.t_)
|
|
166
|
+
self.v_.masked_fill_(
|
|
167
|
+
is_p_lt_0,
|
|
168
|
+
0.
|
|
169
|
+
)
|
|
170
|
+
a_.masked_fill_(is_p_lt_0, self.alpha)
|
|
171
|
+
# re-write
|
|
172
|
+
select_indices = th.where(select_mask)[0]
|
|
173
|
+
self.t.index_copy_(1, select_indices, self.t_)
|
|
174
|
+
self.a.index_copy_(1, select_indices, a_)
|
|
175
|
+
self.n_count.index_copy_(1, select_indices, n_count_)
|
|
176
|
+
|
|
177
|
+
else:
|
|
178
|
+
self.v_ = self.v[select_mask, ...]
|
|
179
|
+
self.masses_ = self.masses[select_mask, ...]
|
|
180
|
+
self.t_ = self.t[select_mask, ...]
|
|
181
|
+
a_ = self.a[select_mask, ...]
|
|
182
|
+
n_count_ = self.n_count[select_mask, ...]
|
|
183
|
+
|
|
184
|
+
F_hat = F / (th.linalg.norm(F, dim=(-2, -1), keepdim=True) + 1e-20)
|
|
185
|
+
# (n_batch, n_dim, n_atom) @ (n_batch, n_atom, n_dim) -> (n_batch, 1, 1)
|
|
186
|
+
momenta = th.sum(F * self.v_, dim=(-1, -2), keepdim=True)
|
|
187
|
+
# update velocity
|
|
188
|
+
v_norm = th.linalg.norm(self.v_, dim=(-2, -1), keepdim=True)
|
|
189
|
+
F_hat.mul_(v_norm)
|
|
190
|
+
self.v_.mul_((1. - a_))
|
|
191
|
+
self.v_.addcmul_(a_, F_hat)
|
|
192
|
+
# if P > 0.
|
|
193
|
+
n_count_ += th.where(momenta > 0., 1, -n_count_)
|
|
194
|
+
is_ncount_gt_Nmin = n_count_ >= self.N_min
|
|
195
|
+
new_t_ = (self.t_ * self.fac_inc).clamp_max_(self.MAX_STEPLENGTH)
|
|
196
|
+
self.t_ = th.where(is_ncount_gt_Nmin, new_t_, self.t_)
|
|
197
|
+
a_ = th.where(is_ncount_gt_Nmin, (a_ * self.alpha_fac), a_)
|
|
198
|
+
# if P <= 0.
|
|
199
|
+
is_p_lt_0 = momenta <= 0.
|
|
200
|
+
self.t_ = th.where(is_p_lt_0, (self.t_ * self.fac_dec), self.t_)
|
|
201
|
+
self.v_.masked_fill_(
|
|
202
|
+
is_p_lt_0,
|
|
203
|
+
0.
|
|
204
|
+
)
|
|
205
|
+
a_.masked_fill_(is_p_lt_0, self.alpha)
|
|
206
|
+
# re-write
|
|
207
|
+
select_indices = th.where(select_mask)[0]
|
|
208
|
+
self.t.index_copy_(0, select_indices, self.t_)
|
|
209
|
+
self.a.index_copy_(0, select_indices, a_)
|
|
210
|
+
self.n_count.index_copy_(0, select_indices, n_count_)
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
def _update_direction(
|
|
214
|
+
self,
|
|
215
|
+
g: th.Tensor,
|
|
216
|
+
g_old: th.Tensor,
|
|
217
|
+
p: th.Tensor,
|
|
218
|
+
X: th.Tensor,
|
|
219
|
+
batch_scatter_indices: th.Tensor | None,
|
|
220
|
+
) -> th.Tensor:
|
|
221
|
+
self.v_: th.Tensor
|
|
222
|
+
self.v_.addcdiv_(g * self.t_, self.masses_, value=-9.64853329045427e-3)
|
|
223
|
+
disp = self.v_ * self.t_
|
|
224
|
+
return disp
|
|
225
|
+
|
|
226
|
+
def _update_algo_batches(
|
|
227
|
+
self,
|
|
228
|
+
select_indices: th.Tensor,
|
|
229
|
+
select_indices_short: th.Tensor | None,
|
|
230
|
+
):
|
|
231
|
+
if self.is_concat_X:
|
|
232
|
+
self.v.index_copy_(1, select_indices, self.v_)
|
|
233
|
+
else:
|
|
234
|
+
self.v.index_copy_(0, select_indices, self.v_)
|
|
235
|
+
|
|
236
|
+
def run(
|
|
237
|
+
self,
|
|
238
|
+
func: Any | nn.Module,
|
|
239
|
+
X: th.Tensor,
|
|
240
|
+
grad_func: Any | nn.Module = None,
|
|
241
|
+
func_args: Sequence = tuple(),
|
|
242
|
+
func_kwargs: Dict | None = None,
|
|
243
|
+
grad_func_args: Sequence = tuple(),
|
|
244
|
+
grad_func_kwargs: Dict | None = None,
|
|
245
|
+
is_grad_func_contain_y: bool = True,
|
|
246
|
+
require_grad: bool = False,
|
|
247
|
+
output_grad: bool = False,
|
|
248
|
+
fixed_atom_tensor: Optional[th.Tensor] = None,
|
|
249
|
+
batch_indices: None | List[int] | Tuple[int, ...] | th.Tensor = None,
|
|
250
|
+
elements: List[List[str | int]] | None = None,
|
|
251
|
+
) -> Tuple[th.Tensor, th.Tensor] | Tuple[th.Tensor, th.Tensor, th.Tensor]:
|
|
252
|
+
r"""
|
|
253
|
+
Run the Conjugate gradient
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
func: the main function of instantiated torch.nn.Module class.
|
|
257
|
+
X: Tensor[n_batch, n_atom, 3], the optimization variables (e.g., atom coordinates) that input to func.
|
|
258
|
+
grad_func: user-defined function that grad_func(X, ...) returns the func's gradient at X. if None, grad_func(X, ...) = th.autograd.grad(func(X, ...), X).
|
|
259
|
+
func_args: optional, other input of func.
|
|
260
|
+
func_kwargs: optional, other input of func.
|
|
261
|
+
grad_func_args: optional, other input of grad_func.
|
|
262
|
+
grad_func_kwargs: optional, other input of grad_func.
|
|
263
|
+
is_grad_func_contain_y: bool, if True, grad_func contains output of func followed by X i.e., grad = grad_func(X, y, ...), else grad = grad_func(X, ...)
|
|
264
|
+
require_grad: bool, if True, autograd will be turned on for func(X, *func_args, **func_kwargs) calculation.
|
|
265
|
+
output_grad: bool, whether output gradient of last step.
|
|
266
|
+
fixed_atom_tensor: Optional[th.Tensor], the indices of X that fixed. An integer tensor with the same shape as X where 1 is for free and 0 is for fixation.
|
|
267
|
+
batch_indices: Sequence | th.Tensor | np.ndarray | None, the split points for given X, Element_list & V_init, must be 1D integer array_like.
|
|
268
|
+
the format of batch_indices is the same as `split_size_or_sections` in torch.split:
|
|
269
|
+
batch_indices = (n1, n2, ..., nN) will split X, Element_list & V_init into N parts, and ith parts has ni atoms. sum(n1, ..., nN) = X.shape[1]
|
|
270
|
+
elements: Optional[List[List[str | int]]], the Element of each given atom in X.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
min func: Tensor(n_batch, ), the minimum of func.
|
|
274
|
+
argmin func: Tensor(X.shape), the X corresponds to min func.
|
|
275
|
+
grad of argmin func: Tensor(X.shape), only output when `output_grad` == True. The gradient of X corresponding to minimum.
|
|
276
|
+
"""
|
|
277
|
+
# manage Atomic Type & Masses
|
|
278
|
+
if len(X.shape) == 2:
|
|
279
|
+
X = X.unsqueeze(0)
|
|
280
|
+
elif len(X.shape) != 3:
|
|
281
|
+
raise ValueError(f'`X` must be 2D or 3D, but got shape [{X.shape}]')
|
|
282
|
+
if elements is None:
|
|
283
|
+
self.masses = th.ones_like(X)
|
|
284
|
+
elif isinstance(elements, Sequence):
|
|
285
|
+
masses = list()
|
|
286
|
+
for _Elem in elements:
|
|
287
|
+
masses.append([MASS[__elem] if isinstance(__elem, str) else N_MASS[__elem] for __elem in _Elem])
|
|
288
|
+
masses = th.tensor(masses, dtype=th.float32, device=self.device)
|
|
289
|
+
self.masses = masses.unsqueeze(-1).expand_as(X).clone() # (n_batch, n_atom, n_dim)
|
|
290
|
+
else:
|
|
291
|
+
raise TypeError(f'Expected masses is a Sequence[Sequence[...]], but occurred {type(elements)}.')
|
|
292
|
+
self.v = th.zeros_like(X, device=self.device)
|
|
293
|
+
|
|
294
|
+
_results = super().run(
|
|
295
|
+
func,
|
|
296
|
+
X,
|
|
297
|
+
grad_func,
|
|
298
|
+
func_args,
|
|
299
|
+
func_kwargs,
|
|
300
|
+
grad_func_args,
|
|
301
|
+
grad_func_kwargs,
|
|
302
|
+
is_grad_func_contain_y=is_grad_func_contain_y,
|
|
303
|
+
require_grad=require_grad,
|
|
304
|
+
output_grad=output_grad,
|
|
305
|
+
fixed_atom_tensor=fixed_atom_tensor,
|
|
306
|
+
batch_indices=batch_indices,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
return _results
|
|
310
|
+
|