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,863 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025.7.4, BUCToolkit.
|
|
2
|
+
# Authors: Pu Pengxin, Song Xin
|
|
3
|
+
# Version: 0.9a
|
|
4
|
+
# File: _BaseMC.py
|
|
5
|
+
# Environment: Python 3.12
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import math
|
|
9
|
+
import sys
|
|
10
|
+
from itertools import accumulate
|
|
11
|
+
from typing import Dict, Any, Optional, Sequence, Tuple, List
|
|
12
|
+
import time
|
|
13
|
+
import threading, queue
|
|
14
|
+
import warnings
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
import torch as th
|
|
19
|
+
from torch import nn
|
|
20
|
+
|
|
21
|
+
from BUCToolkit.BatchOptim._utils._warnings import NotConvergeWarning
|
|
22
|
+
from BUCToolkit.utils._Element_info import ATOMIC_SYMBOL
|
|
23
|
+
from BUCToolkit.utils._print_formatter import FLOAT_ARRAY_FORMAT, SCIENTIFIC_ARRAY_FORMAT
|
|
24
|
+
from BUCToolkit.utils.setup_loggers import has_any_handler, clear_all_handlers, BaseIO
|
|
25
|
+
from BUCToolkit.utils.index_ops import index_reduce
|
|
26
|
+
from BUCToolkit.utils.function_utils import preload_func
|
|
27
|
+
from BUCToolkit.BatchStructures.StructuresIO import structures_io_dumper
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _BaseMC(BaseIO):
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
iter_scheme: str,
|
|
34
|
+
maxiter: int = 100,
|
|
35
|
+
output_file: str|None = None,
|
|
36
|
+
output_structures_per_step: int = 1,
|
|
37
|
+
device: str | th.device = 'cpu',
|
|
38
|
+
verbose: int = 0,
|
|
39
|
+
is_compile: bool = False,
|
|
40
|
+
compile_kwargs: Dict|None = None,
|
|
41
|
+
) -> None:
|
|
42
|
+
r"""
|
|
43
|
+
A Base Framework of Algorithm for optimization.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
maxiter: int, max iterations.
|
|
47
|
+
output_file: the path to the binary file that stores trajectories.
|
|
48
|
+
output_structures_per_step: int, output structures per output_structures_per_step steps.
|
|
49
|
+
device: The device that program runs on.
|
|
50
|
+
verbose: amount of print information.
|
|
51
|
+
is_compile: whether to use jit to compile the update function or not.
|
|
52
|
+
compile_kwargs: keyword arguments passed to compile. Only work when is_compile is True.
|
|
53
|
+
|
|
54
|
+
Method:
|
|
55
|
+
run: running the main optimization program.
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
self.batch_scatter = None
|
|
59
|
+
self.batch_tensor = None
|
|
60
|
+
self.atom_masks = None
|
|
61
|
+
self.T_now = None
|
|
62
|
+
self.is_compile = is_compile
|
|
63
|
+
self.compile_kwargs = compile_kwargs if compile_kwargs is not None else dict()
|
|
64
|
+
|
|
65
|
+
self.iterform = str(iter_scheme)
|
|
66
|
+
self.verbose = int(verbose)
|
|
67
|
+
self.device = device
|
|
68
|
+
self.maxiter = int(maxiter)
|
|
69
|
+
self.output_structures_per_step = int(output_structures_per_step)
|
|
70
|
+
|
|
71
|
+
self.n_batch, self.n_atom, self.n_dim = None, None, None
|
|
72
|
+
self.is_concat_X = False # whether the output of `func` was concatenated.
|
|
73
|
+
self.scatter_dim_out_size = None
|
|
74
|
+
|
|
75
|
+
# An inner attr that lets the dumper do not close after `self.run`.
|
|
76
|
+
# It is used to contiguously run within a loop.
|
|
77
|
+
# Adv. API `MonteCarlo` turns on it.
|
|
78
|
+
self._HOLD_DUMPER = False
|
|
79
|
+
|
|
80
|
+
# logger
|
|
81
|
+
super().__init__(output_file)
|
|
82
|
+
self.init_logger('Main.MC')
|
|
83
|
+
|
|
84
|
+
def _do_async_dump(self, q: queue.Queue):
|
|
85
|
+
"""
|
|
86
|
+
A backend thread to async. dump
|
|
87
|
+
Args:
|
|
88
|
+
q: queue to receive data. contains: tuple of (dumper, event, *data)
|
|
89
|
+
|
|
90
|
+
Returns: None
|
|
91
|
+
|
|
92
|
+
"""
|
|
93
|
+
while True:
|
|
94
|
+
try:
|
|
95
|
+
dumper, event, _print_E, _print_X = q.get()
|
|
96
|
+
if dumper is None:
|
|
97
|
+
break
|
|
98
|
+
# event: th.cuda.Event, ensure copy done
|
|
99
|
+
event.synchronize()
|
|
100
|
+
dumper.step(
|
|
101
|
+
_print_E.numpy(),
|
|
102
|
+
_print_X.numpy(),
|
|
103
|
+
)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
self.logger.error(f"Error: Failed to dump data due to \"{e}\"")
|
|
106
|
+
|
|
107
|
+
def _do_async_print(self, q: queue.Queue):
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
# Print information / Verbose
|
|
114
|
+
while True:
|
|
115
|
+
numit, _print_E, _print_dE, batch_indices, X, batch_slice_indx = q.get()
|
|
116
|
+
if numit is None:
|
|
117
|
+
break
|
|
118
|
+
if self.verbose > 0:
|
|
119
|
+
self.logger.info(
|
|
120
|
+
f"ITERATION {numit:>5d}\n "
|
|
121
|
+
f"delta E: {np.array2string(_print_dE.numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n "
|
|
122
|
+
f"Accepted: {np.array2string(self.is_accept.numpy(force=True))}\n "
|
|
123
|
+
f"Energies: {np.array2string(_print_E.numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n "
|
|
124
|
+
f"Temperature: {self.T_now:.3e}\n "
|
|
125
|
+
# f"TIME: {time.perf_counter() - t_step:>6.4f} s"
|
|
126
|
+
)
|
|
127
|
+
# t_step = time.perf_counter()
|
|
128
|
+
if self.verbose > 1:
|
|
129
|
+
# split batches if specified batch
|
|
130
|
+
if batch_indices is not None:
|
|
131
|
+
X_np = X.numpy(force=True)
|
|
132
|
+
X_tup = np.split(X_np, batch_slice_indx[1:-1], axis=1)
|
|
133
|
+
else:
|
|
134
|
+
X_tup = (X.numpy(force=True),)
|
|
135
|
+
self.logger.info(f" Coordinates:\n")
|
|
136
|
+
X_str = [
|
|
137
|
+
np.array2string(xi, **FLOAT_ARRAY_FORMAT).replace("[", " ").replace("]", " ")
|
|
138
|
+
for xi in X_tup
|
|
139
|
+
]
|
|
140
|
+
[self.logger.info(f'{x_str}\n') for x_str in X_str]
|
|
141
|
+
|
|
142
|
+
def calc_shape_center(self, Xr, ) -> th.Tensor:
|
|
143
|
+
"""
|
|
144
|
+
calculate shape center based on Xr.
|
|
145
|
+
Args:
|
|
146
|
+
Xr:
|
|
147
|
+
|
|
148
|
+
Returns: shape_center
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
if self.batch_scatter is None:
|
|
152
|
+
# initialize topologie
|
|
153
|
+
shape_center = th.mean(Xr, dim=1, keepdim=True) # (n_batch, 1, n_dim)
|
|
154
|
+
else:
|
|
155
|
+
# initialize topologie
|
|
156
|
+
shape_center = index_reduce(Xr, self.batch_scatter, dim=1, out_size=self.scatter_dim_out_size) / self.batch_tensor # (1, n_batch, n_dim)
|
|
157
|
+
shape_center = shape_center.index_select(1, self.batch_scatter) # (1, sumN*A, n_dim)
|
|
158
|
+
|
|
159
|
+
return shape_center
|
|
160
|
+
|
|
161
|
+
def run(
|
|
162
|
+
self,
|
|
163
|
+
func: Any | nn.Module,
|
|
164
|
+
X: th.Tensor,
|
|
165
|
+
Element_list: List[List[str]] | List[List[int]] | None = None,
|
|
166
|
+
Cell_vector: th.Tensor | None = None,
|
|
167
|
+
func_args: Sequence = tuple(),
|
|
168
|
+
func_kwargs: Dict | None = None,
|
|
169
|
+
batch_indices: None | List[int] | Tuple[int, ...] | th.Tensor = None,
|
|
170
|
+
fixed_atom_tensor: Optional[th.Tensor] = None,
|
|
171
|
+
move_to_center_freq: int = -1
|
|
172
|
+
):
|
|
173
|
+
try:
|
|
174
|
+
if th.device(self.device).type == "cuda":
|
|
175
|
+
self.__run_on_cuda(
|
|
176
|
+
func,
|
|
177
|
+
X,
|
|
178
|
+
Element_list,
|
|
179
|
+
Cell_vector,
|
|
180
|
+
func_args,
|
|
181
|
+
func_kwargs,
|
|
182
|
+
batch_indices,
|
|
183
|
+
fixed_atom_tensor,
|
|
184
|
+
move_to_center_freq
|
|
185
|
+
)
|
|
186
|
+
elif th.device(self.device).type == "cpu":
|
|
187
|
+
self.__run_on_cpu(
|
|
188
|
+
func,
|
|
189
|
+
X,
|
|
190
|
+
Element_list,
|
|
191
|
+
Cell_vector,
|
|
192
|
+
func_args,
|
|
193
|
+
func_kwargs,
|
|
194
|
+
batch_indices,
|
|
195
|
+
fixed_atom_tensor,
|
|
196
|
+
move_to_center_freq
|
|
197
|
+
)
|
|
198
|
+
else:
|
|
199
|
+
raise NotImplementedError(F"device {self.device} not supported.")
|
|
200
|
+
finally:
|
|
201
|
+
self.dumper.close()
|
|
202
|
+
|
|
203
|
+
def __run_on_cuda(
|
|
204
|
+
self,
|
|
205
|
+
func: Any | nn.Module,
|
|
206
|
+
X: th.Tensor,
|
|
207
|
+
Element_list: List[List[str]] | List[List[int]] | None = None,
|
|
208
|
+
Cell_vector: th.Tensor | None = None,
|
|
209
|
+
func_args: Sequence = tuple(),
|
|
210
|
+
func_kwargs: Dict | None = None,
|
|
211
|
+
batch_indices: None | List[int] | Tuple[int, ...] | th.Tensor = None,
|
|
212
|
+
fixed_atom_tensor: Optional[th.Tensor] = None,
|
|
213
|
+
move_to_center_freq: int = -1
|
|
214
|
+
) -> None:
|
|
215
|
+
"""
|
|
216
|
+
Run MC Algorithm.
|
|
217
|
+
|
|
218
|
+
Parameters:
|
|
219
|
+
func: the main function of instantiated torch.nn.Module class.
|
|
220
|
+
X: Tensor[n_batch, n_atom, 3], the atom coordinates that input to func.
|
|
221
|
+
Cell_vector: Tensor[n_batch, 3, 3], the cell vectors. Only for logging out information, not involves in calculation.
|
|
222
|
+
Element_list: The list of elements/atom types. Only for logging out information, not involves in calculation.
|
|
223
|
+
if None, all atomic number of points will be labeled to 0.
|
|
224
|
+
func_args: optional, other input of func.
|
|
225
|
+
func_kwargs: optional, other input of func.
|
|
226
|
+
fixed_atom_tensor: Optional[th.Tensor], the indices of X that fixed.
|
|
227
|
+
batch_indices: the split points for given X, Element_list & V_init, must be 1D integer array_like.
|
|
228
|
+
the format of batch_indices is same as `split_size_or_sections` in torch.split:
|
|
229
|
+
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]
|
|
230
|
+
move_to_center_freq: the period of translating coordinates and velocities of atoms into the shape center & 0.
|
|
231
|
+
Note that is not MASS CENTER but shape center, different from MD.
|
|
232
|
+
if `move_to_center_freq` <= 0, the translation would not apply.
|
|
233
|
+
|
|
234
|
+
Return: None
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
t_main = time.perf_counter()
|
|
238
|
+
if func_kwargs is None:
|
|
239
|
+
func_kwargs = dict()
|
|
240
|
+
# Check batch indices; irregular batch
|
|
241
|
+
if isinstance(X, th.Tensor):
|
|
242
|
+
if len(X.shape) == 2:
|
|
243
|
+
X = X.unsqueeze(0)
|
|
244
|
+
elif len(X.shape) != 3:
|
|
245
|
+
raise ValueError(f'`X` must be 2D or 3D, but got shape [{X.shape}]')
|
|
246
|
+
n_batch, n_atom, n_dim = X.shape
|
|
247
|
+
else:
|
|
248
|
+
raise TypeError(f'`X` must be torch.Tensor, but occurred {type(X)}.')
|
|
249
|
+
if batch_indices is not None:
|
|
250
|
+
if n_batch != 1:
|
|
251
|
+
raise RuntimeError(f'If batch_indices was specified, the 1st dimension of X must be 1 instead of {n_batch}.')
|
|
252
|
+
if isinstance(batch_indices, (th.Tensor, np.ndarray)):
|
|
253
|
+
batch_indices = batch_indices.tolist()
|
|
254
|
+
elif not isinstance(batch_indices, (Tuple, List)):
|
|
255
|
+
raise TypeError(f'Invalid type of batch_indices {type(batch_indices)}. '
|
|
256
|
+
f'It must be Sequence[int] | th.Tensor | np.ndarray | None')
|
|
257
|
+
for i in batch_indices: assert isinstance(i, int), f'All elements in batch_indices must be int, but occurred {type(i)}'
|
|
258
|
+
n_true_batch = len(batch_indices)
|
|
259
|
+
batch_slice_indx = [0] + list(accumulate(batch_indices)) # convert n_atom of each batch into split point of each batch
|
|
260
|
+
self.batch_tensor = th.as_tensor(batch_indices, device=self.device)
|
|
261
|
+
self.batch_scatter = th.repeat_interleave(
|
|
262
|
+
th.arange(0, len(batch_indices), dtype=th.int64, device=self.device),
|
|
263
|
+
self.batch_tensor,
|
|
264
|
+
dim=0
|
|
265
|
+
)
|
|
266
|
+
self.scatter_dim_out_size = self.batch_scatter.max().item() + 1
|
|
267
|
+
else:
|
|
268
|
+
n_true_batch = n_batch
|
|
269
|
+
self.batch_scatter = None
|
|
270
|
+
self.batch_tensor = None
|
|
271
|
+
batch_slice_indx = None
|
|
272
|
+
# initialize vars
|
|
273
|
+
maxiter = self.maxiter
|
|
274
|
+
self.n_batch, self.n_atom, self.n_dim = n_batch, n_atom, n_dim
|
|
275
|
+
if Element_list is not None:
|
|
276
|
+
atomic_numbers = list()
|
|
277
|
+
for _Elem in Element_list:
|
|
278
|
+
atomic_numbers.append([ATOMIC_SYMBOL[__elem] if isinstance(__elem, str) else int(__elem) for __elem in _Elem])
|
|
279
|
+
else:
|
|
280
|
+
atomic_numbers = [[0]*self.n_atom]*self.n_batch
|
|
281
|
+
if not isinstance(move_to_center_freq, int):
|
|
282
|
+
raise TypeError(f'`move_to_center_freq` must be an integer, but got {type(move_to_center_freq)}.')
|
|
283
|
+
is_fix_mass_center = (move_to_center_freq > 0)
|
|
284
|
+
|
|
285
|
+
# Selective dyamics
|
|
286
|
+
if fixed_atom_tensor is None:
|
|
287
|
+
self.atom_masks = th.ones_like(X, device=self.device)
|
|
288
|
+
elif fixed_atom_tensor.shape == X.shape:
|
|
289
|
+
self.atom_masks = fixed_atom_tensor.to(self.device)
|
|
290
|
+
else:
|
|
291
|
+
raise RuntimeError(f'The shape of fixed_atom_tensor (shape: {fixed_atom_tensor.shape}) does not match X (shape: {X.shape}).')
|
|
292
|
+
# other check
|
|
293
|
+
if (not isinstance(maxiter, int)) or (maxiter <= 0):
|
|
294
|
+
raise ValueError(f'Invalid value of maxiter: {maxiter}. It would be an integer greater than 0.')
|
|
295
|
+
|
|
296
|
+
# set variables device
|
|
297
|
+
func = preload_func(func, self.device)
|
|
298
|
+
X = X.to(self.device)
|
|
299
|
+
|
|
300
|
+
# calc. freedom degree
|
|
301
|
+
if batch_indices is None:
|
|
302
|
+
_free_degree = X.shape[1] * n_dim
|
|
303
|
+
if is_fix_mass_center:
|
|
304
|
+
_free_degree -= 3
|
|
305
|
+
self.free_degree = th.full((n_batch,), _free_degree, dtype=th.int64, device=self.device)
|
|
306
|
+
n_reduce = th.where(th.abs(self.atom_masks) < 1e-6, 1, 0).sum(dim=(-2, -1)) # (n_batch, )
|
|
307
|
+
self.free_degree -= n_reduce
|
|
308
|
+
else:
|
|
309
|
+
self.free_degree = self.batch_tensor * n_dim # (n_batch, )
|
|
310
|
+
if is_fix_mass_center:
|
|
311
|
+
self.free_degree -= 3
|
|
312
|
+
n_reduce_tensor = th.where(th.abs(self.atom_masks) < 1e-6, 1, 0).sum(dim=-1)
|
|
313
|
+
n_reduce = index_reduce(n_reduce_tensor, self.batch_scatter, dim=1, out_size=self.scatter_dim_out_size).squeeze(0) # (n_batch, )
|
|
314
|
+
self.free_degree -= n_reduce
|
|
315
|
+
|
|
316
|
+
# initialize the dumper
|
|
317
|
+
X_arr = X.numpy(force=True)
|
|
318
|
+
_x_dtype = X_arr.dtype.str
|
|
319
|
+
atom_masks_arr = self.atom_masks.numpy(force=True).astype(_x_dtype)
|
|
320
|
+
_num_dump = math.ceil(self.maxiter / self.output_structures_per_step)
|
|
321
|
+
# pre-allocate the tensors
|
|
322
|
+
# _buf_* is the vars on GPU that apply copy.
|
|
323
|
+
# _print_* is the vars on CPU that async. do D2H for _buf_*.
|
|
324
|
+
if batch_indices is not None:
|
|
325
|
+
_print_E = th.empty(len(batch_indices), device='cpu', dtype=th.float32, pin_memory=True)
|
|
326
|
+
_buf_E = th.empty(len(batch_indices), device=self.device, dtype=th.float32)
|
|
327
|
+
_print_dE = th.zeros(len(batch_indices), device='cpu', dtype=th.float32, pin_memory=True)
|
|
328
|
+
_buf_dE = th.empty(len(batch_indices), device=self.device, dtype=th.float32)
|
|
329
|
+
else:
|
|
330
|
+
_print_E = th.empty(n_batch, device='cpu', dtype=th.float32, pin_memory=True)
|
|
331
|
+
_buf_E = th.empty(n_batch, device=self.device, dtype=th.float32)
|
|
332
|
+
_print_dE = th.zeros(n_batch, device='cpu', dtype=th.float32, pin_memory=True)
|
|
333
|
+
_buf_dE = th.empty(n_batch, device=self.device, dtype=th.float32)
|
|
334
|
+
_print_X = th.empty_like(X, device='cpu', dtype=th.float32, pin_memory=True)
|
|
335
|
+
_buf_X = th.empty_like(X, device=self.device, dtype=th.float32)
|
|
336
|
+
# write head information.
|
|
337
|
+
if Cell_vector is None:
|
|
338
|
+
Cell_vector = np.zeros((n_true_batch, 3, 3), dtype=np.float32)
|
|
339
|
+
elif isinstance(Cell_vector, th.Tensor):
|
|
340
|
+
Cell_vector = Cell_vector.numpy(force=True)
|
|
341
|
+
elif not isinstance(Cell_vector, np.ndarray):
|
|
342
|
+
Cell_vector = np.asarray(Cell_vector)
|
|
343
|
+
if self.batch_tensor is not None:
|
|
344
|
+
self.dumper.start_from_arrays(
|
|
345
|
+
1,
|
|
346
|
+
self.batch_tensor.numpy(force=True), # batch indices
|
|
347
|
+
Cell_vector, # cell
|
|
348
|
+
np.asarray(atomic_numbers), # element type / atomic number
|
|
349
|
+
atom_masks_arr, # fixation mask
|
|
350
|
+
)
|
|
351
|
+
self.dumper.step(
|
|
352
|
+
self.batch_tensor.numpy(force=True),
|
|
353
|
+
Cell_vector,
|
|
354
|
+
np.asarray(atomic_numbers),
|
|
355
|
+
atom_masks_arr
|
|
356
|
+
)
|
|
357
|
+
else:
|
|
358
|
+
self.dumper.start_from_arrays(
|
|
359
|
+
1,
|
|
360
|
+
Cell_vector,
|
|
361
|
+
np.asarray(atomic_numbers), # element type / atomic number
|
|
362
|
+
atom_masks_arr, # fixation mask
|
|
363
|
+
)
|
|
364
|
+
self.dumper.step(
|
|
365
|
+
Cell_vector,
|
|
366
|
+
np.asarray(atomic_numbers),
|
|
367
|
+
atom_masks_arr
|
|
368
|
+
)
|
|
369
|
+
# continue to write main data
|
|
370
|
+
self.dumper.start_from_arrays(
|
|
371
|
+
_num_dump,
|
|
372
|
+
_print_E.numpy(),
|
|
373
|
+
_print_X.numpy(),
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# initialize
|
|
377
|
+
t_step = time.perf_counter()
|
|
378
|
+
self.initialize_algo_param()
|
|
379
|
+
#ptlist = [X[:, None, :, 0].numpy(force=True)] # for converged samp, stop calc., test <<<
|
|
380
|
+
if self.verbose:
|
|
381
|
+
self.logger.info('-' * 100)
|
|
382
|
+
self.logger.info(f'Iteration Scheme: {self.iterform}')
|
|
383
|
+
self.logger.info('-' * 100)
|
|
384
|
+
# MAIN LOOP
|
|
385
|
+
with th.no_grad():
|
|
386
|
+
energies: th.Tensor = func(X, *func_args, **func_kwargs)
|
|
387
|
+
delta_E: th.Tensor = th.zeros_like(energies)
|
|
388
|
+
# note: irregular tensor regularized by concat. thus n_batch of X shown as 1, but y has shape of the true batch size.
|
|
389
|
+
if energies.shape[0] != self.n_batch:
|
|
390
|
+
if batch_indices is None:
|
|
391
|
+
raise ValueError(
|
|
392
|
+
f"batch indices is None "
|
|
393
|
+
f"while shape of model output ({energies.shape}) does not match batch size ({self.n_batch})."
|
|
394
|
+
)
|
|
395
|
+
if energies.shape[0] != n_true_batch:
|
|
396
|
+
raise ValueError(f"shape of output ({energies.shape}) does not match given batch indices")
|
|
397
|
+
self.is_concat_X = (batch_indices is not None)
|
|
398
|
+
self.is_accept = th.full_like(energies, False, dtype=th.bool)
|
|
399
|
+
|
|
400
|
+
# preload a graph of shape center. A very small overhead that no longer `is_fix_center`, computation does.
|
|
401
|
+
if batch_indices is None:
|
|
402
|
+
SHAPE_CENTER = th.mean(X, dim=1, keepdim=True) # (n_batch, 1, n_dim)
|
|
403
|
+
else:
|
|
404
|
+
SHAPE_CENTER = index_reduce(X, self.batch_scatter, dim=1, out_size=self.scatter_dim_out_size) / self.batch_tensor.unsqueeze(-1)
|
|
405
|
+
SHAPE_CENTER = SHAPE_CENTER.index_select(1, self.batch_scatter) # (1, sumN*A, n_dim)
|
|
406
|
+
|
|
407
|
+
copy_stream = th.cuda.Stream()
|
|
408
|
+
copy_event = th.cuda.Event()
|
|
409
|
+
compute_event = th.cuda.Event()
|
|
410
|
+
compute_event.record(th.cuda.default_stream(self.device)) # the default stream is the compute (main) stream.
|
|
411
|
+
dump_queue = queue.Queue()
|
|
412
|
+
dump_thread = threading.Thread(target=self._do_async_dump, args=(dump_queue,))
|
|
413
|
+
logout_queue = queue.Queue()
|
|
414
|
+
logout_thread = threading.Thread(target=self._do_async_print, args=(logout_queue,))
|
|
415
|
+
try:
|
|
416
|
+
dump_thread.start()
|
|
417
|
+
logout_thread.start()
|
|
418
|
+
fl = th.compile(self._main_for_loop_cuda, **self.compile_kwargs, disable=(not self.is_compile))
|
|
419
|
+
fl(
|
|
420
|
+
compute_event,
|
|
421
|
+
_buf_E,
|
|
422
|
+
_buf_dE,
|
|
423
|
+
_buf_X,
|
|
424
|
+
_print_E,
|
|
425
|
+
_print_dE,
|
|
426
|
+
_print_X,
|
|
427
|
+
energies,
|
|
428
|
+
delta_E,
|
|
429
|
+
X,
|
|
430
|
+
copy_stream,
|
|
431
|
+
copy_event,
|
|
432
|
+
dump_queue,
|
|
433
|
+
logout_queue,
|
|
434
|
+
func, func_args, func_kwargs,
|
|
435
|
+
batch_indices, batch_slice_indx,
|
|
436
|
+
is_fix_mass_center,
|
|
437
|
+
move_to_center_freq,
|
|
438
|
+
SHAPE_CENTER
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
if not self._HOLD_DUMPER:
|
|
442
|
+
self.dumper.close()
|
|
443
|
+
th.cuda.synchronize()
|
|
444
|
+
finally:
|
|
445
|
+
dump_queue.put([None]*4)
|
|
446
|
+
dump_thread.join()
|
|
447
|
+
logout_queue.put([None]*6)
|
|
448
|
+
logout_thread.join()
|
|
449
|
+
|
|
450
|
+
if self.verbose > 0:
|
|
451
|
+
self.logger.info(
|
|
452
|
+
'-' * 100 + f'\nMAIN LOOP Done. Total Time: {time.perf_counter() - t_main:<.4f} s\n'
|
|
453
|
+
)
|
|
454
|
+
if self.verbose < 2: # verbose = 1, brief mode only output last step coords.
|
|
455
|
+
# split batches if specified batch
|
|
456
|
+
if batch_indices is not None:
|
|
457
|
+
X_np = X.numpy(force=True)
|
|
458
|
+
X_tup = np.split(X_np, batch_slice_indx[1:-1], axis=1)
|
|
459
|
+
else:
|
|
460
|
+
X_tup = (X.numpy(force=True),)
|
|
461
|
+
self.logger.info(f" Final Coordinates:\n")
|
|
462
|
+
X_str = [
|
|
463
|
+
np.array2string(xi, **FLOAT_ARRAY_FORMAT).replace("[", " ").replace("]", " ")
|
|
464
|
+
for xi in X_tup
|
|
465
|
+
]
|
|
466
|
+
[self.logger.info(f'{x_str}\n') for x_str in X_str]
|
|
467
|
+
# output
|
|
468
|
+
#return energies, X #, ptlist # test <<<
|
|
469
|
+
|
|
470
|
+
def _main_for_loop_cuda(
|
|
471
|
+
self,
|
|
472
|
+
compute_event,
|
|
473
|
+
_buf_E,
|
|
474
|
+
_buf_dE,
|
|
475
|
+
_buf_X,
|
|
476
|
+
_print_E,
|
|
477
|
+
_print_dE,
|
|
478
|
+
_print_X,
|
|
479
|
+
energies,
|
|
480
|
+
delta_E,
|
|
481
|
+
X,
|
|
482
|
+
copy_stream,
|
|
483
|
+
copy_event,
|
|
484
|
+
dump_queue,
|
|
485
|
+
logout_queue,
|
|
486
|
+
func, func_args, func_kwargs,
|
|
487
|
+
batch_indices, batch_slice_indx,
|
|
488
|
+
is_fix_mass_center,
|
|
489
|
+
move_to_center_freq,
|
|
490
|
+
SHAPE_CENTER
|
|
491
|
+
):
|
|
492
|
+
#t_step = time.perf_counter()
|
|
493
|
+
_do_print = False
|
|
494
|
+
for numit in range(self.maxiter):
|
|
495
|
+
# dump
|
|
496
|
+
if numit % self.output_structures_per_step == 0:
|
|
497
|
+
_do_print = True
|
|
498
|
+
compute_event.wait(th.cuda.default_stream(self.device))
|
|
499
|
+
# D2D, fast copy purely on GPU
|
|
500
|
+
_buf_E.copy_(energies.squeeze().contiguous())
|
|
501
|
+
_buf_dE.copy_(delta_E)
|
|
502
|
+
_buf_X.copy_(X)
|
|
503
|
+
# D2H, async.
|
|
504
|
+
with th.cuda.stream(copy_stream):
|
|
505
|
+
copy_stream.wait_stream(th.cuda.default_stream(self.device))
|
|
506
|
+
_print_E.copy_(_buf_E, non_blocking=True)
|
|
507
|
+
_print_dE.copy_(_buf_dE, non_blocking=True)
|
|
508
|
+
_print_X.copy_(_buf_X, non_blocking=True) # D2H
|
|
509
|
+
copy_event.record(copy_stream)
|
|
510
|
+
# use backend thread to dump
|
|
511
|
+
dump_queue.put((self.dumper, copy_event, _print_E, _print_X))
|
|
512
|
+
|
|
513
|
+
# update X & energy. update before printing to cover the dumping cost by update.
|
|
514
|
+
X_old = X # .clone() # note that X is not in-place updated in `self._update_X` due to `where` ops. Hence `clone` is not necessary.
|
|
515
|
+
energies_old = energies.clone()
|
|
516
|
+
energies, delta_E, X = self._update_X(func, func_args, func_kwargs, energies_old, X) # (n_batch, n_atom, n_dim)
|
|
517
|
+
X_diff = X - X_old
|
|
518
|
+
compute_event.record(th.cuda.default_stream(self.device)) # the default stream is the compute (main) stream.
|
|
519
|
+
|
|
520
|
+
if _do_print:
|
|
521
|
+
logout_queue.put((numit, _print_E, _print_dE, batch_indices, X, batch_slice_indx))
|
|
522
|
+
_do_print = False
|
|
523
|
+
|
|
524
|
+
# update algo. parameters.
|
|
525
|
+
self._update_algo_param(numit, X_diff)
|
|
526
|
+
|
|
527
|
+
# move to center
|
|
528
|
+
if is_fix_mass_center and (numit % move_to_center_freq == 0):
|
|
529
|
+
X.add_(SHAPE_CENTER - self.calc_shape_center(X)) # (n_batch, n_atom, n_dim) - (n_batch, 1, n_dim)
|
|
530
|
+
|
|
531
|
+
# Check NaN
|
|
532
|
+
# if th.any(energies != energies): raise RuntimeError(f'NaN Occurred in output: {energies}')
|
|
533
|
+
|
|
534
|
+
# ptlist.append(X[:, None, :, 0].numpy(force=True)) # test <<<
|
|
535
|
+
|
|
536
|
+
def __run_on_cpu(
|
|
537
|
+
self,
|
|
538
|
+
func: Any | nn.Module,
|
|
539
|
+
X: th.Tensor,
|
|
540
|
+
Element_list: List[List[str]] | List[List[int]] | None = None,
|
|
541
|
+
Cell_vector: th.Tensor | None = None,
|
|
542
|
+
func_args: Sequence = tuple(),
|
|
543
|
+
func_kwargs: Dict | None = None,
|
|
544
|
+
batch_indices: None | List[int] | Tuple[int, ...] | th.Tensor = None,
|
|
545
|
+
fixed_atom_tensor: Optional[th.Tensor] = None,
|
|
546
|
+
move_to_center_freq: int = -1
|
|
547
|
+
) -> None:
|
|
548
|
+
"""
|
|
549
|
+
Run MC Algorithm.
|
|
550
|
+
|
|
551
|
+
Parameters:
|
|
552
|
+
func: the main function of instantiated torch.nn.Module class.
|
|
553
|
+
X: Tensor[n_batch, n_atom, 3], the atom coordinates that input to func.
|
|
554
|
+
Cell_vector: Tensor[n_batch, 3, 3], the cell vectors. Only for logging out information, not involves in calculation.
|
|
555
|
+
Element_list: The list of elements/atom types. Only for logging out information, not involves in calculation.
|
|
556
|
+
if None, all atomic number of points will be labeled to 0.
|
|
557
|
+
func_args: optional, other input of func.
|
|
558
|
+
func_kwargs: optional, other input of func.
|
|
559
|
+
fixed_atom_tensor: Optional[th.Tensor], the indices of X that fixed.
|
|
560
|
+
batch_indices: Sequence | th.Tensor | np.ndarray | None, the split points for given X, Element_list & V_init, must be 1D integer array_like.
|
|
561
|
+
the format of batch_indices is same as `split_size_or_sections` in torch.split:
|
|
562
|
+
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]
|
|
563
|
+
|
|
564
|
+
Return:
|
|
565
|
+
min func: Tensor(n_batch, ), the minimum of func.
|
|
566
|
+
argmin func: Tensor(X.shape), the X corresponds to min func.
|
|
567
|
+
grad of argmin func: Tensor(X.shape), only output when `output_grad` == True. The gradient of X corresponding to minimum.
|
|
568
|
+
"""
|
|
569
|
+
t_main = time.perf_counter()
|
|
570
|
+
if func_kwargs is None:
|
|
571
|
+
func_kwargs = dict()
|
|
572
|
+
# Check batch indices; irregular batch
|
|
573
|
+
if isinstance(X, th.Tensor):
|
|
574
|
+
if len(X.shape) == 2:
|
|
575
|
+
X = X.unsqueeze(0)
|
|
576
|
+
elif len(X.shape) != 3:
|
|
577
|
+
raise ValueError(f'`X` must be 2D or 3D, but got shape [{X.shape}]')
|
|
578
|
+
if not isinstance(move_to_center_freq, int):
|
|
579
|
+
raise TypeError(f'`move_to_center_freq` must be an integer, but got {type(move_to_center_freq)}.')
|
|
580
|
+
n_batch, n_atom, n_dim = X.shape
|
|
581
|
+
else:
|
|
582
|
+
raise TypeError(f'`X` must be torch.Tensor, but occurred {type(X)}.')
|
|
583
|
+
if batch_indices is not None:
|
|
584
|
+
if n_batch != 1:
|
|
585
|
+
raise RuntimeError(f'If batch_indices was specified, the 1st dimension of X must be 1 instead of {n_batch}.')
|
|
586
|
+
if isinstance(batch_indices, (th.Tensor, np.ndarray)):
|
|
587
|
+
batch_indices = batch_indices.tolist()
|
|
588
|
+
elif not isinstance(batch_indices, (Tuple, List)):
|
|
589
|
+
raise TypeError(f'Invalid type of batch_indices {type(batch_indices)}. '
|
|
590
|
+
f'It must be Sequence[int] | th.Tensor | np.ndarray | None')
|
|
591
|
+
for i in batch_indices: assert isinstance(i, int), f'All elements in batch_indices must be int, but occurred {type(i)}'
|
|
592
|
+
n_true_batch = len(batch_indices)
|
|
593
|
+
batch_slice_indx = [0] + list(accumulate(batch_indices)) # convert n_atom of each batch into split point of each batch
|
|
594
|
+
self.batch_tensor = th.as_tensor(batch_indices, device=self.device)
|
|
595
|
+
self.batch_scatter = th.repeat_interleave(
|
|
596
|
+
th.arange(0, len(batch_indices), dtype=th.int64, device=self.device),
|
|
597
|
+
self.batch_tensor,
|
|
598
|
+
dim=0
|
|
599
|
+
)
|
|
600
|
+
else:
|
|
601
|
+
n_true_batch = n_batch
|
|
602
|
+
self.batch_scatter = None
|
|
603
|
+
self.batch_tensor = None
|
|
604
|
+
batch_slice_indx = None
|
|
605
|
+
# initialize vars
|
|
606
|
+
maxiter = self.maxiter
|
|
607
|
+
n_batch, n_atom, n_dim = X.shape
|
|
608
|
+
self.n_batch, self.n_atom, self.n_dim = n_batch, n_atom, n_dim
|
|
609
|
+
if Element_list is not None:
|
|
610
|
+
atomic_numbers = list()
|
|
611
|
+
for _Elem in Element_list:
|
|
612
|
+
atomic_numbers.append([ATOMIC_SYMBOL[__elem] if isinstance(__elem, str) else int(__elem) for __elem in _Elem])
|
|
613
|
+
else:
|
|
614
|
+
atomic_numbers = [[0]*self.n_atom]*self.n_batch
|
|
615
|
+
if not isinstance(move_to_center_freq, int):
|
|
616
|
+
raise TypeError(f'`move_to_center_freq` must be an integer, but got {type(move_to_center_freq)}.')
|
|
617
|
+
is_fix_mass_center = (move_to_center_freq > 0)
|
|
618
|
+
|
|
619
|
+
# Selective dynamics
|
|
620
|
+
if fixed_atom_tensor is None:
|
|
621
|
+
self.atom_masks = th.ones_like(X, device=self.device)
|
|
622
|
+
elif fixed_atom_tensor.shape == X.shape:
|
|
623
|
+
self.atom_masks = fixed_atom_tensor.to(self.device)
|
|
624
|
+
else:
|
|
625
|
+
raise RuntimeError(f'The shape of fixed_atom_tensor (shape: {fixed_atom_tensor.shape}) does not match X (shape: {X.shape}).')
|
|
626
|
+
self.fixed_indices = th.where(th.any(self.atom_masks, dim=-1))[1] # COO sumN or n_atom dim
|
|
627
|
+
# other check
|
|
628
|
+
if (not isinstance(maxiter, int)) or (maxiter <= 0):
|
|
629
|
+
raise ValueError(f'Invalid value of maxiter: {maxiter}. It would be an integer greater than 0.')
|
|
630
|
+
|
|
631
|
+
# set variables device
|
|
632
|
+
func = preload_func(func, self.device)
|
|
633
|
+
X = X.to(self.device)
|
|
634
|
+
|
|
635
|
+
# calc. freedom degree
|
|
636
|
+
if batch_indices is None:
|
|
637
|
+
_free_degree = X.shape[1] * n_dim
|
|
638
|
+
if is_fix_mass_center:
|
|
639
|
+
_free_degree -= 3
|
|
640
|
+
self.free_degree = th.full((n_batch,), _free_degree, dtype=th.int64, device=self.device)
|
|
641
|
+
n_reduce = th.where(th.abs(self.atom_masks) < 1e-6, 1, 0).sum(dim=(-2, -1)) # (n_batch, )
|
|
642
|
+
self.free_degree -= n_reduce
|
|
643
|
+
else:
|
|
644
|
+
self.free_degree = self.batch_tensor * n_dim # (n_batch, )
|
|
645
|
+
if is_fix_mass_center:
|
|
646
|
+
self.free_degree -= 3
|
|
647
|
+
n_reduce_tensor = th.where(th.abs(self.atom_masks) < 1e-6, 1, 0).sum(dim=-1)
|
|
648
|
+
n_reduce = index_reduce(n_reduce_tensor, self.batch_scatter, dim=1, out_size=self.scatter_dim_out_size).squeeze(0) # (n_batch, )
|
|
649
|
+
self.free_degree -= n_reduce
|
|
650
|
+
|
|
651
|
+
# initialize the dumper
|
|
652
|
+
X_arr = X.numpy(force=True)
|
|
653
|
+
_x_dtype = X_arr.dtype.str
|
|
654
|
+
atom_masks_arr = self.atom_masks.numpy(force=True).astype(_x_dtype)
|
|
655
|
+
_num_dump = math.ceil(self.maxiter / self.output_structures_per_step)
|
|
656
|
+
# write head information.
|
|
657
|
+
if Cell_vector is None:
|
|
658
|
+
Cell_vector = np.zeros((n_true_batch, 3, 3), dtype=np.float32)
|
|
659
|
+
elif isinstance(Cell_vector, th.Tensor):
|
|
660
|
+
Cell_vector = Cell_vector.numpy(force=True)
|
|
661
|
+
elif not isinstance(Cell_vector, np.ndarray):
|
|
662
|
+
Cell_vector = np.asarray(Cell_vector)
|
|
663
|
+
if self.batch_tensor is not None:
|
|
664
|
+
self.dumper.start_from_arrays(
|
|
665
|
+
1,
|
|
666
|
+
self.batch_tensor.numpy(force=True), # batch indices
|
|
667
|
+
Cell_vector, # cell
|
|
668
|
+
np.asarray(atomic_numbers), # element type / atomic number
|
|
669
|
+
atom_masks_arr, # fixation mask
|
|
670
|
+
)
|
|
671
|
+
self.dumper.step(
|
|
672
|
+
self.batch_tensor.numpy(force=True),
|
|
673
|
+
Cell_vector,
|
|
674
|
+
np.asarray(atomic_numbers),
|
|
675
|
+
atom_masks_arr
|
|
676
|
+
)
|
|
677
|
+
else:
|
|
678
|
+
self.dumper.start_from_arrays(
|
|
679
|
+
1,
|
|
680
|
+
Cell_vector,
|
|
681
|
+
np.asarray(atomic_numbers), # element type / atomic number
|
|
682
|
+
atom_masks_arr, # fixation mask
|
|
683
|
+
)
|
|
684
|
+
self.dumper.step(
|
|
685
|
+
Cell_vector,
|
|
686
|
+
np.asarray(atomic_numbers),
|
|
687
|
+
atom_masks_arr
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
# initialize
|
|
691
|
+
t_step = time.perf_counter()
|
|
692
|
+
self.initialize_algo_param()
|
|
693
|
+
#ptlist = [X[:, None, :, 0].numpy(force=True)] # for converged samp, stop calc., test <<<
|
|
694
|
+
if self.verbose:
|
|
695
|
+
self.logger.info('-' * 100)
|
|
696
|
+
self.logger.info(f'Iteration Scheme: {self.iterform}')
|
|
697
|
+
self.logger.info('-' * 100)
|
|
698
|
+
# MAIN LOOP
|
|
699
|
+
with th.no_grad():
|
|
700
|
+
energies: th.Tensor = func(X, *func_args, **func_kwargs)
|
|
701
|
+
delta_E = th.zeros_like(energies)
|
|
702
|
+
# continue to write main data
|
|
703
|
+
self.dumper.start_from_arrays(
|
|
704
|
+
_num_dump,
|
|
705
|
+
energies.numpy(),
|
|
706
|
+
X.numpy(),
|
|
707
|
+
)
|
|
708
|
+
# note: irregular tensor regularized by concat. thus n_batch of X shown as 1, but y has shape of the true batch size.
|
|
709
|
+
if energies.shape[0] != self.n_batch:
|
|
710
|
+
if batch_indices is None:
|
|
711
|
+
raise ValueError(
|
|
712
|
+
f"batch indices is None "
|
|
713
|
+
f"while shape of model output ({energies.shape}) does not match batch size ({self.n_batch})."
|
|
714
|
+
)
|
|
715
|
+
if energies.shape[0] != n_true_batch:
|
|
716
|
+
raise ValueError(f"shape of output ({energies.shape}) does not match given batch indices")
|
|
717
|
+
self.is_concat_X = (batch_indices is not None)
|
|
718
|
+
self.is_accept = th.full_like(energies, False, dtype=th.bool)
|
|
719
|
+
|
|
720
|
+
# preload a graph of shape center. A very small overhead that no longer `is_fix_center`, computation does.
|
|
721
|
+
if batch_indices is None:
|
|
722
|
+
SHAPE_CENTER = th.mean(X, dim=1, keepdim=True) # (n_batch, 1, n_dim)
|
|
723
|
+
else:
|
|
724
|
+
SHAPE_CENTER = index_reduce(X, self.batch_scatter, dim=1, out_size=self.scatter_dim_out_size) / self.batch_tensor.unsqueeze(-1)
|
|
725
|
+
SHAPE_CENTER = SHAPE_CENTER.index_select(1, self.batch_scatter) # (1, sumN*A, n_dim)
|
|
726
|
+
|
|
727
|
+
fl = th.compile(self._main_for_loop_cpu, **self.compile_kwargs, disable=(not self.is_compile))
|
|
728
|
+
fl(
|
|
729
|
+
energies,
|
|
730
|
+
delta_E,
|
|
731
|
+
X,
|
|
732
|
+
func, func_args, func_kwargs,
|
|
733
|
+
batch_indices, batch_slice_indx,
|
|
734
|
+
is_fix_mass_center,
|
|
735
|
+
move_to_center_freq,
|
|
736
|
+
SHAPE_CENTER
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
if not self._HOLD_DUMPER:
|
|
740
|
+
self.dumper.close()
|
|
741
|
+
|
|
742
|
+
if self.verbose > 0:
|
|
743
|
+
self.logger.info(
|
|
744
|
+
'-' * 100 + f'\nMAIN LOOP Done. Total Time: {time.perf_counter() - t_main:<.4f} s\n'
|
|
745
|
+
)
|
|
746
|
+
if self.verbose < 2: # verbose = 1, brief mode only output last step coords.
|
|
747
|
+
# split batches if specified batch
|
|
748
|
+
if batch_indices is not None:
|
|
749
|
+
X_np = X.numpy(force=True)
|
|
750
|
+
X_tup = np.split(X_np, batch_slice_indx[1:-1], axis=1)
|
|
751
|
+
else:
|
|
752
|
+
X_tup = (X.numpy(force=True),)
|
|
753
|
+
self.logger.info(f" Final Coordinates:\n")
|
|
754
|
+
X_str = [
|
|
755
|
+
np.array2string(xi, **FLOAT_ARRAY_FORMAT).replace("[", " ").replace("]", " ")
|
|
756
|
+
for xi in X_tup
|
|
757
|
+
]
|
|
758
|
+
[self.logger.info(f'{x_str}\n') for x_str in X_str]
|
|
759
|
+
|
|
760
|
+
def _main_for_loop_cpu(
|
|
761
|
+
self,
|
|
762
|
+
energies,
|
|
763
|
+
delta_E,
|
|
764
|
+
X,
|
|
765
|
+
func, func_args, func_kwargs,
|
|
766
|
+
batch_indices, batch_slice_indx,
|
|
767
|
+
is_fix_mass_center,
|
|
768
|
+
move_to_center_freq,
|
|
769
|
+
SHAPE_CENTER
|
|
770
|
+
):
|
|
771
|
+
_do_print = False
|
|
772
|
+
for numit in range(self.maxiter):
|
|
773
|
+
# dump
|
|
774
|
+
if numit % self.output_structures_per_step == 0:
|
|
775
|
+
_do_print = True
|
|
776
|
+
self.dumper.step(
|
|
777
|
+
energies.numpy(),
|
|
778
|
+
X.numpy(),
|
|
779
|
+
)
|
|
780
|
+
# Print information / Verbose
|
|
781
|
+
if _do_print:
|
|
782
|
+
if self.verbose > 0:
|
|
783
|
+
self.logger.info(
|
|
784
|
+
f"ITERATION {numit:>5d}\n "
|
|
785
|
+
f"delta E: {np.array2string(delta_E.numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n "
|
|
786
|
+
f"Accepted: {np.array2string(self.is_accept.numpy(force=True))}\n "
|
|
787
|
+
f"Energies: {np.array2string(energies.numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n "
|
|
788
|
+
f"Temperature: {self.T_now:.3e}\n "
|
|
789
|
+
#f"TIME: {time.perf_counter() - t_step:>6.4f} s"
|
|
790
|
+
)
|
|
791
|
+
#t_step = time.perf_counter()
|
|
792
|
+
if self.verbose > 1:
|
|
793
|
+
# split batches if specified batch
|
|
794
|
+
if batch_indices is not None:
|
|
795
|
+
X_np = X.numpy(force=True)
|
|
796
|
+
X_tup = np.split(X_np, batch_slice_indx[1:-1], axis=1)
|
|
797
|
+
else:
|
|
798
|
+
X_tup = (X.numpy(force=True),)
|
|
799
|
+
self.logger.info(f" Coordinates:\n")
|
|
800
|
+
X_str = [
|
|
801
|
+
np.array2string(xi, **FLOAT_ARRAY_FORMAT).replace("[", " ").replace("]", " ")
|
|
802
|
+
for xi in X_tup
|
|
803
|
+
]
|
|
804
|
+
[self.logger.info(f'{x_str}\n') for x_str in X_str]
|
|
805
|
+
|
|
806
|
+
# update X & energy
|
|
807
|
+
X_old = X.clone()
|
|
808
|
+
energies_old = energies.clone()
|
|
809
|
+
energies, delta_E, X = self._update_X(func, func_args, func_kwargs, energies_old, X) # (n_batch, n_atom, n_dim)
|
|
810
|
+
X_diff = X - X_old
|
|
811
|
+
|
|
812
|
+
# update algo. parameters.
|
|
813
|
+
self._update_algo_param(numit, X_diff)
|
|
814
|
+
|
|
815
|
+
# Check NaN
|
|
816
|
+
# if not th.all(energies.isfinite()): raise RuntimeError(f'NaN Occurred in output: {energies}')
|
|
817
|
+
|
|
818
|
+
# ptlist.append(X[:, None, :, 0].numpy(force=True)) # test <<<
|
|
819
|
+
|
|
820
|
+
# move to center
|
|
821
|
+
if is_fix_mass_center and (numit % move_to_center_freq == 0):
|
|
822
|
+
X.add_(SHAPE_CENTER - self.calc_shape_center(X)) # (n_batch, n_atom, n_dim) - (n_batch, 1, n_dim)
|
|
823
|
+
|
|
824
|
+
def initialize_algo_param(self):
|
|
825
|
+
"""
|
|
826
|
+
Override this method to initialize attribute variables for self._update_direction.
|
|
827
|
+
Examples:
|
|
828
|
+
|
|
829
|
+
Returns: None
|
|
830
|
+
"""
|
|
831
|
+
raise NotImplementedError
|
|
832
|
+
|
|
833
|
+
def _update_X(
|
|
834
|
+
self,
|
|
835
|
+
func,
|
|
836
|
+
func_args,
|
|
837
|
+
func_kwargs,
|
|
838
|
+
energies_old: th.Tensor,
|
|
839
|
+
X: th.Tensor,
|
|
840
|
+
) -> Tuple[th.Tensor, th.Tensor, th.Tensor]:
|
|
841
|
+
"""
|
|
842
|
+
Override this method to implement X update algorithm.
|
|
843
|
+
Args:
|
|
844
|
+
func: function
|
|
845
|
+
func_args:
|
|
846
|
+
func_kwargs:
|
|
847
|
+
X: (n_batch, n_atom, 3), the independent vars X.
|
|
848
|
+
|
|
849
|
+
Returns:
|
|
850
|
+
energies, delta_E, X
|
|
851
|
+
"""
|
|
852
|
+
raise NotImplementedError
|
|
853
|
+
|
|
854
|
+
def _update_algo_param(self, i: int, displace: th.Tensor) -> None:
|
|
855
|
+
"""
|
|
856
|
+
Override this method to update the parameters of X update algorithm i.e., self.iterform.
|
|
857
|
+
Args:
|
|
858
|
+
i: iteration step now.
|
|
859
|
+
displace: (n_batch, n_atom*3, 1), the displacement of X at this step. displace = step-length * p
|
|
860
|
+
|
|
861
|
+
Returns: None
|
|
862
|
+
"""
|
|
863
|
+
pass
|