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,702 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025.7.4, BUCToolkit.
|
|
2
|
+
# Authors: Pu Pengxin, Song Xin
|
|
3
|
+
# Version: 0.9a
|
|
4
|
+
# File: _BaseOpt.py
|
|
5
|
+
# Environment: Python 3.12
|
|
6
|
+
import logging
|
|
7
|
+
import sys
|
|
8
|
+
from itertools import accumulate
|
|
9
|
+
from typing import Dict, Any, Literal, Optional, Sequence, Tuple, List, Callable
|
|
10
|
+
import time
|
|
11
|
+
import warnings
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
import torch as th
|
|
17
|
+
from torch import nn
|
|
18
|
+
from BUCToolkit.BatchOptim._utils._line_search import LineSearch
|
|
19
|
+
from BUCToolkit.BatchOptim._utils._warnings import NotConvergeWarning
|
|
20
|
+
from BUCToolkit.utils.function_utils import preload_func
|
|
21
|
+
from BUCToolkit.utils.setup_loggers import BaseIO
|
|
22
|
+
from BUCToolkit.utils._print_formatter import FLOAT_ARRAY_FORMAT, SCIENTIFIC_ARRAY_FORMAT, STRING_ARRAY_FORMAT
|
|
23
|
+
from BUCToolkit.utils.index_ops import index_reduce, index_inner_product
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _BaseOpt(BaseIO, ABC):
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
iter_scheme: str,
|
|
30
|
+
E_threshold: float = 1e-3,
|
|
31
|
+
F_threshold: float = 0.05,
|
|
32
|
+
maxiter: int = 100,
|
|
33
|
+
linesearch: Literal['Backtrack', 'B', 'Wolfe', 'W', 'MT', 'EXACT', 'None', 'N'] = 'Backtrack',
|
|
34
|
+
linesearch_maxiter: int = 10,
|
|
35
|
+
linesearch_thres: float = 0.02,
|
|
36
|
+
linesearch_factor: float = 0.6,
|
|
37
|
+
steplength: float = 0.5,
|
|
38
|
+
use_bb: bool = True,
|
|
39
|
+
output_file: str | None = None,
|
|
40
|
+
device: str | th.device = 'cpu',
|
|
41
|
+
verbose: int = 2,
|
|
42
|
+
_hold_samples: bool = False,
|
|
43
|
+
) -> None:
|
|
44
|
+
r"""
|
|
45
|
+
A Base Framework of Algorithm for optimization.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
E_threshold: float, threshold of difference of func between 2 iterations.
|
|
49
|
+
F_threshold: float, threshold of gradient of func.
|
|
50
|
+
maxiter: int, max iterations.
|
|
51
|
+
linesearch: Scheme of linesearch.
|
|
52
|
+
'Backtrack': Backtrack line search to satisfy Armijo's condition.
|
|
53
|
+
'B': Alias for 'Backtrack'.
|
|
54
|
+
'Wolfe': More-Thuente algorithm line search to satisfy Wolfe-Powell (weak Wolfe) condition.
|
|
55
|
+
'W': Alias for 'Wolfe'.
|
|
56
|
+
'MT': Alias for 'Wolfe'.
|
|
57
|
+
'EXACT': Exact line search by Brent algorithm.
|
|
58
|
+
'BRENT': Alias for 'BRENT'.
|
|
59
|
+
'None': No line search. Directly return input steplength.
|
|
60
|
+
linesearch_maxiter: Max iterations for linesearch.
|
|
61
|
+
linesearch_thres: Threshold for linesearch. Only for "Golden" and "Newton".
|
|
62
|
+
linesearch_factor: A factor in linesearch. Shrinkage factor for "Backtrack", scaling factor in interval search for "Golden" and line steplength for "Newton".
|
|
63
|
+
steplength: The initial step length.
|
|
64
|
+
use_bb: whether to use Barzilai-Borwein steplength (BB1 or long BB) as initial steplength instead of fixed one.
|
|
65
|
+
output_file: the file to dump trajectory. if None, nothing will be dumped.
|
|
66
|
+
device: The device that program runs on.
|
|
67
|
+
verbose: amount of print information.
|
|
68
|
+
_hold_samples: ONLY FOR SPECIAL USES (e.g., CI-NEB or DEBUG).
|
|
69
|
+
If True, optimizer will not remove any sample in a batch even if the sample has converged.
|
|
70
|
+
|
|
71
|
+
Method:
|
|
72
|
+
run: running the main optimization program.
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
warnings.filterwarnings('always', category=NotConvergeWarning)
|
|
76
|
+
warnings.filterwarnings('always', )
|
|
77
|
+
|
|
78
|
+
self.iterform = iter_scheme
|
|
79
|
+
self.n_true_batch = None
|
|
80
|
+
|
|
81
|
+
self.linesearch: str = linesearch
|
|
82
|
+
self.steplength: float = steplength
|
|
83
|
+
self.linesearch_maxiter = linesearch_maxiter
|
|
84
|
+
self.linesearch_thres = linesearch_thres
|
|
85
|
+
self.linesearch_factor = linesearch_factor
|
|
86
|
+
self.use_bb = use_bb
|
|
87
|
+
self._line_search = LineSearch(
|
|
88
|
+
linesearch,
|
|
89
|
+
maxiter=linesearch_maxiter,
|
|
90
|
+
thres=linesearch_thres,
|
|
91
|
+
factor=linesearch_factor,
|
|
92
|
+
verbose=verbose,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self.E_threshold = E_threshold
|
|
96
|
+
self.F_threshold = F_threshold
|
|
97
|
+
self.maxiter = maxiter
|
|
98
|
+
self.n_batch, self.n_atom, self.n_dim = None, None, None
|
|
99
|
+
self.converge_mask = None # To record the batch which has converged and not update.
|
|
100
|
+
self.is_concat_X = False # whether the output of `func` was concatenated.
|
|
101
|
+
|
|
102
|
+
self._hold_samples = _hold_samples
|
|
103
|
+
self.device = device
|
|
104
|
+
self.verbose = verbose
|
|
105
|
+
|
|
106
|
+
# logger
|
|
107
|
+
super().__init__(output_file)
|
|
108
|
+
self.init_logger('Main.OPT')
|
|
109
|
+
|
|
110
|
+
def _update_batch(self, mask: th.Tensor, func_args: Tuple, func_kwargs: Dict, grad_func_args: Tuple, grad_func_kwargs: Dict):
|
|
111
|
+
"""
|
|
112
|
+
Default update method for the input of func if the func has non-opt variables, i.e., the identical transform.
|
|
113
|
+
Args:
|
|
114
|
+
mask:
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
return func_args, func_kwargs, grad_func_args, grad_func_kwargs
|
|
120
|
+
|
|
121
|
+
def set_batch_updater(
|
|
122
|
+
self,
|
|
123
|
+
method: Callable[[th.Tensor, Tuple|None, Dict|None, Tuple|None, Dict|None], Tuple[Tuple, Dict, Tuple, Dict]] | None,
|
|
124
|
+
line_search_method: Callable[[th.Tensor, Tuple|None, Dict|None, Tuple|None, Dict|None], Tuple[Tuple, Dict, Tuple, Dict]] | None = None,
|
|
125
|
+
) -> None:
|
|
126
|
+
"""
|
|
127
|
+
Set a method to update the taget function when variables change.
|
|
128
|
+
If input Callables, these Callables receive a mask tensor of shape (n_batch, )
|
|
129
|
+
that only selects the `True` part to input to the function, the old
|
|
130
|
+
`func_args`, `func_kwargs`, `grad_func_args`, and `grad_func_kwargs`,
|
|
131
|
+
returns the corresponding masked new `func_args`, `func_kwargs`, `grad_func_args`, and `grad_func_kwargs`.
|
|
132
|
+
If input None, self._hold_samples will be set to True that toggles off the dynamic removal.
|
|
133
|
+
|
|
134
|
+
This method is used to dynamically 'remove' the samples which have been converged in a batch to avoid
|
|
135
|
+
redundant calculation of converged samples.
|
|
136
|
+
|
|
137
|
+
Default transform is identical transform (i.e., do nothing)
|
|
138
|
+
|
|
139
|
+
`method` is for main loop update; and `line_search_method` is for line search subroutine update;
|
|
140
|
+
Args:
|
|
141
|
+
method: Callable(
|
|
142
|
+
mask: Tensor, func_args: Tuple, func_kwargs: Dict, grad_func_args: Tuple, grad_func_kwargs: Dict
|
|
143
|
+
) -> Tuple[Tuple, Dict, Tuple, Dict], the method of updating function arguments for a mask.
|
|
144
|
+
line_search_method: as the same use of `method`, but for line search subroutines.
|
|
145
|
+
|
|
146
|
+
Returns: None
|
|
147
|
+
"""
|
|
148
|
+
if method is None:
|
|
149
|
+
self._hold_samples = True
|
|
150
|
+
elif callable(method):
|
|
151
|
+
self._update_batch = method
|
|
152
|
+
else:
|
|
153
|
+
raise TypeError(f'`method` must be a callable, but {type(method)} is not.')
|
|
154
|
+
self._line_search.set_batch_updater(line_search_method)
|
|
155
|
+
|
|
156
|
+
def run(
|
|
157
|
+
self,
|
|
158
|
+
func: Any | nn.Module,
|
|
159
|
+
X: th.Tensor,
|
|
160
|
+
grad_func: Any | nn.Module = None,
|
|
161
|
+
func_args: Tuple[Any] = tuple(),
|
|
162
|
+
func_kwargs: Dict | None = None,
|
|
163
|
+
grad_func_args: Tuple[Any] = tuple(),
|
|
164
|
+
grad_func_kwargs: Dict | None = None,
|
|
165
|
+
is_grad_func_contain_y: bool = True,
|
|
166
|
+
require_grad: bool = False,
|
|
167
|
+
output_grad: bool = False,
|
|
168
|
+
fixed_atom_tensor: Optional[th.Tensor] = None,
|
|
169
|
+
batch_indices: None | List[int] | Tuple[int, ...] | th.Tensor = None,
|
|
170
|
+
) -> Tuple[th.Tensor, th.Tensor] | Tuple[th.Tensor, th.Tensor, th.Tensor]:
|
|
171
|
+
"""
|
|
172
|
+
Run the Optimization Algorithm.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
func: the main function of instantiated torch.nn.Module class.
|
|
176
|
+
X: Tensor[n_batch, n_atom, 3], the atom coordinates that input to func.
|
|
177
|
+
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).
|
|
178
|
+
func_args: optional, other input of func.
|
|
179
|
+
func_kwargs: optional, other input of func.
|
|
180
|
+
grad_func_args: optional, other input of grad_func.
|
|
181
|
+
grad_func_kwargs: optional, other input of grad_func.
|
|
182
|
+
is_grad_func_contain_y: bool, if True, grad_func contains output of func followed by X
|
|
183
|
+
i.e., grad = grad_func(X, y, *grad_func_args, **grad_func_kwargs), else grad = grad_func(X, *grad_func_args, **grad_func_kwargs)
|
|
184
|
+
require_grad: bool, if True, autograd will be turned on for func(X, *func_args, **func_kwargs) calculation.
|
|
185
|
+
output_grad: bool, whether output gradient of last step.
|
|
186
|
+
fixed_atom_tensor: Optional[th.Tensor], the indices of X that fixed.
|
|
187
|
+
batch_indices: Sequence | th.Tensor | np.ndarray | None, the split points for given X, Element_list & V_init, must be 1D integer array_like.
|
|
188
|
+
the format of batch_indices is the same as `split_size_or_sections` in torch.split:
|
|
189
|
+
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]
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
min func: Tensor(n_batch, ), the minimum of func.
|
|
193
|
+
argmin func: Tensor(X.shape), the X corresponds to min func.
|
|
194
|
+
grad of argmin func: Tensor(X.shape), only output when `output_grad` == True. The gradient of X corresponding to minimum.
|
|
195
|
+
"""
|
|
196
|
+
t_main = time.perf_counter()
|
|
197
|
+
if func_kwargs is None:
|
|
198
|
+
func_kwargs = dict()
|
|
199
|
+
if grad_func_kwargs is None:
|
|
200
|
+
grad_func_kwargs = dict()
|
|
201
|
+
# Check batch indices; irregular batch
|
|
202
|
+
if isinstance(X, th.Tensor):
|
|
203
|
+
if len(X.shape) == 2:
|
|
204
|
+
X = X.unsqueeze(0)
|
|
205
|
+
elif len(X.shape) != 3:
|
|
206
|
+
raise ValueError(f'`X` must be 2D or 3D, but got shape [{X.shape}]')
|
|
207
|
+
n_batch, n_atom, n_dim = X.shape
|
|
208
|
+
else:
|
|
209
|
+
raise TypeError(f'`X` must be torch.Tensor, but occurred {type(X)}.')
|
|
210
|
+
if batch_indices is not None:
|
|
211
|
+
if n_batch != 1:
|
|
212
|
+
raise RuntimeError(f'If batch_indices was specified, the 1st dimension of X must be 1 instead of {n_batch}.')
|
|
213
|
+
if isinstance(batch_indices, (th.Tensor, np.ndarray)):
|
|
214
|
+
batch_indices = batch_indices.tolist()
|
|
215
|
+
elif not isinstance(batch_indices, (Tuple, List)):
|
|
216
|
+
raise TypeError(f'Invalid type of batch_indices {type(batch_indices)}. '
|
|
217
|
+
f'It must be Sequence[int] | th.Tensor | np.ndarray | None')
|
|
218
|
+
for i in batch_indices: assert isinstance(i, int), f'All elements in batch_indices must be int, but occurred {type(i)}'
|
|
219
|
+
n_true_batch = len(batch_indices) # the true batch size for irregular batches
|
|
220
|
+
batch_slice_indx = [0] + list(accumulate(batch_indices)) # convert n_atom of each batch into split point of each batch
|
|
221
|
+
self.batch_tensor = th.as_tensor(batch_indices, device=self.device)
|
|
222
|
+
self.batch_scatter = th.repeat_interleave(
|
|
223
|
+
th.arange(0, len(batch_indices), dtype=th.int64, device=self.device),
|
|
224
|
+
self.batch_tensor,
|
|
225
|
+
dim=0
|
|
226
|
+
)
|
|
227
|
+
# steplength
|
|
228
|
+
steplength_tensor = th.full(
|
|
229
|
+
(1, n_true_batch, 1), fill_value=self.steplength, device=self.device, dtype=th.float32
|
|
230
|
+
) # (n_batch, sumN, 1), initial step length
|
|
231
|
+
else:
|
|
232
|
+
n_true_batch = n_batch
|
|
233
|
+
# steplength
|
|
234
|
+
steplength_tensor = th.full(
|
|
235
|
+
(n_batch, 1, 1), fill_value=self.steplength, device=self.device, dtype=th.float32
|
|
236
|
+
) # (n_batch, sumN, 1), initial step length
|
|
237
|
+
# initialize vars
|
|
238
|
+
self.n_true_batch = n_true_batch
|
|
239
|
+
maxiter = self.maxiter
|
|
240
|
+
n_batch, n_atom, n_dim = X.shape
|
|
241
|
+
self.n_batch, self.n_atom, self.n_dim = n_batch, n_atom, n_dim
|
|
242
|
+
p = th.zeros_like(X) # like X, the previous direction
|
|
243
|
+
self.converge_mask = None # (n_true_batch, )
|
|
244
|
+
X_grad_old = th.full_like(X, 1e-20, dtype=th.float32, device=self.device) # like X, initial old grad.
|
|
245
|
+
displace = th.full_like(X_grad_old, 0.) # like X, the X displacement
|
|
246
|
+
# handle grad func
|
|
247
|
+
if grad_func is None:
|
|
248
|
+
is_grad_func_contain_y = True
|
|
249
|
+
require_grad = True
|
|
250
|
+
def grad_func_(y, x, grad_shape=None):
|
|
251
|
+
if grad_shape is None:
|
|
252
|
+
grad_shape = th.ones_like(y)
|
|
253
|
+
_g = th.autograd.grad(y, x, grad_shape)
|
|
254
|
+
return _g[0]
|
|
255
|
+
else:
|
|
256
|
+
grad_func_ = grad_func
|
|
257
|
+
self._line_search.require_grad = require_grad # set linear search
|
|
258
|
+
# Selective dyamics
|
|
259
|
+
if fixed_atom_tensor is None:
|
|
260
|
+
atom_masks = th.ones_like(X, device=self.device)
|
|
261
|
+
elif fixed_atom_tensor.shape == X.shape:
|
|
262
|
+
atom_masks = fixed_atom_tensor.to(self.device)
|
|
263
|
+
else:
|
|
264
|
+
raise RuntimeError(f'The shape of fixed_atom_tensor (shape: {fixed_atom_tensor.shape}) does not match X (shape: {X.shape}).')
|
|
265
|
+
#atom_masks = atom_masks.flatten(-2, -1).unsqueeze(-1) # (n_batch, n_atom*n_dim, 1)
|
|
266
|
+
# other check
|
|
267
|
+
if (not isinstance(maxiter, int)) or (maxiter <= 0):
|
|
268
|
+
raise ValueError(f'Invalid value of maxiter: {maxiter}. It would be an integer greater than 0.')
|
|
269
|
+
|
|
270
|
+
# set variables device
|
|
271
|
+
func = preload_func(func, self.device)
|
|
272
|
+
|
|
273
|
+
if isinstance(grad_func_, nn.Module):
|
|
274
|
+
grad_func_ = grad_func_.to(self.device)
|
|
275
|
+
X = X.detach()
|
|
276
|
+
X = X.to(self.device)
|
|
277
|
+
|
|
278
|
+
# initialize
|
|
279
|
+
############################## BATCHED ALGORITHM ###################################
|
|
280
|
+
# variables with '_' refer to the dynamically changed variables during iteration,
|
|
281
|
+
# and they will in-placed copy into origin variables (i.e., without '_') at the end
|
|
282
|
+
# of each iteration to update data.
|
|
283
|
+
#
|
|
284
|
+
####################################################################################
|
|
285
|
+
is_main_loop_converge = False
|
|
286
|
+
t_st = time.perf_counter()
|
|
287
|
+
# Section: initialize
|
|
288
|
+
#ptlist = [X[:, None, :, 0].numpy(force=True)] # for converged samp, stop calc., test <<<
|
|
289
|
+
if self.verbose:
|
|
290
|
+
self.logger.info('-' * 100)
|
|
291
|
+
self.logger.info(f'Iteration Scheme: {self.iterform}')
|
|
292
|
+
self.logger.info('-' * 100)
|
|
293
|
+
# MAIN LOOP
|
|
294
|
+
with th.no_grad():
|
|
295
|
+
with th.set_grad_enabled(require_grad):
|
|
296
|
+
X.requires_grad_(require_grad)
|
|
297
|
+
energies: th.Tensor = func(X, *func_args, **func_kwargs)
|
|
298
|
+
# note: irregular tensor regularized by concat. thus n_batch of X shown as 1, but y has shape of the true batch size.
|
|
299
|
+
if energies.shape[0] != self.n_batch:
|
|
300
|
+
if batch_indices is None:
|
|
301
|
+
raise ValueError(
|
|
302
|
+
f"batch indices is None "
|
|
303
|
+
f"while shape of model output ({energies.shape}) does not match batch size ({self.n_batch})."
|
|
304
|
+
)
|
|
305
|
+
if energies.shape[0] != n_true_batch:
|
|
306
|
+
raise ValueError(f"shape of output ({energies.shape}) does not match given batch indices")
|
|
307
|
+
self.is_concat_X = (batch_indices is not None)
|
|
308
|
+
# calc. grad
|
|
309
|
+
if is_grad_func_contain_y:
|
|
310
|
+
X_grad = grad_func_(energies, X, *grad_func_args, **grad_func_kwargs)
|
|
311
|
+
else:
|
|
312
|
+
X_grad = grad_func_(X, *grad_func_args, **grad_func_kwargs)
|
|
313
|
+
if X_grad.shape != X.shape:
|
|
314
|
+
raise RuntimeError(f'X_grad ({X_grad.shape}) and X ({X.shape}) have different shapes.')
|
|
315
|
+
energies = energies.detach()
|
|
316
|
+
energies_old = th.full_like(energies, th.inf)
|
|
317
|
+
X_grad = X_grad.detach()
|
|
318
|
+
X_grad.mul_(atom_masks)
|
|
319
|
+
X = X.detach()
|
|
320
|
+
# Section: initialize custom algorithm state.
|
|
321
|
+
self.initialize_algo_param()
|
|
322
|
+
# cache for dynamically changed batch indices due to convergence, avoiding reallocate mem.
|
|
323
|
+
batch_tensor_indx_cache = th.arange(0, len(self.batch_tensor), dtype=th.int64, device=self.device)
|
|
324
|
+
for numit in range(maxiter):
|
|
325
|
+
# Calc. Criteria
|
|
326
|
+
E_diff = energies - energies_old
|
|
327
|
+
E_eps = th.abs(E_diff) # (n_batch, )
|
|
328
|
+
energies_old.copy_(energies)
|
|
329
|
+
# manage the irregular tensors
|
|
330
|
+
if self.is_concat_X:
|
|
331
|
+
# (1, n_batch*n_atom, 3)
|
|
332
|
+
F_eps = index_reduce(
|
|
333
|
+
th.max(th.abs(X_grad[0]), dim=-1).values, self.batch_scatter, 0, 'amax', -1.
|
|
334
|
+
)
|
|
335
|
+
f_converge = F_eps < self.F_threshold
|
|
336
|
+
converge_mask = (E_eps < self.E_threshold) & f_converge # (n_true_batch, ), to stop the update of converged samples.
|
|
337
|
+
converge_check = converge_mask
|
|
338
|
+
self.converge_mask = converge_check
|
|
339
|
+
converge_str = converge_check.numpy(force=True)
|
|
340
|
+
converge_mask = converge_mask.reshape(1, -1, 1)[:, self.batch_scatter, ...] # (1, n_batch*n_atom, 3)
|
|
341
|
+
else:
|
|
342
|
+
F_eps = th.amax(th.abs(X_grad), dim=(-2, -1)) # (n_batch, n_atom, 3) -> (n_batch)
|
|
343
|
+
f_converge = (F_eps < self.F_threshold).reshape(-1, 1, 1)
|
|
344
|
+
converge_mask = (E_eps < self.E_threshold).reshape(-1, 1, 1) & f_converge # To stop the update of converged samples.
|
|
345
|
+
converge_check = converge_mask[:, 0, 0]
|
|
346
|
+
self.converge_mask = converge_check
|
|
347
|
+
converge_str = (converge_mask[:, 0, 0]).numpy(force=True)
|
|
348
|
+
|
|
349
|
+
# Print information / Verbose
|
|
350
|
+
if self.verbose > 0:
|
|
351
|
+
self.logger.info(f"ITERATION {numit:>5d}\n "
|
|
352
|
+
f"MAD_energies: {np.array2string(E_diff.numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n "
|
|
353
|
+
f"MAX_F: {np.array2string(F_eps.numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n "
|
|
354
|
+
f"Energies: {np.array2string(energies.numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n "
|
|
355
|
+
f"Converged: {np.array2string(converge_str, **STRING_ARRAY_FORMAT)}\n "
|
|
356
|
+
f"TIME: {time.perf_counter() - t_st:>6.4f} s")
|
|
357
|
+
if self.verbose > 1:
|
|
358
|
+
# split batches if specified batch
|
|
359
|
+
if batch_indices is not None:
|
|
360
|
+
X_np = X.numpy(force=True)
|
|
361
|
+
X_tup = np.split(X_np, batch_slice_indx[1:-1], axis=1)
|
|
362
|
+
if self.verbose > 2:
|
|
363
|
+
F_np = (- X_grad).numpy(force=True)
|
|
364
|
+
F_tup = np.split(F_np, batch_slice_indx[1:-1], axis=1)
|
|
365
|
+
else:
|
|
366
|
+
X_tup = (X.numpy(force=True),)
|
|
367
|
+
if self.verbose > 2:
|
|
368
|
+
F_tup = (- X_grad.numpy(force=True),)
|
|
369
|
+
self.logger.info(f" Coordinates:\n")
|
|
370
|
+
X_str = [
|
|
371
|
+
np.array2string(xi, **FLOAT_ARRAY_FORMAT).replace("[", " ").replace("]", " ")
|
|
372
|
+
for xi in X_tup
|
|
373
|
+
]
|
|
374
|
+
[self.logger.info(f'{x_str}\n') for x_str in X_str]
|
|
375
|
+
if self.verbose > 2:
|
|
376
|
+
self.logger.info(f" Forces:\n")
|
|
377
|
+
X_str = [
|
|
378
|
+
np.array2string(xi, **FLOAT_ARRAY_FORMAT).replace("[", " ").replace("]", " ")
|
|
379
|
+
for xi in F_tup
|
|
380
|
+
]
|
|
381
|
+
[self.logger.info(f'{x_str}\n') for x_str in X_str]
|
|
382
|
+
|
|
383
|
+
# judge thres
|
|
384
|
+
if th.all(self.converge_mask):
|
|
385
|
+
is_main_loop_converge = True
|
|
386
|
+
break
|
|
387
|
+
|
|
388
|
+
#g: th.Tensor = th.flatten(X_grad, 1, 2).unsqueeze(-1).contiguous() # (n_batch, n_atom*3, 1)
|
|
389
|
+
# Section: update batch
|
|
390
|
+
if not self._hold_samples:
|
|
391
|
+
func_args_, func_kwargs_, grad_func_args_, grad_func_kwargs_ = self._update_batch(
|
|
392
|
+
~converge_check,
|
|
393
|
+
func_args,
|
|
394
|
+
func_kwargs,
|
|
395
|
+
grad_func_args,
|
|
396
|
+
grad_func_kwargs
|
|
397
|
+
)
|
|
398
|
+
if self.is_concat_X:
|
|
399
|
+
select_mask = ~(converge_mask[0, :, 0])
|
|
400
|
+
select_mask_short = ~converge_check
|
|
401
|
+
energies_ = energies[select_mask_short]
|
|
402
|
+
X_grad_ = X_grad[:, select_mask, :]
|
|
403
|
+
X_grad_old_ = X_grad_old[:, select_mask, :]
|
|
404
|
+
p_ = p[:, select_mask, :]
|
|
405
|
+
X_ = X[:, select_mask, :]
|
|
406
|
+
displace_ = displace[:, select_mask, :]
|
|
407
|
+
atom_masks_ = atom_masks[:, select_mask, :]
|
|
408
|
+
steplength_ = steplength_tensor[:, select_mask_short, :]
|
|
409
|
+
batch_tensor_ = self.batch_tensor[select_mask_short]
|
|
410
|
+
batch_scatter_ = th.repeat_interleave(
|
|
411
|
+
batch_tensor_indx_cache[:len(batch_tensor_)],
|
|
412
|
+
batch_tensor_,
|
|
413
|
+
dim=0
|
|
414
|
+
)
|
|
415
|
+
else:
|
|
416
|
+
select_mask = ~converge_check
|
|
417
|
+
energies_ = energies[select_mask]
|
|
418
|
+
X_grad_ = X_grad[select_mask, ...]
|
|
419
|
+
X_grad_old_ = X_grad_old[select_mask, ...]
|
|
420
|
+
p_ = p[select_mask, ...]
|
|
421
|
+
X_ = X[select_mask, ...]
|
|
422
|
+
atom_masks_ = atom_masks[select_mask, ...]
|
|
423
|
+
displace_ = displace[select_mask, ...]
|
|
424
|
+
steplength_ = steplength_tensor[select_mask, ...]
|
|
425
|
+
batch_tensor_ = None
|
|
426
|
+
batch_scatter_ = None
|
|
427
|
+
else:
|
|
428
|
+
func_args_, func_kwargs_, grad_func_args_, grad_func_kwargs_ = (
|
|
429
|
+
func_args,
|
|
430
|
+
func_kwargs,
|
|
431
|
+
grad_func_args,
|
|
432
|
+
grad_func_kwargs
|
|
433
|
+
)
|
|
434
|
+
select_mask = ~(converge_mask[0, :, 0])
|
|
435
|
+
select_mask_short = ~converge_check
|
|
436
|
+
energies_ = energies
|
|
437
|
+
X_grad_ = X_grad
|
|
438
|
+
X_grad_old_ = X_grad_old
|
|
439
|
+
p_ = p
|
|
440
|
+
X_ = X
|
|
441
|
+
displace_ = displace
|
|
442
|
+
atom_masks_ = atom_masks
|
|
443
|
+
steplength_ = steplength_tensor
|
|
444
|
+
batch_tensor_ = self.batch_tensor
|
|
445
|
+
batch_scatter_ = self.batch_scatter
|
|
446
|
+
|
|
447
|
+
# Section: update algo. parameters.
|
|
448
|
+
self._update_algo_param(
|
|
449
|
+
select_mask,
|
|
450
|
+
select_mask_short,
|
|
451
|
+
batch_scatter_,
|
|
452
|
+
X_grad_,
|
|
453
|
+
X_grad_old_,
|
|
454
|
+
p_,
|
|
455
|
+
displace_
|
|
456
|
+
)
|
|
457
|
+
self.select_mask = select_mask
|
|
458
|
+
|
|
459
|
+
t_st = time.perf_counter()
|
|
460
|
+
# Section: search directions
|
|
461
|
+
p_ = self._update_direction(
|
|
462
|
+
X_grad_,
|
|
463
|
+
X_grad_old_,
|
|
464
|
+
p_,
|
|
465
|
+
X_,
|
|
466
|
+
batch_scatter_,
|
|
467
|
+
) # (n_batch, n_atom, n_dim)
|
|
468
|
+
# use BB steplength_tensor
|
|
469
|
+
if self.use_bb:
|
|
470
|
+
g_go = X_grad_ - X_grad_old_ # (n_batch, n_atom, n_dim)
|
|
471
|
+
if self.is_concat_X:
|
|
472
|
+
_steplength_ = th.sum(index_inner_product(
|
|
473
|
+
displace_,
|
|
474
|
+
displace_,
|
|
475
|
+
dim=1,
|
|
476
|
+
batch_indices=batch_scatter_
|
|
477
|
+
), dim=-1, keepdim=True) / th.sum(index_inner_product(
|
|
478
|
+
displace_,
|
|
479
|
+
g_go,
|
|
480
|
+
dim=1,
|
|
481
|
+
batch_indices=batch_scatter_
|
|
482
|
+
), dim=-1, keepdim=True) # BB1, (1, B, 1)
|
|
483
|
+
_steplength_ = th.where(
|
|
484
|
+
(_steplength_ < 2. * self.steplength) & (_steplength_ > 1e-4),
|
|
485
|
+
_steplength_,
|
|
486
|
+
steplength_
|
|
487
|
+
)
|
|
488
|
+
else:
|
|
489
|
+
# (n_batch, 1, n_atom*n_dim) @ (n_batch, n_atom*n_dim, 1) =
|
|
490
|
+
_steplength_ = th.sum(
|
|
491
|
+
displace_ * displace_, dim=(-2, -1), keepdim=True
|
|
492
|
+
) / th.sum(
|
|
493
|
+
displace_ * g_go, dim=(-2, -1), keepdim=True
|
|
494
|
+
) # BB1
|
|
495
|
+
_steplength_ = th.where(
|
|
496
|
+
(_steplength_ < 2. * self.steplength) * (_steplength_ > 1e-4),
|
|
497
|
+
_steplength_,
|
|
498
|
+
steplength_
|
|
499
|
+
)
|
|
500
|
+
else:
|
|
501
|
+
_steplength_ = steplength_
|
|
502
|
+
# Section: search step length -> steplength_tensor: (n_batch, 1, 1)
|
|
503
|
+
steplength_: th.Tensor = self._line_search.run(
|
|
504
|
+
func,
|
|
505
|
+
grad_func_,
|
|
506
|
+
X_,
|
|
507
|
+
energies_,
|
|
508
|
+
X_grad_,
|
|
509
|
+
p_,
|
|
510
|
+
_steplength_,
|
|
511
|
+
is_grad_func_contain_y,
|
|
512
|
+
require_grad,
|
|
513
|
+
func_args=func_args_,
|
|
514
|
+
func_kwargs=func_kwargs_,
|
|
515
|
+
grad_func_args=grad_func_args_,
|
|
516
|
+
grad_func_kwargs=grad_func_kwargs_,
|
|
517
|
+
batch_indices=batch_tensor_
|
|
518
|
+
)
|
|
519
|
+
# update X
|
|
520
|
+
if self.is_concat_X:
|
|
521
|
+
alpha = steplength_.index_select(1, batch_scatter_)
|
|
522
|
+
else:
|
|
523
|
+
alpha = steplength_
|
|
524
|
+
displace_ = alpha * p_ # (n_batch, 1, 1) * (n_batch, n_atom, n_dim) or (1, sumN, 1) * (1, sumN, n_dim)
|
|
525
|
+
X_.add_(displace_) # (n_batch, n_atom, 3) + (n_batch, n_atom, 3)
|
|
526
|
+
# update old grad
|
|
527
|
+
X_grad_old_ = X_grad_ # (n_batch, n_atom, n_dim)
|
|
528
|
+
# calc. new energy & grad.
|
|
529
|
+
with th.set_grad_enabled(require_grad):
|
|
530
|
+
X_.requires_grad_(require_grad)
|
|
531
|
+
energies_: th.Tensor = func(X_, *func_args_, **func_kwargs_)
|
|
532
|
+
if is_grad_func_contain_y:
|
|
533
|
+
X_grad_ = grad_func_(energies_, X_, *grad_func_args_, **grad_func_kwargs_)
|
|
534
|
+
else:
|
|
535
|
+
X_grad_ = grad_func_(X_, *grad_func_args_, **grad_func_kwargs_)
|
|
536
|
+
energies_ = energies_.detach()
|
|
537
|
+
X_grad_ = X_grad_.detach()
|
|
538
|
+
X_grad_.mul_(atom_masks_)
|
|
539
|
+
X_.detach_()
|
|
540
|
+
|
|
541
|
+
# Section: rewrite. update origin variables
|
|
542
|
+
if not self._hold_samples:
|
|
543
|
+
if self.is_concat_X:
|
|
544
|
+
select_indices = th.where(select_mask)[0]
|
|
545
|
+
select_indices_short = th.where(select_mask_short)[0]
|
|
546
|
+
energies.index_copy_(0, select_indices_short, energies_)
|
|
547
|
+
X_grad.index_copy_(1, select_indices, X_grad_)
|
|
548
|
+
X_grad_old.index_copy_(1, select_indices, X_grad_old_)
|
|
549
|
+
p.index_copy_(1, select_indices, p_)
|
|
550
|
+
X.index_copy_(1, select_indices, X_)
|
|
551
|
+
displace.index_copy_(1, select_indices, displace_)
|
|
552
|
+
#atom_masks.index_copy_(1, select_indices, atom_masks_)
|
|
553
|
+
#steplength_tensor.index_copy_(1, select_indices_short, steplength_)
|
|
554
|
+
|
|
555
|
+
else:
|
|
556
|
+
select_indices = th.where(select_mask)[0]
|
|
557
|
+
energies.index_copy_(0, select_indices, energies_)
|
|
558
|
+
X_grad.index_copy_(0, select_indices, X_grad_)
|
|
559
|
+
X_grad_old.index_copy_(0, select_indices, X_grad_old_)
|
|
560
|
+
p.index_copy_(0, select_indices, p_)
|
|
561
|
+
X.index_copy_(0, select_indices, X_)
|
|
562
|
+
displace.index_copy_(0, select_indices, displace_)
|
|
563
|
+
#atom_masks.index_copy_(0, select_indices, atom_masks_)
|
|
564
|
+
#steplength_tensor.index_copy_(0, select_indices, steplength_)
|
|
565
|
+
else:
|
|
566
|
+
#select_indices = th.where(select_mask)[0]
|
|
567
|
+
#select_indices_short = th.where(select_mask_short)[0]
|
|
568
|
+
energies = energies_
|
|
569
|
+
X_grad = X_grad_
|
|
570
|
+
X_grad_old = X_grad_old_
|
|
571
|
+
p = p_
|
|
572
|
+
X = X_
|
|
573
|
+
displace = displace_
|
|
574
|
+
# Section: update batch information of algos if necessary
|
|
575
|
+
self._update_algo_batches(select_indices, select_indices_short)
|
|
576
|
+
# Check NaN
|
|
577
|
+
#if not th.all(energies.isfinite()): raise RuntimeError(f'NaN Occurred in output: {energies}')
|
|
578
|
+
|
|
579
|
+
#ptlist.append(X[:, None, :, 0].numpy(force=True)) # test <<<
|
|
580
|
+
|
|
581
|
+
if self.verbose > 0:
|
|
582
|
+
if is_main_loop_converge:
|
|
583
|
+
self.logger.info(
|
|
584
|
+
'-' * 100 + f'\nAll Structures were Converged.\nMAIN LOOP Done. Total Time: {time.perf_counter() - t_main:<.4f} s\n'
|
|
585
|
+
)
|
|
586
|
+
else:
|
|
587
|
+
self.logger.info(
|
|
588
|
+
'-' * 100 + f'\nSome Structures were NOT Converged yet!\nMAIN LOOP Done. Total Time: {time.perf_counter() - t_main:<.4f} s\n'
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
if self.verbose < 2: # verbose = 1, brief mode only output last step coords.
|
|
592
|
+
# split batches if specified batch
|
|
593
|
+
if batch_indices is not None:
|
|
594
|
+
X_np = X.numpy(force=True)
|
|
595
|
+
X_tup = np.split(X_np, batch_slice_indx[1:-1], axis=1)
|
|
596
|
+
F_np = (- X_grad).numpy(force=True)
|
|
597
|
+
F_tup = np.split(F_np, batch_slice_indx[1:-1], axis=1)
|
|
598
|
+
else:
|
|
599
|
+
X_tup = (X.numpy(force=True),)
|
|
600
|
+
F_tup = (- X_grad.numpy(force=True),)
|
|
601
|
+
self.logger.info(f" Final Coordinates:\n")
|
|
602
|
+
X_str = [
|
|
603
|
+
np.array2string(xi, **FLOAT_ARRAY_FORMAT).replace("[", " ").replace("]", " ")
|
|
604
|
+
for xi in X_tup
|
|
605
|
+
]
|
|
606
|
+
[self.logger.info(f'{x_str}\n') for x_str in X_str]
|
|
607
|
+
self.logger.info(f" Final Forces:\n")
|
|
608
|
+
X_str = [
|
|
609
|
+
np.array2string(xi, **FLOAT_ARRAY_FORMAT).replace("[", " ").replace("]", " ")
|
|
610
|
+
for xi in F_tup
|
|
611
|
+
]
|
|
612
|
+
[self.logger.info(f'{x_str}\n') for x_str in X_str]
|
|
613
|
+
else:
|
|
614
|
+
if not is_main_loop_converge: warnings.warn('Some Structures were NOT Converged yet!',
|
|
615
|
+
NotConvergeWarning)
|
|
616
|
+
# release
|
|
617
|
+
|
|
618
|
+
# output
|
|
619
|
+
if output_grad:
|
|
620
|
+
return energies, X, X_grad
|
|
621
|
+
else:
|
|
622
|
+
return energies, X #, ptlist # test <<<
|
|
623
|
+
|
|
624
|
+
@abstractmethod
|
|
625
|
+
def initialize_algo_param(self):
|
|
626
|
+
"""
|
|
627
|
+
Override this method to initialize attribute variables for self._update_direction.
|
|
628
|
+
Examples:
|
|
629
|
+
(BFGS algo.)
|
|
630
|
+
# descent direction
|
|
631
|
+
self.p = 0.
|
|
632
|
+
# Initial quasi-inverse Hessian Matrix (n_batch, n_atom*n_dim, n_atom*n_dim)
|
|
633
|
+
self.H_inv = (th.eye(n_atom * n_dim, device=self.device).unsqueeze(0)).expand(n_batch, -1, -1)
|
|
634
|
+
# prepared identity matrix
|
|
635
|
+
self.Ident = (th.eye(n_atom * n_dim, device=self.device).unsqueeze(0)).expand(n_batch, -1, -1)
|
|
636
|
+
|
|
637
|
+
Returns: None
|
|
638
|
+
"""
|
|
639
|
+
raise NotImplementedError
|
|
640
|
+
|
|
641
|
+
@abstractmethod
|
|
642
|
+
def _update_direction(
|
|
643
|
+
self,
|
|
644
|
+
g: th.Tensor,
|
|
645
|
+
g_old: th.Tensor,
|
|
646
|
+
p: th.Tensor,
|
|
647
|
+
X: th.Tensor,
|
|
648
|
+
batch_scatter_indices: th.Tensor | None,
|
|
649
|
+
) -> th.Tensor:
|
|
650
|
+
"""
|
|
651
|
+
Override this method to implement X update algorithm.
|
|
652
|
+
Args:
|
|
653
|
+
g: (n_batch, n_atom, n_dim), the gradient of X at this step
|
|
654
|
+
g_old: (n_batch, n_atom, n_dim), the gradient of X at last step
|
|
655
|
+
p: (n_batch, n_atom, n_dim), the update direction of X at last step
|
|
656
|
+
X: (n_batch, n_atom, n_dim), the independent vars X.
|
|
657
|
+
batch_scatter_indices: the batch indices. See `_update_algo_param`.
|
|
658
|
+
|
|
659
|
+
Returns:
|
|
660
|
+
p: th.Tensor, the new update direction of X.
|
|
661
|
+
"""
|
|
662
|
+
raise NotImplementedError
|
|
663
|
+
|
|
664
|
+
@abstractmethod
|
|
665
|
+
def _update_algo_param(
|
|
666
|
+
self,
|
|
667
|
+
select_mask: th.Tensor,
|
|
668
|
+
select_mask_short: th.Tensor | None,
|
|
669
|
+
batch_scatter_indices: th.Tensor | None,
|
|
670
|
+
g: th.Tensor,
|
|
671
|
+
g_old: th.Tensor,
|
|
672
|
+
p: th.Tensor,
|
|
673
|
+
displace: th.Tensor
|
|
674
|
+
) -> None:
|
|
675
|
+
"""
|
|
676
|
+
Override this method to update the parameters of X update algorithm i.e., self.iterform.
|
|
677
|
+
Args:
|
|
678
|
+
select_mask: (n_batch, ), the mask of batch that converged. Only the position of `True` would be selected to calculate.
|
|
679
|
+
batch_scatter_indices: (sumN, ), the batch scatter indices of sample that not yet converged.
|
|
680
|
+
format: [0, 0, ..., 0, 1, 1, ..., 1, ..., N-1], where the same number means the line in the same sample.
|
|
681
|
+
If samples are in a regular batch, it would be set to None.
|
|
682
|
+
g: (n_batch, n_atom, n_dim), the gradient of X at this step
|
|
683
|
+
g_old: (n_batch, n_atom, n_dim), the gradient of X at last step
|
|
684
|
+
p: (n_batch, n_atom, n_dim), the update direction of X at last step
|
|
685
|
+
displace: (n_batch, n_atom, n_dim), the displacement of X at this step. displace = step-length * p
|
|
686
|
+
|
|
687
|
+
Returns: None
|
|
688
|
+
"""
|
|
689
|
+
raise NotImplementedError
|
|
690
|
+
|
|
691
|
+
def _update_algo_batches(
|
|
692
|
+
self,
|
|
693
|
+
select_indices: th.Tensor,
|
|
694
|
+
select_indices_short: th.Tensor | None,
|
|
695
|
+
):
|
|
696
|
+
"""
|
|
697
|
+
Optional.
|
|
698
|
+
Override this method to update the batch information of algorithm parameters, i.e., self.iterform.
|
|
699
|
+
Returns:
|
|
700
|
+
|
|
701
|
+
"""
|
|
702
|
+
pass
|