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,1251 @@
|
|
|
1
|
+
""" Molecular Dynamics via Verlet algo. """
|
|
2
|
+
# Copyright (c) 2024-2025.7.4, BUCToolkit.
|
|
3
|
+
# Authors: Pu Pengxin, Song Xin
|
|
4
|
+
# Version: 0.9a
|
|
5
|
+
# File: _BaseMD.py
|
|
6
|
+
# Environment: Python 3.12
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Iterable, Dict, Any, List, Literal, Optional, Callable, Sequence, Tuple # noqa: F401
|
|
10
|
+
import time
|
|
11
|
+
import math
|
|
12
|
+
import logging
|
|
13
|
+
import warnings
|
|
14
|
+
import os
|
|
15
|
+
import threading, queue
|
|
16
|
+
|
|
17
|
+
import torch as th
|
|
18
|
+
from torch import nn
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
from BUCToolkit.utils._Element_info import MASS, N_MASS, ATOMIC_NUMBER, ATOMIC_SYMBOL
|
|
23
|
+
from BUCToolkit.utils._print_formatter import FLOAT_ARRAY_FORMAT, SCIENTIFIC_ARRAY_FORMAT
|
|
24
|
+
from BUCToolkit.utils.index_ops import index_reduce
|
|
25
|
+
from BUCToolkit.utils.function_utils import preload_func
|
|
26
|
+
from BUCToolkit.BatchStructures.StructuresIO import structures_io_dumper
|
|
27
|
+
from BUCToolkit.utils.setup_loggers import has_any_handler, clear_all_handlers, BaseIO
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _BaseMD(BaseIO):
|
|
31
|
+
""" Base BatchMD """
|
|
32
|
+
|
|
33
|
+
__slots__ = [
|
|
34
|
+
'time_step', 'time_now',
|
|
35
|
+
'verbose', 'logger',
|
|
36
|
+
'batch_tensor', 'batch_scatter',
|
|
37
|
+
'free_degree',
|
|
38
|
+
'require_grad',
|
|
39
|
+
'EK_TARGET',
|
|
40
|
+
'Ekt_vir',
|
|
41
|
+
'Ek',
|
|
42
|
+
'p_iota',
|
|
43
|
+
#'__dict__'
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
time_step: float,
|
|
49
|
+
max_step: int,
|
|
50
|
+
T_init: float = 298.15,
|
|
51
|
+
output_file: str | None = None,
|
|
52
|
+
output_structures_per_step: int = 1,
|
|
53
|
+
device: str | th.device = 'cpu',
|
|
54
|
+
verbose: int = 0,
|
|
55
|
+
is_compile: bool = False,
|
|
56
|
+
compile_kwargs: dict | None = None,
|
|
57
|
+
):
|
|
58
|
+
"""
|
|
59
|
+
Parameters:
|
|
60
|
+
time_step: float, time per step (fs).
|
|
61
|
+
max_step: maximum steps.
|
|
62
|
+
T_init: initial temperature, only to generate initial velocities of atoms by Maxwell-Boltzmann distribution.
|
|
63
|
+
If V_init is given, T_init will be ignored.
|
|
64
|
+
output_file: the path to the binary file that stores trajectories.
|
|
65
|
+
output_structures_per_step: int, output structures per output_structures_per_step steps.
|
|
66
|
+
device: device that program run on.
|
|
67
|
+
verbose: control the detailed degree of output text information.
|
|
68
|
+
0 for silence, 1 for output Energy and Forces per step, 2 for output all structures.
|
|
69
|
+
Note: verbose > 0 will be very slow, especially for computation on GPU.
|
|
70
|
+
is_compile: whether to use jit to compile integrator or not.
|
|
71
|
+
compile_kwargs: keyword arguments passed to compile. Only work when is_compile is True.
|
|
72
|
+
"""
|
|
73
|
+
self.time_step = time_step
|
|
74
|
+
self.time_now = th.scalar_tensor(0., device=device) # the accumulated time
|
|
75
|
+
assert (max_step > 0) and isinstance(max_step, int), f'max_step must be a positive integer, but occurred {max_step}.'
|
|
76
|
+
self.max_step = int(max_step)
|
|
77
|
+
self.T_init = float(T_init)
|
|
78
|
+
self.output_structures_per_step = int(output_structures_per_step)
|
|
79
|
+
self.device = device
|
|
80
|
+
self.verbose = int(verbose)
|
|
81
|
+
self.is_compile = bool(is_compile)
|
|
82
|
+
self.compile_kwargs = compile_kwargs if compile_kwargs is not None else dict()
|
|
83
|
+
|
|
84
|
+
self.EK_TARGET = None # target kinetic energy under set temperature.
|
|
85
|
+
self.Ekt_vir = None # virtual kinetic energy for _CSVR_ thermostat.
|
|
86
|
+
self.Ek = None # kinetic energy at each timestep.
|
|
87
|
+
self.p_iota = None # thermostat var. for Nose-Hoover.
|
|
88
|
+
|
|
89
|
+
self.batch_tensor = None # tensor form of `batch_indices` if it was given.
|
|
90
|
+
self.batch_scatter = None # tensor indices form of `batch_indices` if it was given
|
|
91
|
+
# e.g., batch_indices = (3, 2, 1), thus self.scatter = tensor([0, 0, 0, 1, 1, 2])
|
|
92
|
+
self.free_degree = None # (n_batch, ), freedom degree tensor
|
|
93
|
+
self.require_grad = None
|
|
94
|
+
|
|
95
|
+
# An inner attr that lets the dumper do not close after `self.run`.
|
|
96
|
+
# It is used to contiguously run within a loop.
|
|
97
|
+
# Adv. API `MolecularDynamics` turns on it.
|
|
98
|
+
self._HOLD_DUMPER = False
|
|
99
|
+
|
|
100
|
+
# logging & dumper
|
|
101
|
+
super().__init__(output_file)
|
|
102
|
+
self.init_logger('Main.MD')
|
|
103
|
+
|
|
104
|
+
def _reduce_Ek_T(self, batch_indices, masses, V):
|
|
105
|
+
if batch_indices is not None:
|
|
106
|
+
Ek = th.sum(
|
|
107
|
+
0.5 * index_reduce(
|
|
108
|
+
masses * V * V,
|
|
109
|
+
self.batch_scatter,
|
|
110
|
+
1,
|
|
111
|
+
out_size=self.scatter_dim_out_size
|
|
112
|
+
) * 103.642696562621738,
|
|
113
|
+
dim=-1
|
|
114
|
+
).squeeze_(0) # (n_batch, ), eV/atom. Faraday constant F = 96485.3321233100184.
|
|
115
|
+
else:
|
|
116
|
+
Ek = 0.5 * th.sum(
|
|
117
|
+
masses * V * V,
|
|
118
|
+
dim=(-2, -1)
|
|
119
|
+
) * 103.642696562621738 # (n_batch, ), eV/atom. Faraday constant F = 96485.3321233100184.
|
|
120
|
+
temperature = (2 * Ek) / ((self.free_degree + 1e-20) * 8.617333262145e-5) # Boltzmann constant kB = 8.617333262145e-5 eV/K
|
|
121
|
+
|
|
122
|
+
return Ek, temperature
|
|
123
|
+
|
|
124
|
+
def calc_mass_center(self, mass, mass_short, Xr, ) -> th.Tensor:
|
|
125
|
+
"""
|
|
126
|
+
calculate mass center based on mass_short and Xr.
|
|
127
|
+
Args:
|
|
128
|
+
mass:
|
|
129
|
+
mass_short:
|
|
130
|
+
Xr:
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
|
|
134
|
+
"""
|
|
135
|
+
if self.batch_scatter is None:
|
|
136
|
+
# initialize topologie
|
|
137
|
+
mass_sum = th.sum(mass_short, dim=1, keepdim=True).unsqueeze(-2) # (n_batch, 1, 1)
|
|
138
|
+
mass_center = th.sum(Xr * mass, dim=1, keepdim=True) / mass_sum # (n_batch, 1, n_dim)
|
|
139
|
+
else:
|
|
140
|
+
# initialize topologie
|
|
141
|
+
mass_sum = index_reduce(mass_short, self.batch_scatter, dim=1, out_size=self.scatter_dim_out_size).unsqueeze(-1) # (1, n_batch, 1)
|
|
142
|
+
mass_center = index_reduce(Xr * mass, self.batch_scatter, dim=1, out_size=self.scatter_dim_out_size) / mass_sum # (1, n_batch, n_dim)
|
|
143
|
+
mass_center = mass_center.index_select(1, self.batch_scatter) # (1, sumN*A, n_dim)
|
|
144
|
+
|
|
145
|
+
return mass_center
|
|
146
|
+
|
|
147
|
+
def _do_async_dump(self, q: queue.Queue):
|
|
148
|
+
"""
|
|
149
|
+
A backend thread to async. dump
|
|
150
|
+
Args:
|
|
151
|
+
q: queue to receive data. contains: tuple of (dumper, event, *data)
|
|
152
|
+
|
|
153
|
+
Returns: None
|
|
154
|
+
|
|
155
|
+
"""
|
|
156
|
+
while True:
|
|
157
|
+
try:
|
|
158
|
+
dumper, event, _print_Ep, _print_X, _print_V, _print_F = q.get()
|
|
159
|
+
if dumper is None:
|
|
160
|
+
break
|
|
161
|
+
# event: th.cuda.Event, ensure copy done
|
|
162
|
+
event.synchronize()
|
|
163
|
+
dumper.step(
|
|
164
|
+
_print_Ep.numpy(),
|
|
165
|
+
_print_X.numpy(),
|
|
166
|
+
_print_V.numpy(),
|
|
167
|
+
_print_F.numpy(),
|
|
168
|
+
)
|
|
169
|
+
except Exception as e:
|
|
170
|
+
self.logger.error(f"Error: Failed to dump data due to \"{e}\"")
|
|
171
|
+
|
|
172
|
+
def _do_async_print(self, q: queue.Queue):
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
q:
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
|
|
180
|
+
"""
|
|
181
|
+
formatter1 = {'float': '{:> .2f}'.format}
|
|
182
|
+
formatter2 = {'float': '{:> 5.10f}'.format}
|
|
183
|
+
i = 0 # reinsurance for Exception part
|
|
184
|
+
# Note: PRINTING IS VERY EXPENSIVE !!!
|
|
185
|
+
while True:
|
|
186
|
+
try:
|
|
187
|
+
i, batch_indices, _print_Ep, _print_Ek, _print_temperature, _print_X, _print_V, _print_F = q.get()
|
|
188
|
+
if i is None:
|
|
189
|
+
break
|
|
190
|
+
if self.verbose > 0:
|
|
191
|
+
# print format
|
|
192
|
+
np.set_printoptions(
|
|
193
|
+
precision=8,
|
|
194
|
+
linewidth=1024,
|
|
195
|
+
floatmode='fixed',
|
|
196
|
+
suppress=True,
|
|
197
|
+
formatter=formatter1,
|
|
198
|
+
threshold=2000
|
|
199
|
+
)
|
|
200
|
+
self.logger.info(
|
|
201
|
+
f'Step: {i:>12d}\n\t'
|
|
202
|
+
f'T = {_print_temperature.numpy(force=True)}\n\t'
|
|
203
|
+
f'E_tol = {np.array2string((_print_Ek + _print_Ep).numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n\t'
|
|
204
|
+
f'Ek = {np.array2string(_print_Ek.numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n\t'
|
|
205
|
+
f'Ep = {np.array2string(_print_Ep.numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n\t'
|
|
206
|
+
# f'Time: {time.perf_counter() - t_step:>5.4f}'
|
|
207
|
+
)
|
|
208
|
+
# t_step = time.perf_counter()
|
|
209
|
+
if self.verbose > 1:
|
|
210
|
+
# split to print
|
|
211
|
+
if batch_indices is not None:
|
|
212
|
+
X_tup = th.split(_print_X, batch_indices, dim=1)
|
|
213
|
+
V_tup = th.split(_print_V, batch_indices, dim=1)
|
|
214
|
+
else:
|
|
215
|
+
X_tup = (_print_X,)
|
|
216
|
+
V_tup = (_print_V,)
|
|
217
|
+
np.set_printoptions(
|
|
218
|
+
precision=8,
|
|
219
|
+
floatmode='fixed',
|
|
220
|
+
suppress=True,
|
|
221
|
+
formatter=formatter2,
|
|
222
|
+
threshold=3000000
|
|
223
|
+
)
|
|
224
|
+
self.logger.info('_' * 100)
|
|
225
|
+
self.logger.info(f'Configuration {i}:')
|
|
226
|
+
for __x in X_tup:
|
|
227
|
+
X_str = np.array2string(
|
|
228
|
+
__x.numpy(force=True), **FLOAT_ARRAY_FORMAT
|
|
229
|
+
).replace("[", " ").replace("]", " ")
|
|
230
|
+
self.logger.info(f'{X_str}\n')
|
|
231
|
+
del X_str, X_tup
|
|
232
|
+
if self.verbose > 2:
|
|
233
|
+
self.logger.info(f'Velocities {i}:')
|
|
234
|
+
for __x in V_tup:
|
|
235
|
+
V_str = np.array2string(
|
|
236
|
+
__x.numpy(force=True), **FLOAT_ARRAY_FORMAT
|
|
237
|
+
).replace("[", " ").replace("]", " ")
|
|
238
|
+
self.logger.info(f'{V_str}\n')
|
|
239
|
+
del V_str
|
|
240
|
+
self.logger.info('_' * 100)
|
|
241
|
+
except Exception as e:
|
|
242
|
+
self.logger.error(f"Error: Failed to logout at {i}-th iteration due to \"{e}\".")
|
|
243
|
+
|
|
244
|
+
def _print_elem_info(self, Element_list, batch_indices):
|
|
245
|
+
# elem info
|
|
246
|
+
elem_list = list()
|
|
247
|
+
_element_list = list()
|
|
248
|
+
if batch_indices is not None:
|
|
249
|
+
indx_old = 0
|
|
250
|
+
for indx in batch_indices:
|
|
251
|
+
_element_list.append(Element_list[0][indx_old: indx_old + indx])
|
|
252
|
+
indx_old += indx
|
|
253
|
+
else:
|
|
254
|
+
_element_list = Element_list
|
|
255
|
+
for elements in _element_list:
|
|
256
|
+
__element_now = ''
|
|
257
|
+
__elem = ''
|
|
258
|
+
elem_info = ''
|
|
259
|
+
__elem_count = ''
|
|
260
|
+
for i, elem in enumerate(elements, 1):
|
|
261
|
+
# get element symbol
|
|
262
|
+
if isinstance(elem, int):
|
|
263
|
+
__elem = ATOMIC_NUMBER[elem]
|
|
264
|
+
else:
|
|
265
|
+
__elem = elem
|
|
266
|
+
# count element number
|
|
267
|
+
if __elem == __element_now:
|
|
268
|
+
__elem_count += 1
|
|
269
|
+
else:
|
|
270
|
+
elem_info = elem_info + str(__elem_count) + ' '
|
|
271
|
+
elem_info = elem_info + __elem + ': '
|
|
272
|
+
__elem_count = 1
|
|
273
|
+
__element_now = __elem
|
|
274
|
+
elem_info = elem_info + str(__elem_count)
|
|
275
|
+
elem_list.append(elem_info)
|
|
276
|
+
# log out
|
|
277
|
+
for i, ee in enumerate(elem_list):
|
|
278
|
+
self.logger.info(f'Structure {i:>5d}: {ee}')
|
|
279
|
+
|
|
280
|
+
@th.compiler.disable
|
|
281
|
+
def _calc_EF(
|
|
282
|
+
self,
|
|
283
|
+
X,
|
|
284
|
+
func,
|
|
285
|
+
func_args,
|
|
286
|
+
func_kwargs,
|
|
287
|
+
grad_func_,
|
|
288
|
+
grad_func_args,
|
|
289
|
+
grad_func_kwargs,
|
|
290
|
+
is_grad_func_contain_y,
|
|
291
|
+
) -> Tuple[th.Tensor, th.Tensor]:
|
|
292
|
+
with th.set_grad_enabled(self.require_grad):
|
|
293
|
+
X.requires_grad_(self.require_grad)
|
|
294
|
+
Energy = func(X, *func_args, **func_kwargs)
|
|
295
|
+
if is_grad_func_contain_y:
|
|
296
|
+
Force = - grad_func_(X, Energy, *grad_func_args, **grad_func_kwargs)
|
|
297
|
+
else:
|
|
298
|
+
Force = - grad_func_(X, *grad_func_args, **grad_func_kwargs)
|
|
299
|
+
return Energy, Force
|
|
300
|
+
|
|
301
|
+
def initialize(
|
|
302
|
+
self,
|
|
303
|
+
func: Any | nn.Module,
|
|
304
|
+
X: th.Tensor,
|
|
305
|
+
Element_list: List[List[str]] | List[List[int]],
|
|
306
|
+
masses: th.Tensor,
|
|
307
|
+
V_init: th.Tensor | None = None,
|
|
308
|
+
grad_func: Any | nn.Module = None,
|
|
309
|
+
func_args: Sequence = tuple(),
|
|
310
|
+
func_kwargs: Dict | None = None,
|
|
311
|
+
grad_func_args: Sequence = tuple(),
|
|
312
|
+
grad_func_kwargs: Dict | None = None,
|
|
313
|
+
is_grad_func_contain_y: bool = True,
|
|
314
|
+
require_grad: bool = False,
|
|
315
|
+
batch_indices: List[int] | Tuple[int, ...] | th.Tensor | np.ndarray | None = None,
|
|
316
|
+
fixed_atom_tensor: Optional[th.Tensor] = None,
|
|
317
|
+
is_fix_mass_center: bool = False
|
|
318
|
+
) -> None:
|
|
319
|
+
"""
|
|
320
|
+
Do some possible initialization before entering main loop.
|
|
321
|
+
Default is doing nothing.
|
|
322
|
+
"""
|
|
323
|
+
pass
|
|
324
|
+
|
|
325
|
+
def run(
|
|
326
|
+
self,
|
|
327
|
+
func: Any | nn.Module,
|
|
328
|
+
X: th.Tensor,
|
|
329
|
+
Element_list: List[List[str]] | List[List[int]],
|
|
330
|
+
Cell_vector: th.Tensor | None = None,
|
|
331
|
+
V_init: th.Tensor | None = None,
|
|
332
|
+
grad_func: Any | nn.Module = None,
|
|
333
|
+
func_args: Sequence = tuple(),
|
|
334
|
+
func_kwargs: Dict | None = None,
|
|
335
|
+
grad_func_args: Sequence = tuple(),
|
|
336
|
+
grad_func_kwargs: Dict | None = None,
|
|
337
|
+
is_grad_func_contain_y: bool = True,
|
|
338
|
+
require_grad: bool = False,
|
|
339
|
+
batch_indices: List[int] | Tuple[int, ...] | th.Tensor | np.ndarray | None = None,
|
|
340
|
+
fixed_atom_tensor: Optional[th.Tensor] = None,
|
|
341
|
+
move_to_center_freq: int = -1
|
|
342
|
+
) -> None:
|
|
343
|
+
"""
|
|
344
|
+
Parameters:
|
|
345
|
+
func: the main function of instantiated torch.nn.Module class.
|
|
346
|
+
X: Tensor[n_batch, n_atom, 3], the atom coordinates that input to func. If a 2D X was given, the first dimension would be set to 1.
|
|
347
|
+
Element_list: List[List[str | int]], the atomic type (element) corresponding to each row of each batch in X.
|
|
348
|
+
Cell_vector: Tensor[n_batch, 3, 3], the cell vectors. Only for logging out information, no really calculate. If not given, set all zeros.
|
|
349
|
+
V_init: the initial velocities of each atom. If None, a random velocity generated by Boltzmann distribution would be set.
|
|
350
|
+
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).
|
|
351
|
+
func_args: optional, other input of func.
|
|
352
|
+
func_kwargs: optional, other input of func.
|
|
353
|
+
grad_func_args: optional, other input of grad_func.
|
|
354
|
+
grad_func_kwargs: optional, other input of grad_func.
|
|
355
|
+
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, ...)
|
|
356
|
+
require_grad: bool, if True, autograd will be turned on for func(X, *func_args, **func_kwargs) calculation.
|
|
357
|
+
batch_indices: the split points for given X, Element_list & V_init, must be 1D integer array_like.
|
|
358
|
+
the format of batch_indices is the same as `split_size_or_sections` in torch.split:
|
|
359
|
+
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]
|
|
360
|
+
fixed_atom_tensor: the indices of X that fixed.
|
|
361
|
+
move_to_center_freq: the period of translating coordinates and velocities of atoms into the mass center & 0.
|
|
362
|
+
if `move_to_center_freq` <= 0, the translation would not apply.
|
|
363
|
+
|
|
364
|
+
Returns: None
|
|
365
|
+
|
|
366
|
+
"""
|
|
367
|
+
try:
|
|
368
|
+
if th.device(self.device).type == "cuda":
|
|
369
|
+
self.__run_on_cuda(
|
|
370
|
+
func,
|
|
371
|
+
X,
|
|
372
|
+
Element_list,
|
|
373
|
+
Cell_vector,
|
|
374
|
+
V_init,
|
|
375
|
+
grad_func,
|
|
376
|
+
func_args,
|
|
377
|
+
func_kwargs,
|
|
378
|
+
grad_func_args,
|
|
379
|
+
grad_func_kwargs,
|
|
380
|
+
is_grad_func_contain_y,
|
|
381
|
+
require_grad,
|
|
382
|
+
batch_indices,
|
|
383
|
+
fixed_atom_tensor,
|
|
384
|
+
move_to_center_freq
|
|
385
|
+
)
|
|
386
|
+
elif th.device(self.device).type == "cpu":
|
|
387
|
+
self.__run_on_cpu(
|
|
388
|
+
func,
|
|
389
|
+
X,
|
|
390
|
+
Element_list,
|
|
391
|
+
Cell_vector,
|
|
392
|
+
V_init,
|
|
393
|
+
grad_func,
|
|
394
|
+
func_args,
|
|
395
|
+
func_kwargs,
|
|
396
|
+
grad_func_args,
|
|
397
|
+
grad_func_kwargs,
|
|
398
|
+
is_grad_func_contain_y,
|
|
399
|
+
require_grad,
|
|
400
|
+
batch_indices,
|
|
401
|
+
fixed_atom_tensor,
|
|
402
|
+
move_to_center_freq
|
|
403
|
+
)
|
|
404
|
+
else:
|
|
405
|
+
raise NotImplementedError(F"device {self.device} not supported.")
|
|
406
|
+
finally:
|
|
407
|
+
pass
|
|
408
|
+
|
|
409
|
+
def __run_on_cuda(
|
|
410
|
+
self,
|
|
411
|
+
func: Any | nn.Module,
|
|
412
|
+
X: th.Tensor,
|
|
413
|
+
Element_list: List[List[str]] | List[List[int]],
|
|
414
|
+
Cell_vector: th.Tensor | None = None,
|
|
415
|
+
V_init: th.Tensor | None = None,
|
|
416
|
+
grad_func: Any | nn.Module = None,
|
|
417
|
+
func_args: Sequence = tuple(),
|
|
418
|
+
func_kwargs: Dict | None = None,
|
|
419
|
+
grad_func_args: Sequence = tuple(),
|
|
420
|
+
grad_func_kwargs: Dict | None = None,
|
|
421
|
+
is_grad_func_contain_y: bool = True,
|
|
422
|
+
require_grad: bool = False,
|
|
423
|
+
batch_indices: List[int] | Tuple[int, ...] | th.Tensor | np.ndarray | None = None,
|
|
424
|
+
fixed_atom_tensor: Optional[th.Tensor] = None,
|
|
425
|
+
move_to_center_freq: int = -1
|
|
426
|
+
) -> None:
|
|
427
|
+
"""
|
|
428
|
+
GPU version of BatchMD.run(...), including buffer, sync. D2H copy, etc.
|
|
429
|
+
"""
|
|
430
|
+
if len(X.shape) == 2:
|
|
431
|
+
X = X.unsqueeze(0)
|
|
432
|
+
elif len(X.shape) != 3:
|
|
433
|
+
raise ValueError(f'`X` must be 2D or 3D, but got shape [{X.shape}]')
|
|
434
|
+
if not isinstance(move_to_center_freq, int):
|
|
435
|
+
raise TypeError(f'`move_to_center_freq` must be an integer, but got {type(move_to_center_freq)}.')
|
|
436
|
+
elif move_to_center_freq <= 0:
|
|
437
|
+
is_fix_mass_center = False
|
|
438
|
+
else:
|
|
439
|
+
is_fix_mass_center = True
|
|
440
|
+
n_batch, n_atom, n_dim = X.shape
|
|
441
|
+
if func_kwargs is None: func_kwargs = dict()
|
|
442
|
+
if grad_func_kwargs is None: grad_func_kwargs = dict()
|
|
443
|
+
# Check batch indices
|
|
444
|
+
if batch_indices is not None:
|
|
445
|
+
if n_batch != 1:
|
|
446
|
+
raise RuntimeError(f'If batch_indices was specified, the 1st dimension of X must be 1 instead of {n_batch}.')
|
|
447
|
+
if isinstance(batch_indices, (th.Tensor, np.ndarray)):
|
|
448
|
+
self.batch_tensor = batch_indices
|
|
449
|
+
batch_indices = batch_indices.tolist()
|
|
450
|
+
elif not isinstance(batch_indices, (List, Tuple)):
|
|
451
|
+
raise TypeError(f'Invalid type of batch_indices {type(batch_indices)}. '
|
|
452
|
+
f'It must be List[int] | Tuple[int, ...] | th.Tensor | np.ndarray | None')
|
|
453
|
+
for i in batch_indices: assert isinstance(i, int), f'All elements in batch_indices must be int, but occurred {type(i)}'
|
|
454
|
+
self.batch_tensor = th.as_tensor(batch_indices, device=self.device) # the tensor version of batch_indices which is a List.
|
|
455
|
+
self.batch_scatter = th.repeat_interleave(
|
|
456
|
+
th.arange(0, len(batch_indices), dtype=th.int64, device=self.device),
|
|
457
|
+
self.batch_tensor,
|
|
458
|
+
dim=0
|
|
459
|
+
) # scatter mask of the int tensor with the same shape as X.shape[1], which the data in one batch have one index.
|
|
460
|
+
self.scatter_dim_out_size = self.batch_scatter.max().item() + 1
|
|
461
|
+
n_true_batch = len(batch_indices)
|
|
462
|
+
else:
|
|
463
|
+
n_true_batch = n_batch
|
|
464
|
+
|
|
465
|
+
# Manage Atomic Type & Masses
|
|
466
|
+
masses = list()
|
|
467
|
+
atomic_numbers = list()
|
|
468
|
+
for _Elem in Element_list:
|
|
469
|
+
if not isinstance(_Elem, list): raise TypeError(f'Expected `Element_list` of List[List[int|str]], but got List[{type(_Elem)}].')
|
|
470
|
+
atomic_numbers.append([ATOMIC_SYMBOL[__elem] if isinstance(__elem, str) else int(__elem) for __elem in _Elem])
|
|
471
|
+
masses.append([MASS[__elem] if isinstance(__elem, str) else N_MASS[__elem] for __elem in _Elem])
|
|
472
|
+
masses_short = th.tensor(masses, dtype=th.float32, device=self.device) # (n_batch, n_atom)
|
|
473
|
+
masses = masses_short.unsqueeze(-1).expand_as(X).contiguous() # (n_batch, n_atom, n_dim)
|
|
474
|
+
# grad_func
|
|
475
|
+
if grad_func is None:
|
|
476
|
+
is_grad_func_contain_y = True
|
|
477
|
+
def grad_func_(x, y, grad_shape=None):
|
|
478
|
+
if grad_shape is None:
|
|
479
|
+
grad_shape = th.ones_like(y)
|
|
480
|
+
g = th.autograd.grad(y, x, grad_shape)
|
|
481
|
+
return g[0]
|
|
482
|
+
else:
|
|
483
|
+
grad_func_ = grad_func
|
|
484
|
+
# Selective dynamics
|
|
485
|
+
if fixed_atom_tensor is None:
|
|
486
|
+
atom_masks = th.ones_like(X, device=self.device)
|
|
487
|
+
elif fixed_atom_tensor.shape == X.shape:
|
|
488
|
+
atom_masks = fixed_atom_tensor.to(self.device)
|
|
489
|
+
else:
|
|
490
|
+
raise RuntimeError(f'fixed_atom_tensor (shape: {fixed_atom_tensor.shape}) does not match X (shape: {X.shape}).')
|
|
491
|
+
# other check
|
|
492
|
+
if (not isinstance(self.max_step, int)) or (self.max_step <= 0):
|
|
493
|
+
raise ValueError(f'Invalid value of maxiter: {self.max_step}. It would be an integer greater than 0.')
|
|
494
|
+
|
|
495
|
+
# set variables device
|
|
496
|
+
func = preload_func(func, self.device)
|
|
497
|
+
|
|
498
|
+
if isinstance(grad_func_, nn.Module):
|
|
499
|
+
grad_func_ = grad_func_.to(self.device)
|
|
500
|
+
X = X.to(self.device)
|
|
501
|
+
# calc. freedom degree
|
|
502
|
+
if batch_indices is None:
|
|
503
|
+
_free_degree = X.shape[1] * n_dim
|
|
504
|
+
if is_fix_mass_center:
|
|
505
|
+
_free_degree -= 3
|
|
506
|
+
# initialize topologie
|
|
507
|
+
MASS_SUM = th.sum(masses_short, dim=1, keepdim=True).unsqueeze(-2) # (n_batch, 1, 1)
|
|
508
|
+
MASS_CENTER = th.sum(X * masses, dim=1, keepdim=True)/MASS_SUM # (n_batch, 1, n_dim)
|
|
509
|
+
self.free_degree = th.full((n_batch, ), _free_degree, dtype=th.int64, device=self.device)
|
|
510
|
+
n_reduce = th.where(th.abs(atom_masks) < 1e-6, 1, 0).sum(dim=(-2, -1)) # (n_batch, )
|
|
511
|
+
self.free_degree -= n_reduce
|
|
512
|
+
else:
|
|
513
|
+
self.free_degree = self.batch_tensor * n_dim # (n_batch, )
|
|
514
|
+
if is_fix_mass_center:
|
|
515
|
+
self.free_degree -= 3
|
|
516
|
+
# initialize topologie
|
|
517
|
+
MASS_SUM = index_reduce(masses_short, self.batch_scatter, dim=1, out_size=self.scatter_dim_out_size).unsqueeze(-1) # (1, n_batch, 1)
|
|
518
|
+
MASS_CENTER = index_reduce(X * masses, self.batch_scatter, dim=1, out_size=self.scatter_dim_out_size)/MASS_SUM # (1, n_batch, n_dim)
|
|
519
|
+
MASS_CENTER = MASS_CENTER.index_select(1, self.batch_scatter) # (1, sumN*A, n_dim)
|
|
520
|
+
n_reduce_tensor = th.where(th.abs(atom_masks) < 1e-6, 1, 0).sum(dim=-1)
|
|
521
|
+
n_reduce = index_reduce(n_reduce_tensor, self.batch_scatter, dim=1, out_size=self.scatter_dim_out_size).squeeze(0) # (n_batch, )
|
|
522
|
+
self.free_degree -= n_reduce
|
|
523
|
+
# target kinetic energy for NVT|NPT ensembles
|
|
524
|
+
self.EK_TARGET = (self.free_degree / 2.) * 8.617333262145e-5 * self.T_init
|
|
525
|
+
# Generate initial Velocities
|
|
526
|
+
if V_init is not None:
|
|
527
|
+
if V_init.shape != X.shape:
|
|
528
|
+
raise ValueError(f'V_init and X must have the same shape, but got {V_init.shape} and {X.shape}')
|
|
529
|
+
if self.verbose > 0: self.logger.info('Initial velocities was given, T_init will be ignored.')
|
|
530
|
+
V_init = V_init.to(self.device)
|
|
531
|
+
V = V_init.detach() * atom_masks
|
|
532
|
+
V = V.to(self.device)
|
|
533
|
+
else:
|
|
534
|
+
V = th.normal(
|
|
535
|
+
0.,
|
|
536
|
+
th.sqrt(self.T_init * 8314.46261815324 / masses) * 1.e-5, # Unit: Ang/fs, R = kB * L = 8.31446261815324 J/(mol * K)
|
|
537
|
+
) * atom_masks
|
|
538
|
+
# remove translation veloc.
|
|
539
|
+
if is_fix_mass_center:
|
|
540
|
+
V.sub_(self.calc_mass_center(masses, masses_short, V))
|
|
541
|
+
# split by batch_indices
|
|
542
|
+
if batch_indices is not None:
|
|
543
|
+
masses_tup = th.split(masses, batch_indices, dim=1)
|
|
544
|
+
V_tup = th.split(V, batch_indices, dim=1)
|
|
545
|
+
else:
|
|
546
|
+
masses_tup = (masses,)
|
|
547
|
+
V_tup = (V,)
|
|
548
|
+
|
|
549
|
+
# initialize thermostat parameters
|
|
550
|
+
# (n_batch, ), eV/atom. The initial virtual Ek_t for CSVR.
|
|
551
|
+
self.Ekt_vir = th.cat(
|
|
552
|
+
[0.5 * th.sum(_m * V_tup[_] ** 2, dim=(-2, -1)) * 103.642696562621738 for _, _m in enumerate(masses_tup)]
|
|
553
|
+
)
|
|
554
|
+
# The initial iota for Nose-Hoover
|
|
555
|
+
if batch_indices is not None:
|
|
556
|
+
self.p_iota = th.zeros(1, len(batch_indices), 1, device=self.device, dtype=th.float32)
|
|
557
|
+
else:
|
|
558
|
+
self.p_iota = th.zeros(n_batch, 1, 1, device=self.device, dtype=th.float32)
|
|
559
|
+
# whether grad needs autograd
|
|
560
|
+
self.require_grad = require_grad
|
|
561
|
+
|
|
562
|
+
# pre-allocate the tensors
|
|
563
|
+
# _buf_* is the vars on GPU that apply copy.
|
|
564
|
+
# _print_* is the vars on CPU that async. do D2H for _buf_*.
|
|
565
|
+
if batch_indices is not None:
|
|
566
|
+
_print_temperature = th.empty(len(batch_indices), device='cpu', dtype=th.float32, pin_memory=True)
|
|
567
|
+
_print_Ek = th.empty(len(batch_indices), device='cpu', dtype=th.float32, pin_memory=True)
|
|
568
|
+
_print_Ep = th.empty(len(batch_indices), device='cpu', dtype=th.float32, pin_memory=True)
|
|
569
|
+
_buf_Tp = th.empty(len(batch_indices), device=self.device, dtype=th.float32)
|
|
570
|
+
_buf_Ek = th.empty(len(batch_indices), device=self.device, dtype=th.float32)
|
|
571
|
+
_buf_Ep = th.empty(len(batch_indices), device=self.device, dtype=th.float32)
|
|
572
|
+
else:
|
|
573
|
+
_print_temperature = th.empty(n_batch, device='cpu', dtype=th.float32, pin_memory=True)
|
|
574
|
+
_print_Ek = th.empty(n_batch, device='cpu', dtype=th.float32, pin_memory=True)
|
|
575
|
+
_print_Ep = th.empty(n_batch, device='cpu', dtype=th.float32, pin_memory=True)
|
|
576
|
+
_buf_Tp = th.empty(n_batch, device=self.device, dtype=th.float32)
|
|
577
|
+
_buf_Ek = th.empty(n_batch, device=self.device, dtype=th.float32)
|
|
578
|
+
_buf_Ep = th.empty(n_batch, device=self.device, dtype=th.float32)
|
|
579
|
+
_print_X = th.empty_like(X, device='cpu', dtype=th.float32, pin_memory=True)
|
|
580
|
+
_print_V = th.empty_like(V, device='cpu', dtype=th.float32, pin_memory=True)
|
|
581
|
+
_print_F = th.empty_like(X, device='cpu', dtype=th.float32, pin_memory=True)
|
|
582
|
+
_buf_X = th.empty_like(X, device=self.device, dtype=th.float32)
|
|
583
|
+
_buf_V = th.empty_like(V, device=self.device, dtype=th.float32)
|
|
584
|
+
_buf_F = th.empty_like(X, device=self.device, dtype=th.float32)
|
|
585
|
+
# initialize the dumper
|
|
586
|
+
X_arr = X.numpy(force=True)
|
|
587
|
+
_x_dtype = X_arr.dtype.str
|
|
588
|
+
atom_masks_arr = atom_masks.numpy(force=True).astype(_x_dtype)
|
|
589
|
+
_num_dump = math.ceil(self.max_step/self.output_structures_per_step)
|
|
590
|
+
dumper = self.dumper
|
|
591
|
+
# write head information.
|
|
592
|
+
if Cell_vector is None:
|
|
593
|
+
Cell_vector = np.zeros((n_true_batch, 3, 3), dtype=np.float32)
|
|
594
|
+
elif isinstance(Cell_vector, th.Tensor):
|
|
595
|
+
Cell_vector = Cell_vector.numpy(force=True)
|
|
596
|
+
elif not isinstance(Cell_vector, np.ndarray):
|
|
597
|
+
Cell_vector = np.asarray(Cell_vector)
|
|
598
|
+
if self.batch_tensor is not None:
|
|
599
|
+
dumper.start_from_arrays(
|
|
600
|
+
1,
|
|
601
|
+
self.batch_tensor.numpy(force=True), # batch indices
|
|
602
|
+
Cell_vector, # cell
|
|
603
|
+
np.asarray(atomic_numbers), # element type / atomic number
|
|
604
|
+
atom_masks_arr, # fixation mask
|
|
605
|
+
)
|
|
606
|
+
dumper.step(
|
|
607
|
+
self.batch_tensor.numpy(force=True),
|
|
608
|
+
Cell_vector,
|
|
609
|
+
np.asarray(atomic_numbers),
|
|
610
|
+
atom_masks_arr
|
|
611
|
+
)
|
|
612
|
+
else:
|
|
613
|
+
dumper.start_from_arrays(
|
|
614
|
+
1,
|
|
615
|
+
Cell_vector,
|
|
616
|
+
np.asarray(atomic_numbers), # element type / atomic number
|
|
617
|
+
atom_masks_arr, # fixation mask
|
|
618
|
+
)
|
|
619
|
+
dumper.step(
|
|
620
|
+
Cell_vector,
|
|
621
|
+
np.asarray(atomic_numbers),
|
|
622
|
+
atom_masks_arr
|
|
623
|
+
)
|
|
624
|
+
# continue to write main data
|
|
625
|
+
dumper.start_from_arrays(
|
|
626
|
+
_num_dump,
|
|
627
|
+
_print_Ep.numpy(),
|
|
628
|
+
_print_X.numpy(),
|
|
629
|
+
_print_V.numpy(),
|
|
630
|
+
_print_F.numpy(),
|
|
631
|
+
)
|
|
632
|
+
# custom initialization
|
|
633
|
+
self.initialize(
|
|
634
|
+
func,
|
|
635
|
+
X,
|
|
636
|
+
Element_list,
|
|
637
|
+
masses,
|
|
638
|
+
V,
|
|
639
|
+
grad_func,
|
|
640
|
+
func_args,
|
|
641
|
+
func_kwargs,
|
|
642
|
+
grad_func_args,
|
|
643
|
+
grad_func_kwargs,
|
|
644
|
+
is_grad_func_contain_y,
|
|
645
|
+
require_grad,
|
|
646
|
+
batch_indices,
|
|
647
|
+
fixed_atom_tensor,
|
|
648
|
+
is_fix_mass_center
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
# print Atoms Information
|
|
652
|
+
# if has no handler, means the handler is upper level 'Main', thus not print repeatedly
|
|
653
|
+
if (self.verbose > 0) and len(self.logger.handlers) > 0:
|
|
654
|
+
self._print_elem_info(Element_list, batch_indices)
|
|
655
|
+
|
|
656
|
+
# MAIN Loop
|
|
657
|
+
with th.no_grad():
|
|
658
|
+
X = X.contiguous()
|
|
659
|
+
V = V.contiguous()
|
|
660
|
+
masses = masses.contiguous()
|
|
661
|
+
atom_masks = atom_masks.contiguous()
|
|
662
|
+
t_step = time.perf_counter()
|
|
663
|
+
t_main_loop = time.perf_counter()
|
|
664
|
+
with th.set_grad_enabled(require_grad):
|
|
665
|
+
X.requires_grad_(require_grad)
|
|
666
|
+
Energy = func(X, *func_args, **func_kwargs) # Note: func must return th.Tensor(n_batch, )
|
|
667
|
+
if batch_indices is not None:
|
|
668
|
+
if len(Energy) != len(batch_indices): # check batch number of output
|
|
669
|
+
raise RuntimeError(
|
|
670
|
+
f'batch number of `func` output ({len(Energy)}) does not match the input `batch_indices` ({len(batch_indices)}'
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
if is_grad_func_contain_y:
|
|
674
|
+
Forces = - grad_func_(X, Energy, *grad_func_args, **grad_func_kwargs) * atom_masks
|
|
675
|
+
else:
|
|
676
|
+
Forces = - grad_func_(X, *grad_func_args, **grad_func_kwargs) * atom_masks
|
|
677
|
+
Forces = Forces.contiguous()
|
|
678
|
+
|
|
679
|
+
Ek = th.zeros_like(Energy)
|
|
680
|
+
temperature = th.zeros_like(Energy)
|
|
681
|
+
# preload a graph of Ek, T
|
|
682
|
+
Ek_T_graph = th.cuda.CUDAGraph()
|
|
683
|
+
with th.cuda.graph(Ek_T_graph):
|
|
684
|
+
_e_tmp, _t_tmp = self._reduce_Ek_T(batch_indices, masses, V)
|
|
685
|
+
Ek.copy_(_e_tmp)
|
|
686
|
+
temperature.copy_(_t_tmp)
|
|
687
|
+
# preload a graph of mass center
|
|
688
|
+
if is_fix_mass_center:
|
|
689
|
+
mass_center_graph = th.cuda.CUDAGraph()
|
|
690
|
+
with th.cuda.graph(mass_center_graph):
|
|
691
|
+
_dX = MASS_CENTER - self.calc_mass_center(masses, masses_short, X)
|
|
692
|
+
_dV = - self.calc_mass_center(masses, masses_short, V)
|
|
693
|
+
X.add_(_dX) # (n_batch, n_atom, n_dim) - (n_batch, 1, n_dim)
|
|
694
|
+
V.add_(_dV)
|
|
695
|
+
else:
|
|
696
|
+
mass_center_graph = None
|
|
697
|
+
# preload Verlet update
|
|
698
|
+
#self.Verlet1_graph = th.cuda.CUDAGraph()
|
|
699
|
+
#with th.cuda.graph(self.Verlet1_graph):
|
|
700
|
+
# V.addcdiv_(Forces, masses, value=0.5 * self.time_step * 9.64853329045427e-3)
|
|
701
|
+
# X.add_(V, alpha=self.time_step)
|
|
702
|
+
#self.Verlet2_graph = th.cuda.CUDAGraph()
|
|
703
|
+
#with th.cuda.graph(self.Verlet2_graph):
|
|
704
|
+
# Forces.mul_(atom_masks)
|
|
705
|
+
# # V = V + (Force / (2. * masses)) * self.time_step * 9.64853329045427e-3
|
|
706
|
+
# V.addcdiv_(Forces, masses, value=0.5 * self.time_step * 9.64853329045427e-3)
|
|
707
|
+
|
|
708
|
+
copy_stream = th.cuda.Stream()
|
|
709
|
+
copy_event = th.cuda.Event()
|
|
710
|
+
compute_event = th.cuda.Event()
|
|
711
|
+
compute_event.record(th.cuda.default_stream(self.device)) # the default stream is the compute (main) stream.
|
|
712
|
+
# launch the dumping thread
|
|
713
|
+
dump_queue = queue.Queue()
|
|
714
|
+
dump_thread = threading.Thread(target=self._do_async_dump, args=(dump_queue, ), daemon=True)
|
|
715
|
+
logout_queue = queue.Queue()
|
|
716
|
+
logout_thread = threading.Thread(target=self._do_async_print, args=(logout_queue, ), daemon=True)
|
|
717
|
+
try:
|
|
718
|
+
dump_thread.start()
|
|
719
|
+
logout_thread.start()
|
|
720
|
+
#ptlist = list() # test <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
721
|
+
fl = th.compile(self._main_for_loop_cuda, **self.compile_kwargs, disable=(not self.is_compile))
|
|
722
|
+
fl(
|
|
723
|
+
Ek_T_graph,
|
|
724
|
+
Ek,
|
|
725
|
+
Energy,
|
|
726
|
+
X,
|
|
727
|
+
V,
|
|
728
|
+
Forces,
|
|
729
|
+
compute_event,
|
|
730
|
+
temperature,
|
|
731
|
+
copy_stream,
|
|
732
|
+
_buf_Tp,
|
|
733
|
+
_buf_Ek,
|
|
734
|
+
_buf_Ep,
|
|
735
|
+
_buf_X,
|
|
736
|
+
_buf_V,
|
|
737
|
+
_buf_F,
|
|
738
|
+
_print_temperature,
|
|
739
|
+
_print_Ek,
|
|
740
|
+
_print_Ep,
|
|
741
|
+
_print_X,
|
|
742
|
+
_print_V,
|
|
743
|
+
_print_F,
|
|
744
|
+
copy_event,
|
|
745
|
+
dump_queue,
|
|
746
|
+
dumper,
|
|
747
|
+
logout_queue,
|
|
748
|
+
func, grad_func_, func_args, func_kwargs, grad_func_args, grad_func_kwargs,
|
|
749
|
+
masses, atom_masks, is_grad_func_contain_y, batch_indices,
|
|
750
|
+
is_fix_mass_center,
|
|
751
|
+
move_to_center_freq,
|
|
752
|
+
mass_center_graph
|
|
753
|
+
)
|
|
754
|
+
if not self._HOLD_DUMPER:
|
|
755
|
+
dumper.close()
|
|
756
|
+
th.cuda.synchronize()
|
|
757
|
+
if self.verbose > 0:
|
|
758
|
+
self.logger.info(f'MAIN LOOP DONE. Elapsed time: {time.perf_counter() - t_main_loop:>5.4f} s')
|
|
759
|
+
finally:
|
|
760
|
+
dump_queue.put([None]*6)
|
|
761
|
+
dump_thread.join()
|
|
762
|
+
logout_queue.put([None]*8)
|
|
763
|
+
logout_thread.join()
|
|
764
|
+
|
|
765
|
+
del self.Ekt_vir
|
|
766
|
+
#return ptlist # test <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
767
|
+
|
|
768
|
+
def _main_for_loop_cuda(
|
|
769
|
+
self,
|
|
770
|
+
Ek_T_graph,
|
|
771
|
+
Ek,
|
|
772
|
+
Energy,
|
|
773
|
+
X,
|
|
774
|
+
V,
|
|
775
|
+
Forces,
|
|
776
|
+
compute_event,
|
|
777
|
+
temperature,
|
|
778
|
+
copy_stream,
|
|
779
|
+
_buf_Tp,
|
|
780
|
+
_buf_Ek,
|
|
781
|
+
_buf_Ep,
|
|
782
|
+
_buf_X,
|
|
783
|
+
_buf_V,
|
|
784
|
+
_buf_F,
|
|
785
|
+
_print_temperature,
|
|
786
|
+
_print_Ek,
|
|
787
|
+
_print_Ep,
|
|
788
|
+
_print_X,
|
|
789
|
+
_print_V,
|
|
790
|
+
_print_F,
|
|
791
|
+
copy_event,
|
|
792
|
+
dump_queue,
|
|
793
|
+
dumper,
|
|
794
|
+
logout_queue,
|
|
795
|
+
func, grad_func_, func_args, func_kwargs, grad_func_args, grad_func_kwargs,
|
|
796
|
+
masses, atom_masks, is_grad_func_contain_y, batch_indices,
|
|
797
|
+
is_fix_mass_center,
|
|
798
|
+
move_to_center_freq,
|
|
799
|
+
mass_center_graph
|
|
800
|
+
):
|
|
801
|
+
#t_step = time.perf_counter()
|
|
802
|
+
_do_print = False
|
|
803
|
+
for i in range(self.max_step):
|
|
804
|
+
#with th.profiler.record_function('Calc. E, T <<<<<'):
|
|
805
|
+
Ek_T_graph.replay()
|
|
806
|
+
# Ek, temperature = self._reduce_Ek_T(batch_indices, masses, V)
|
|
807
|
+
self.Ek = Ek # th.sum(Ek, dim=0) # saving the real kinetic energy for VR & CSVR to avoid double counting.
|
|
808
|
+
# if self.verbose > 0:
|
|
809
|
+
#with th.profiler.record_function('D2H COPY <<<<<'):
|
|
810
|
+
if i % self.output_structures_per_step == 0:
|
|
811
|
+
_do_print = True
|
|
812
|
+
compute_event.wait(th.cuda.default_stream(self.device))
|
|
813
|
+
# D2D, fast copy purely on GPU
|
|
814
|
+
_buf_Tp.copy_(temperature.squeeze().contiguous())
|
|
815
|
+
_buf_Ek.copy_(Ek.squeeze().contiguous())
|
|
816
|
+
_buf_Ep.copy_(Energy.squeeze().contiguous())
|
|
817
|
+
_buf_X.copy_(X)
|
|
818
|
+
_buf_V.copy_(V)
|
|
819
|
+
_buf_F.copy_(Forces)
|
|
820
|
+
# D2H, async.
|
|
821
|
+
with th.cuda.stream(copy_stream):
|
|
822
|
+
copy_stream.wait_stream(th.cuda.default_stream(self.device))
|
|
823
|
+
_print_temperature.copy_(_buf_Tp, non_blocking=True)
|
|
824
|
+
_print_Ek.copy_(_buf_Ek, non_blocking=True)
|
|
825
|
+
_print_Ep.copy_(_buf_Ep, non_blocking=True)
|
|
826
|
+
_print_X.copy_(_buf_X, non_blocking=True) # D2H
|
|
827
|
+
_print_V.copy_(_buf_V, non_blocking=True)
|
|
828
|
+
_print_F.copy_(_buf_F, non_blocking=True)
|
|
829
|
+
# ptlist.append(X.numpy(force=True)) # test <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
830
|
+
copy_event.record(copy_stream)
|
|
831
|
+
# use backend thread to dump
|
|
832
|
+
dump_queue.put((dumper, copy_event, _print_Ep, _print_X, _print_V, _print_F))
|
|
833
|
+
|
|
834
|
+
# Update X, V
|
|
835
|
+
#with th.profiler.record_function('MAIN UPDATE <<<<<'):
|
|
836
|
+
X, V, Energy, Forces = self._updateXV(
|
|
837
|
+
X, V, Forces,
|
|
838
|
+
func, grad_func_, func_args, func_kwargs, grad_func_args, grad_func_kwargs,
|
|
839
|
+
masses, atom_masks, is_grad_func_contain_y, batch_indices
|
|
840
|
+
)
|
|
841
|
+
compute_event.record(th.cuda.default_stream(self.device)) # the default stream is the compute (main) stream.
|
|
842
|
+
# print
|
|
843
|
+
#with th.profiler.record_function('PRINT <<<<<'):
|
|
844
|
+
if _do_print:
|
|
845
|
+
logout_queue.put((i, batch_indices, _print_Ep, _print_Ek, _print_temperature, _print_X, _print_V, _print_F))
|
|
846
|
+
_do_print = False
|
|
847
|
+
|
|
848
|
+
# Correct barycentric transition
|
|
849
|
+
#with th.profiler.record_function('MOVE TO CENTER <<<<<'):
|
|
850
|
+
if is_fix_mass_center and (i % move_to_center_freq == 0):
|
|
851
|
+
mass_center_graph.replay()
|
|
852
|
+
self.time_now += self.time_step
|
|
853
|
+
|
|
854
|
+
def __run_on_cpu(
|
|
855
|
+
self,
|
|
856
|
+
func: Any | nn.Module,
|
|
857
|
+
X: th.Tensor,
|
|
858
|
+
Element_list: List[List[str]] | List[List[int]],
|
|
859
|
+
Cell_vector: th.Tensor | None = None,
|
|
860
|
+
V_init: th.Tensor | None = None,
|
|
861
|
+
grad_func: Any | nn.Module = None,
|
|
862
|
+
func_args: Sequence = tuple(),
|
|
863
|
+
func_kwargs: Dict | None = None,
|
|
864
|
+
grad_func_args: Sequence = tuple(),
|
|
865
|
+
grad_func_kwargs: Dict | None = None,
|
|
866
|
+
is_grad_func_contain_y: bool = True,
|
|
867
|
+
require_grad: bool = False,
|
|
868
|
+
batch_indices: List[int] | Tuple[int, ...] | th.Tensor | np.ndarray | None = None,
|
|
869
|
+
fixed_atom_tensor: Optional[th.Tensor] = None,
|
|
870
|
+
move_to_center_freq: int = -1
|
|
871
|
+
) -> None:
|
|
872
|
+
"""
|
|
873
|
+
Pure CPU version of BaseMD.run(...).
|
|
874
|
+
"""
|
|
875
|
+
if len(X.shape) == 2:
|
|
876
|
+
X = X.unsqueeze(0)
|
|
877
|
+
elif len(X.shape) != 3:
|
|
878
|
+
raise ValueError(f'`X` must be 2D or 3D, but got shape [{X.shape}]')
|
|
879
|
+
if not isinstance(move_to_center_freq, int):
|
|
880
|
+
raise TypeError(f'`move_to_center_freq` must be an integer, but got {type(move_to_center_freq)}.')
|
|
881
|
+
elif move_to_center_freq <= 0:
|
|
882
|
+
is_fix_mass_center = False
|
|
883
|
+
else:
|
|
884
|
+
is_fix_mass_center = True
|
|
885
|
+
n_batch, n_atom, n_dim = X.shape
|
|
886
|
+
if func_kwargs is None: func_kwargs = dict()
|
|
887
|
+
if grad_func_kwargs is None: grad_func_kwargs = dict()
|
|
888
|
+
# Check batch indices
|
|
889
|
+
if batch_indices is not None:
|
|
890
|
+
if n_batch != 1:
|
|
891
|
+
raise RuntimeError(f'If batch_indices was specified, the 1st dimension of X must be 1 instead of {n_batch}.')
|
|
892
|
+
if isinstance(batch_indices, (th.Tensor, np.ndarray)):
|
|
893
|
+
self.batch_tensor = batch_indices
|
|
894
|
+
batch_indices = batch_indices.tolist()
|
|
895
|
+
elif not isinstance(batch_indices, (List, Tuple)):
|
|
896
|
+
raise TypeError(f'Invalid type of batch_indices {type(batch_indices)}. '
|
|
897
|
+
f'It must be List[int] | Tuple[int, ...] | th.Tensor | np.ndarray | None')
|
|
898
|
+
for i in batch_indices: assert isinstance(i, int), f'All elements in batch_indices must be int, but occurred {type(i)}'
|
|
899
|
+
self.batch_tensor = th.as_tensor(batch_indices, device=self.device) # the tensor version of batch_indices which is a List.
|
|
900
|
+
self.batch_scatter = th.repeat_interleave(
|
|
901
|
+
th.arange(0, len(batch_indices), dtype=th.int64, device=self.device),
|
|
902
|
+
self.batch_tensor,
|
|
903
|
+
dim=0
|
|
904
|
+
) # scatter mask of the int tensor with the same shape as X.shape[1], which the data in one batch have one index.
|
|
905
|
+
self.scatter_dim_out_size = self.batch_scatter.max().item() + 1
|
|
906
|
+
n_true_batch = len(batch_indices)
|
|
907
|
+
else:
|
|
908
|
+
n_true_batch = n_batch
|
|
909
|
+
|
|
910
|
+
# Manage Atomic Type & Masses
|
|
911
|
+
masses = list()
|
|
912
|
+
atomic_numbers = list()
|
|
913
|
+
for _Elem in Element_list:
|
|
914
|
+
if not isinstance(_Elem, list): raise TypeError(f'Expected `Element_list` of List[List[int|str]], but got List[{type(_Elem)}].')
|
|
915
|
+
atomic_numbers.append([ATOMIC_SYMBOL[__elem] if isinstance(__elem, str) else ATOMIC_NUMBER[__elem] for __elem in _Elem])
|
|
916
|
+
masses.append([MASS[__elem] if isinstance(__elem, str) else N_MASS[__elem] for __elem in _Elem])
|
|
917
|
+
masses_short = th.tensor(masses, dtype=th.float32, device=self.device) # (n_batch, n_atom)
|
|
918
|
+
masses = masses_short.unsqueeze(-1).expand_as(X).contiguous() # (n_batch, n_atom, n_dim)
|
|
919
|
+
# grad_func
|
|
920
|
+
if grad_func is None:
|
|
921
|
+
is_grad_func_contain_y = True
|
|
922
|
+
def grad_func_(x, y, grad_shape=None):
|
|
923
|
+
if grad_shape is None:
|
|
924
|
+
grad_shape = th.ones_like(y)
|
|
925
|
+
g = th.autograd.grad(y, x, grad_shape)
|
|
926
|
+
return g[0]
|
|
927
|
+
else:
|
|
928
|
+
grad_func_ = grad_func
|
|
929
|
+
# Selective dynamics
|
|
930
|
+
if fixed_atom_tensor is None:
|
|
931
|
+
atom_masks = th.ones_like(X, device=self.device)
|
|
932
|
+
elif fixed_atom_tensor.shape == X.shape:
|
|
933
|
+
atom_masks = fixed_atom_tensor.to(self.device)
|
|
934
|
+
else:
|
|
935
|
+
raise RuntimeError(f'fixed_atom_tensor (shape: {fixed_atom_tensor.shape}) does not match X (shape: {X.shape}).')
|
|
936
|
+
# other check
|
|
937
|
+
if (not isinstance(self.max_step, int)) or (self.max_step <= 0):
|
|
938
|
+
raise ValueError(f'Invalid value of maxiter: {self.max_step}. It would be an integer greater than 0.')
|
|
939
|
+
|
|
940
|
+
# set variables device
|
|
941
|
+
func = preload_func(func, self.device)
|
|
942
|
+
|
|
943
|
+
if isinstance(grad_func_, nn.Module):
|
|
944
|
+
grad_func_ = grad_func_.to(self.device)
|
|
945
|
+
X = X.to(self.device)
|
|
946
|
+
# calc. freedom degree
|
|
947
|
+
if batch_indices is None:
|
|
948
|
+
_free_degree = X.shape[1] * n_dim
|
|
949
|
+
if is_fix_mass_center:
|
|
950
|
+
_free_degree -= 3
|
|
951
|
+
# initialize topologie
|
|
952
|
+
MASS_SUM = th.sum(masses_short, dim=1, keepdim=True).unsqueeze(-2) # (n_batch, 1, 1)
|
|
953
|
+
MASS_CENTER = th.sum(X * masses, dim=1, keepdim=True)/MASS_SUM # (n_batch, 1, n_dim)
|
|
954
|
+
else:
|
|
955
|
+
MASS_CENTER = None
|
|
956
|
+
self.free_degree = th.full((n_batch, ), _free_degree, dtype=th.int64, device=self.device)
|
|
957
|
+
n_reduce = th.where(th.abs(atom_masks) < 1e-6, 1, 0).sum(dim=(-2, -1)) # (n_batch, )
|
|
958
|
+
self.free_degree -= n_reduce
|
|
959
|
+
else:
|
|
960
|
+
self.free_degree = self.batch_tensor * n_dim # (n_batch, )
|
|
961
|
+
if is_fix_mass_center:
|
|
962
|
+
self.free_degree -= 3
|
|
963
|
+
# initialize topologie
|
|
964
|
+
MASS_SUM = index_reduce(masses_short, self.batch_scatter, dim=1, out_size=self.scatter_dim_out_size).unsqueeze(-1) # (1, n_batch, 1)
|
|
965
|
+
MASS_CENTER = index_reduce(X * masses, self.batch_scatter, dim=1, out_size=self.scatter_dim_out_size)/MASS_SUM # (1, n_batch, n_dim)
|
|
966
|
+
MASS_CENTER = MASS_CENTER.index_select(1, self.batch_scatter) # (1, sumN*A, n_dim)
|
|
967
|
+
else:
|
|
968
|
+
MASS_CENTER = None
|
|
969
|
+
n_reduce_tensor = th.where(th.abs(atom_masks) < 1e-6, 1, 0).sum(dim=-1)
|
|
970
|
+
n_reduce = index_reduce(n_reduce_tensor, self.batch_scatter, dim=1, out_size=self.scatter_dim_out_size).squeeze(0) # (n_batch, )
|
|
971
|
+
self.free_degree -= n_reduce
|
|
972
|
+
# target kinetic energy for NVT|NPT ensembles
|
|
973
|
+
self.EK_TARGET = (self.free_degree / 2.) * 8.617333262145e-5 * self.T_init
|
|
974
|
+
# Generate initial Velocities
|
|
975
|
+
if V_init is not None:
|
|
976
|
+
if V_init.shape != X.shape:
|
|
977
|
+
raise ValueError(f'V_init and X must have the same shape, but got {V_init.shape} and {X.shape}')
|
|
978
|
+
if self.verbose > 0: self.logger.info('Initial velocities was given, T_init will be ignored.')
|
|
979
|
+
V_init = V_init.to(self.device)
|
|
980
|
+
V = V_init.detach() * atom_masks
|
|
981
|
+
V = V.to(self.device)
|
|
982
|
+
else:
|
|
983
|
+
V = th.normal(
|
|
984
|
+
0.,
|
|
985
|
+
th.sqrt(self.T_init * 8314.46261815324 / masses) * 1.e-5, # Unit: Ang/fs, R = kB * L = 8.31446261815324 J/(mol * K)
|
|
986
|
+
) * atom_masks
|
|
987
|
+
# remove translation veloc.
|
|
988
|
+
if is_fix_mass_center:
|
|
989
|
+
V.sub_(self.calc_mass_center(masses, masses_short, V))
|
|
990
|
+
# split by batch_indices
|
|
991
|
+
if batch_indices is not None:
|
|
992
|
+
masses_tup = th.split(masses, batch_indices, dim=1)
|
|
993
|
+
V_tup = th.split(V, batch_indices, dim=1)
|
|
994
|
+
else:
|
|
995
|
+
masses_tup = (masses,)
|
|
996
|
+
V_tup = (V,)
|
|
997
|
+
|
|
998
|
+
# initialize thermostat parameters
|
|
999
|
+
# (n_batch, ), eV/atom. The initial virtual Ek_t for CSVR.
|
|
1000
|
+
self.Ekt_vir = th.cat(
|
|
1001
|
+
[0.5 * th.sum(_m * V_tup[_] ** 2, dim=(-2, -1)) * 103.642696562621738 for _, _m in enumerate(masses_tup)]
|
|
1002
|
+
)
|
|
1003
|
+
# The initial iota for Nose-Hoover
|
|
1004
|
+
if batch_indices is not None:
|
|
1005
|
+
self.p_iota = th.zeros(1, len(batch_indices), 1, device=self.device, dtype=th.float32)
|
|
1006
|
+
else:
|
|
1007
|
+
self.p_iota = th.zeros(n_batch, 1, 1, device=self.device, dtype=th.float32)
|
|
1008
|
+
# whether grad needs autograd
|
|
1009
|
+
self.require_grad = require_grad
|
|
1010
|
+
|
|
1011
|
+
# initialize the dumper
|
|
1012
|
+
X_arr = X.numpy(force=True)
|
|
1013
|
+
_x_dtype = X_arr.dtype.str
|
|
1014
|
+
atom_masks_arr = atom_masks.numpy(force=True).astype(_x_dtype)
|
|
1015
|
+
# Note: cache_size: NOW it be hard coded as 4 MB
|
|
1016
|
+
_num_dump = math.ceil(self.max_step/self.output_structures_per_step)
|
|
1017
|
+
dumper = self.dumper
|
|
1018
|
+
if Cell_vector is None:
|
|
1019
|
+
Cell_vector = np.zeros((n_true_batch, 3, 3), dtype=np.float32)
|
|
1020
|
+
elif isinstance(Cell_vector, th.Tensor):
|
|
1021
|
+
Cell_vector = Cell_vector.numpy(force=True)
|
|
1022
|
+
elif not isinstance(Cell_vector, np.ndarray):
|
|
1023
|
+
Cell_vector = np.asarray(Cell_vector)
|
|
1024
|
+
# write head information.
|
|
1025
|
+
if self.batch_tensor is not None:
|
|
1026
|
+
dumper.start_from_arrays(
|
|
1027
|
+
1,
|
|
1028
|
+
self.batch_tensor.numpy(force=True), # batch indices
|
|
1029
|
+
Cell_vector,
|
|
1030
|
+
np.asarray(atomic_numbers), # element type / atomic number
|
|
1031
|
+
atom_masks_arr, # fixation mask
|
|
1032
|
+
)
|
|
1033
|
+
dumper.step(
|
|
1034
|
+
self.batch_tensor.numpy(force=True),
|
|
1035
|
+
Cell_vector,
|
|
1036
|
+
np.asarray(atomic_numbers),
|
|
1037
|
+
atom_masks_arr
|
|
1038
|
+
)
|
|
1039
|
+
else:
|
|
1040
|
+
dumper.start_from_arrays(
|
|
1041
|
+
1,
|
|
1042
|
+
Cell_vector,
|
|
1043
|
+
np.asarray(atomic_numbers), # element type / atomic number
|
|
1044
|
+
atom_masks_arr, # fixation mask
|
|
1045
|
+
)
|
|
1046
|
+
dumper.step(
|
|
1047
|
+
Cell_vector,
|
|
1048
|
+
np.asarray(atomic_numbers),
|
|
1049
|
+
atom_masks_arr
|
|
1050
|
+
)
|
|
1051
|
+
# continue to write main data
|
|
1052
|
+
dumper.start_from_arrays(
|
|
1053
|
+
_num_dump,
|
|
1054
|
+
self.Ekt_vir.numpy(),
|
|
1055
|
+
X.numpy(),
|
|
1056
|
+
V.numpy(),
|
|
1057
|
+
V.numpy(), # Here F is not calculated yet, but V has the same shape, hence can do this replacement
|
|
1058
|
+
)
|
|
1059
|
+
# custom initialization
|
|
1060
|
+
self.initialize(
|
|
1061
|
+
func,
|
|
1062
|
+
X,
|
|
1063
|
+
Element_list,
|
|
1064
|
+
masses,
|
|
1065
|
+
V,
|
|
1066
|
+
grad_func,
|
|
1067
|
+
func_args,
|
|
1068
|
+
func_kwargs,
|
|
1069
|
+
grad_func_args,
|
|
1070
|
+
grad_func_kwargs,
|
|
1071
|
+
is_grad_func_contain_y,
|
|
1072
|
+
require_grad,
|
|
1073
|
+
batch_indices,
|
|
1074
|
+
fixed_atom_tensor,
|
|
1075
|
+
is_fix_mass_center
|
|
1076
|
+
)
|
|
1077
|
+
|
|
1078
|
+
# print Atoms Information
|
|
1079
|
+
# if has no handler, means the handler is upper level 'Main', thus not print repeatedly
|
|
1080
|
+
if (self.verbose > 0) and len(self.logger.handlers) > 0:
|
|
1081
|
+
self._print_elem_info(Element_list, batch_indices)
|
|
1082
|
+
formatter1 = {'float': '{:> .2f}'.format}
|
|
1083
|
+
formatter2 = {'float': '{:> 5.10f}'.format}
|
|
1084
|
+
|
|
1085
|
+
# MAIN Loop
|
|
1086
|
+
with th.no_grad():
|
|
1087
|
+
X = X.contiguous()
|
|
1088
|
+
V = V.contiguous()
|
|
1089
|
+
masses = masses.contiguous()
|
|
1090
|
+
atom_masks = atom_masks.contiguous()
|
|
1091
|
+
|
|
1092
|
+
t_in = time.perf_counter()
|
|
1093
|
+
with th.set_grad_enabled(require_grad):
|
|
1094
|
+
X.requires_grad_(require_grad)
|
|
1095
|
+
Energy = func(X, *func_args, **func_kwargs) # Note: func must return th.Tensor(n_batch, )
|
|
1096
|
+
if batch_indices is not None:
|
|
1097
|
+
if len(Energy) != len(batch_indices): # check batch number of output
|
|
1098
|
+
raise RuntimeError(
|
|
1099
|
+
f'batch number of `func` output ({len(Energy)}) does not match the input `batch_indices` ({len(batch_indices)}'
|
|
1100
|
+
)
|
|
1101
|
+
|
|
1102
|
+
if is_grad_func_contain_y:
|
|
1103
|
+
Forces = - grad_func_(X, Energy, *grad_func_args, **grad_func_kwargs) * atom_masks
|
|
1104
|
+
else:
|
|
1105
|
+
Forces = - grad_func_(X, *grad_func_args, **grad_func_kwargs) * atom_masks
|
|
1106
|
+
Forces = Forces.contiguous()
|
|
1107
|
+
|
|
1108
|
+
#ptlist = list() # test <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
1109
|
+
fl = th.compile(self._main_for_loop_cpu, **self.compile_kwargs, disable=(not self.is_compile))
|
|
1110
|
+
fl(
|
|
1111
|
+
Energy,
|
|
1112
|
+
X,
|
|
1113
|
+
V,
|
|
1114
|
+
Forces,
|
|
1115
|
+
dumper,
|
|
1116
|
+
func, grad_func_, func_args, func_kwargs, grad_func_args, grad_func_kwargs,
|
|
1117
|
+
masses, atom_masks, is_grad_func_contain_y, batch_indices,
|
|
1118
|
+
MASS_CENTER,
|
|
1119
|
+
masses_short,
|
|
1120
|
+
formatter1,
|
|
1121
|
+
formatter2,
|
|
1122
|
+
is_fix_mass_center,
|
|
1123
|
+
move_to_center_freq,
|
|
1124
|
+
)
|
|
1125
|
+
if not self._HOLD_DUMPER:
|
|
1126
|
+
dumper.close()
|
|
1127
|
+
|
|
1128
|
+
del self.Ekt_vir
|
|
1129
|
+
#return ptlist # test <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
1130
|
+
|
|
1131
|
+
def _main_for_loop_cpu(
|
|
1132
|
+
self,
|
|
1133
|
+
Energy,
|
|
1134
|
+
X,
|
|
1135
|
+
V,
|
|
1136
|
+
Forces,
|
|
1137
|
+
dumper,
|
|
1138
|
+
func, grad_func_, func_args, func_kwargs, grad_func_args, grad_func_kwargs,
|
|
1139
|
+
masses, atom_masks, is_grad_func_contain_y, batch_indices,
|
|
1140
|
+
MASS_CENTER,
|
|
1141
|
+
masses_short,
|
|
1142
|
+
formatter1,
|
|
1143
|
+
formatter2,
|
|
1144
|
+
is_fix_mass_center,
|
|
1145
|
+
move_to_center_freq,
|
|
1146
|
+
):
|
|
1147
|
+
_do_print = False
|
|
1148
|
+
for i in range(self.max_step):
|
|
1149
|
+
#with th.profiler.record_function('Calc. E, T <<<<<'):
|
|
1150
|
+
Ek, temperature = self._reduce_Ek_T(batch_indices, masses, V)
|
|
1151
|
+
self.Ek = Ek # th.sum(Ek, dim=0) # saving the real kinetic energy for VR & CSVR to avoid double counting.
|
|
1152
|
+
# if self.verbose > 0:
|
|
1153
|
+
#with th.profiler.record_function('DUMPING <<<<<'):
|
|
1154
|
+
if i % self.output_structures_per_step == 0:
|
|
1155
|
+
_do_print = True
|
|
1156
|
+
dumper.step(
|
|
1157
|
+
Energy.numpy(),
|
|
1158
|
+
X.numpy(),
|
|
1159
|
+
V.numpy(),
|
|
1160
|
+
Forces.numpy(),
|
|
1161
|
+
)
|
|
1162
|
+
# print, ensure print data correspond to dumping data, and then update. differ from cuda orders.
|
|
1163
|
+
#with th.profiler.record_function('PRINT <<<<<'):
|
|
1164
|
+
if _do_print:
|
|
1165
|
+
# Note: PRINTING IS VERY EXPENSIVE !!!
|
|
1166
|
+
if self.verbose > 0:
|
|
1167
|
+
# print format
|
|
1168
|
+
np.set_printoptions(
|
|
1169
|
+
precision=8,
|
|
1170
|
+
linewidth=1024,
|
|
1171
|
+
floatmode='fixed',
|
|
1172
|
+
suppress=True,
|
|
1173
|
+
formatter=formatter1,
|
|
1174
|
+
threshold=2000
|
|
1175
|
+
)
|
|
1176
|
+
self.logger.info(
|
|
1177
|
+
f'Step: {i:>12d}\n\t'
|
|
1178
|
+
f'T = {temperature.numpy(force=True)}\n\t'
|
|
1179
|
+
f'E_tol = {np.array2string((Ek + Energy).numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n\t'
|
|
1180
|
+
f'Ek = {np.array2string(Ek.numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n\t'
|
|
1181
|
+
f'Ep = {np.array2string(Energy.numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n\t'
|
|
1182
|
+
#f'Time: {time.perf_counter() - t_in:>5.4f}'
|
|
1183
|
+
)
|
|
1184
|
+
#t_in = time.perf_counter()
|
|
1185
|
+
if self.verbose > 1:
|
|
1186
|
+
# split to print
|
|
1187
|
+
if batch_indices is not None:
|
|
1188
|
+
X_tup = th.split(X, batch_indices, dim=1)
|
|
1189
|
+
V_tup = th.split(V, batch_indices, dim=1)
|
|
1190
|
+
else:
|
|
1191
|
+
X_tup = (X,)
|
|
1192
|
+
V_tup = (V,)
|
|
1193
|
+
np.set_printoptions(
|
|
1194
|
+
precision=8, floatmode='fixed', suppress=True, formatter=formatter2, threshold=3000000
|
|
1195
|
+
)
|
|
1196
|
+
self.logger.info('_' * 100)
|
|
1197
|
+
self.logger.info(f'Configuration {i}:')
|
|
1198
|
+
for __x in X_tup:
|
|
1199
|
+
X_str = np.array2string(
|
|
1200
|
+
__x.numpy(force=True), **FLOAT_ARRAY_FORMAT
|
|
1201
|
+
).replace("[", " ").replace("]", " ")
|
|
1202
|
+
self.logger.info(f'{X_str}\n')
|
|
1203
|
+
del X_str, X_tup
|
|
1204
|
+
if self.verbose > 2:
|
|
1205
|
+
self.logger.info(f'Velocities {i}:')
|
|
1206
|
+
for __x in V_tup:
|
|
1207
|
+
V_str = np.array2string(
|
|
1208
|
+
__x.numpy(force=True), **FLOAT_ARRAY_FORMAT
|
|
1209
|
+
).replace("[", " ").replace("]", " ")
|
|
1210
|
+
self.logger.info(f'{V_str}\n')
|
|
1211
|
+
del V_str
|
|
1212
|
+
self.logger.info('_' * 100)
|
|
1213
|
+
_do_print = False
|
|
1214
|
+
|
|
1215
|
+
# Update X, V
|
|
1216
|
+
#with th.profiler.record_function('MAIN UPDATE <<<<<'):
|
|
1217
|
+
X, V, Energy, Forces = self._updateXV(
|
|
1218
|
+
X, V, Forces,
|
|
1219
|
+
func, grad_func_, func_args, func_kwargs, grad_func_args, grad_func_kwargs,
|
|
1220
|
+
masses, atom_masks, is_grad_func_contain_y, batch_indices
|
|
1221
|
+
)
|
|
1222
|
+
|
|
1223
|
+
# Correct barycentric transition
|
|
1224
|
+
#with th.profiler.record_function('MOVE TO CENTER <<<<<'):
|
|
1225
|
+
if is_fix_mass_center and (i % move_to_center_freq == 0):
|
|
1226
|
+
dX = MASS_CENTER - self.calc_mass_center(masses, masses_short, X)
|
|
1227
|
+
dV = - self.calc_mass_center(masses, masses_short, V)
|
|
1228
|
+
X.add_(dX) # (n_batch, n_atom, n_dim) - (n_batch, 1, n_dim)
|
|
1229
|
+
V.add_(dV)
|
|
1230
|
+
self.time_now += self.time_step
|
|
1231
|
+
|
|
1232
|
+
# OVERRIDE THIS METHOD TO IMPLEMENT BatchMD UNDER VARIOUS ENSEMBLES.
|
|
1233
|
+
def _updateXV(
|
|
1234
|
+
self,
|
|
1235
|
+
X,
|
|
1236
|
+
V,
|
|
1237
|
+
Force,
|
|
1238
|
+
func,
|
|
1239
|
+
grad_func_,
|
|
1240
|
+
func_args,
|
|
1241
|
+
func_kwargs,
|
|
1242
|
+
grad_func_args,
|
|
1243
|
+
grad_func_kwargs,
|
|
1244
|
+
masses,
|
|
1245
|
+
atom_masks,
|
|
1246
|
+
is_grad_func_contain_y,
|
|
1247
|
+
batch_indices
|
|
1248
|
+
) -> Tuple[th.Tensor, th.Tensor, th.Tensor, th.Tensor]:
|
|
1249
|
+
""" Update X, V, Force, and return X, V, Energy, Force. """
|
|
1250
|
+
raise NotImplementedError
|
|
1251
|
+
# return X, V, Energy, Force
|