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,440 @@
|
|
|
1
|
+
""" Molecular Dynamics base framework. """
|
|
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 logging
|
|
12
|
+
|
|
13
|
+
import torch as th
|
|
14
|
+
from torch import nn
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
from BUCToolkit.utils._Element_info import MASS, N_MASS, ATOMIC_NUMBER, ATOMIC_SYMBOL
|
|
19
|
+
from BUCToolkit.utils._print_formatter import FLOAT_ARRAY_FORMAT, SCIENTIFIC_ARRAY_FORMAT
|
|
20
|
+
from BUCToolkit.api._io import DumpStructures
|
|
21
|
+
from BUCToolkit.utils._para_flatt_list import flatten
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class _rBaseMD:
|
|
25
|
+
""" Base BatchMD with regular batches """
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
time_step: float,
|
|
30
|
+
max_step: int,
|
|
31
|
+
T_init: float = 298.15,
|
|
32
|
+
output_file: str | None = None,
|
|
33
|
+
dump_path: str | None = None,
|
|
34
|
+
output_structures_per_step: int = 1,
|
|
35
|
+
device: str | th.device = 'cpu',
|
|
36
|
+
verbose: int = 2
|
|
37
|
+
):
|
|
38
|
+
"""
|
|
39
|
+
Parameters:
|
|
40
|
+
time_step: float, time per step (ps).
|
|
41
|
+
max_step: maximum steps.
|
|
42
|
+
T_init: initial temperature, only to generate initial velocities of atoms by Maxwell-Boltzmann distribution. If V_init is given, T_init will be ignored.
|
|
43
|
+
output_structures_per_step: int, output structures per output_structures_per_step steps.
|
|
44
|
+
device: device that program run on.
|
|
45
|
+
verbose: control the detailed degree of output information. 0 for silence, 1 for output Energy and Forces per step, 2 for output all structures.
|
|
46
|
+
"""
|
|
47
|
+
self.time_step = time_step
|
|
48
|
+
assert (max_step > 0) and isinstance(max_step, int), f'max_step must be a positive integer, but occurred {max_step}.'
|
|
49
|
+
self.time_now = 0. # the accumulated time
|
|
50
|
+
self.max_step = max_step
|
|
51
|
+
self.T_init = T_init
|
|
52
|
+
self.require_grad = None
|
|
53
|
+
self.output_file = output_file
|
|
54
|
+
self.output_structures_per_step = output_structures_per_step
|
|
55
|
+
self.device = device
|
|
56
|
+
self.verbose = verbose
|
|
57
|
+
|
|
58
|
+
self.EK_TARGET = None # target kinetic energy under set temperature.
|
|
59
|
+
self.Ekt_vir = None # virtual kinetic energy for _CSVR_ thermostat.
|
|
60
|
+
self.Ek = None # kinetic energy at each timestep.
|
|
61
|
+
self.p_iota = None # thermostat var. for Nose-Hoover.
|
|
62
|
+
self.n_reduce = 0 # number of reduction of free degree due to constraints
|
|
63
|
+
|
|
64
|
+
self.batch_tensor = None # tensor form of `batch_indices` if it was given.
|
|
65
|
+
self.batch_scatter = None # tensor indices form of `batch_indices` if it was given
|
|
66
|
+
# e.g., batch_indices = (3, 2, 1), thus self.scatter = tensor([0, 0, 0, 1, 1, 2])
|
|
67
|
+
|
|
68
|
+
# dumper
|
|
69
|
+
self.dump_path = dump_path
|
|
70
|
+
self.dumper = DumpStructures(dump_path, output_structures_per_step,)
|
|
71
|
+
|
|
72
|
+
# logging
|
|
73
|
+
self.logger = logging.getLogger('Main.MD')
|
|
74
|
+
self.logger.setLevel(logging.INFO)
|
|
75
|
+
formatter = logging.Formatter('%(message)s')
|
|
76
|
+
if not self.logger.hasHandlers():
|
|
77
|
+
if (output_file is None) or (output_file == ''):
|
|
78
|
+
log_handler = logging.StreamHandler(sys.stdout)
|
|
79
|
+
log_handler.setLevel(logging.INFO)
|
|
80
|
+
log_handler.setFormatter(formatter)
|
|
81
|
+
else:
|
|
82
|
+
log_handler = logging.FileHandler(output_file, 'w')
|
|
83
|
+
log_handler.setLevel(logging.INFO)
|
|
84
|
+
log_handler.setFormatter(formatter)
|
|
85
|
+
self.logger.addHandler(log_handler)
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _reduce_Ek_T(masses, V, n_atom, n_dim, n_reduce=0):
|
|
89
|
+
""" calc. Ek and T, n_reduce is the constraints reducing the system free degrees. """
|
|
90
|
+
Ek = 0.5 * th.sum(
|
|
91
|
+
masses * V ** 2,
|
|
92
|
+
dim=(-2, -1)
|
|
93
|
+
) * 103.642696562621738 # (n_batch, ), eV/atom. Faraday constant F = 96485.3321233100184.
|
|
94
|
+
temperature = (2 * Ek) / (
|
|
95
|
+
(n_dim * (n_atom - 1) - n_reduce + 1e-20) * 8.617333262145e-5
|
|
96
|
+
) # Boltzmann constant kB = 8.617333262145e-5 eV/K
|
|
97
|
+
return Ek, temperature
|
|
98
|
+
|
|
99
|
+
def _elems2masses(self, Element_list, X):
|
|
100
|
+
"""
|
|
101
|
+
convert element list to their masses tensor
|
|
102
|
+
Args:
|
|
103
|
+
Element_list:
|
|
104
|
+
|
|
105
|
+
Returns: masses and flatten atomic number list
|
|
106
|
+
|
|
107
|
+
"""
|
|
108
|
+
# Manage Atomic Type & Masses
|
|
109
|
+
masses = list()
|
|
110
|
+
atomic_number_arr = list()
|
|
111
|
+
for _Elem in Element_list:
|
|
112
|
+
masses.append([MASS[__elem] if isinstance(__elem, str) else N_MASS[__elem] for __elem in _Elem])
|
|
113
|
+
atomic_number_arr.append([ATOMIC_SYMBOL[__elem] if isinstance(__elem, str) else __elem for __elem in _Elem])
|
|
114
|
+
masses = th.tensor(masses, dtype=th.float32, device=self.device)
|
|
115
|
+
masses = masses.unsqueeze(-1).expand_as(X) # (n_batch, n_atom, n_dim)
|
|
116
|
+
atomic_number_arr = th.tensor(atomic_number_arr, dtype=th.int32, device=self.device)
|
|
117
|
+
return masses, atomic_number_arr
|
|
118
|
+
|
|
119
|
+
def _print_elems_info(self, Element_list):
|
|
120
|
+
# print Atoms Information
|
|
121
|
+
if self.verbose > 0:
|
|
122
|
+
elem_list = list()
|
|
123
|
+
_element_list = Element_list
|
|
124
|
+
for elements in _element_list:
|
|
125
|
+
__element_now = ''
|
|
126
|
+
__elem = ''
|
|
127
|
+
elem_info = ''
|
|
128
|
+
__elem_count = ''
|
|
129
|
+
for i, elem in enumerate(elements, 1):
|
|
130
|
+
# get element symbol
|
|
131
|
+
if isinstance(elem, int):
|
|
132
|
+
__elem = ATOMIC_NUMBER[elem]
|
|
133
|
+
else:
|
|
134
|
+
__elem = elem
|
|
135
|
+
# count element number
|
|
136
|
+
if __elem == __element_now:
|
|
137
|
+
__elem_count += 1
|
|
138
|
+
else:
|
|
139
|
+
elem_info = elem_info + str(__elem_count) + ' '
|
|
140
|
+
elem_info = elem_info + __elem + ': '
|
|
141
|
+
__elem_count = 1
|
|
142
|
+
__element_now = __elem
|
|
143
|
+
elem_info = elem_info + str(__elem_count)
|
|
144
|
+
elem_list.append(elem_info)
|
|
145
|
+
# log out
|
|
146
|
+
for i, ee in enumerate(elem_list):
|
|
147
|
+
self.logger.info(f'Structure {i:>5d}: {ee}')
|
|
148
|
+
|
|
149
|
+
def initialize(
|
|
150
|
+
self,
|
|
151
|
+
func: Any | nn.Module,
|
|
152
|
+
X: th.Tensor,
|
|
153
|
+
Element_list: List[List[str]] | List[List[int]],
|
|
154
|
+
masses: th.Tensor,
|
|
155
|
+
V_init: th.Tensor | None = None,
|
|
156
|
+
grad_func: Any | nn.Module = None,
|
|
157
|
+
func_args: Sequence = tuple(),
|
|
158
|
+
func_kwargs: Dict | None = None,
|
|
159
|
+
grad_func_args: Sequence = tuple(),
|
|
160
|
+
grad_func_kwargs: Dict | None = None,
|
|
161
|
+
is_grad_func_contain_y: bool = True,
|
|
162
|
+
require_grad: bool = False,
|
|
163
|
+
fixed_atom_tensor: Optional[th.Tensor] = None,
|
|
164
|
+
is_fix_mass_center: bool = False
|
|
165
|
+
):
|
|
166
|
+
"""
|
|
167
|
+
Initialization method.
|
|
168
|
+
ONE CAN OVERRIDE THIS METHOD TO IMPLEMENT CUSTOM INITIALIZATION.
|
|
169
|
+
Args is the same as input of `self.run`, appending an arg of `masses`.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
|
|
173
|
+
"""
|
|
174
|
+
# now do nothing
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
def run(
|
|
178
|
+
self,
|
|
179
|
+
func: Any | nn.Module,
|
|
180
|
+
X: th.Tensor,
|
|
181
|
+
Element_list: List[List[str]] | List[List[int]],
|
|
182
|
+
V_init: th.Tensor | None = None,
|
|
183
|
+
grad_func: Any | nn.Module = None,
|
|
184
|
+
func_args: Sequence = tuple(),
|
|
185
|
+
func_kwargs: Dict | None = None,
|
|
186
|
+
grad_func_args: Sequence = tuple(),
|
|
187
|
+
grad_func_kwargs: Dict | None = None,
|
|
188
|
+
is_grad_func_contain_y: bool = True,
|
|
189
|
+
require_grad: bool = False,
|
|
190
|
+
fixed_atom_tensor: Optional[th.Tensor] = None,
|
|
191
|
+
is_fix_mass_center: bool = False
|
|
192
|
+
) -> None:
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
Parameters:
|
|
196
|
+
func: the main function of instantiated torch.nn.Module class.
|
|
197
|
+
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.
|
|
198
|
+
Element_list: List[List[str | int]], the atomic type (element) corresponding to each row of each batch in X.
|
|
199
|
+
V_init: the initial velocities of each atom. If None, a random velocity generated by Boltzmann distribution would be set.
|
|
200
|
+
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).
|
|
201
|
+
func_args: optional, other input of func.
|
|
202
|
+
func_kwargs: optional, other input of func.
|
|
203
|
+
grad_func_args: optional, other input of grad_func.
|
|
204
|
+
grad_func_kwargs: optional, other input of grad_func.
|
|
205
|
+
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, ...)
|
|
206
|
+
require_grad: bool, if True, autograd will be turned on for func(X, *func_args, **func_kwargs) calculation.
|
|
207
|
+
fixed_atom_tensor: the mask of X that shows where to be fixed. 0 is for fixed, and 1 is for free.
|
|
208
|
+
is_fix_mass_center: whether transition coordinates and velocities of atoms into the mass center.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
min func: Tensor(n_batch, ), the minimum of func.
|
|
212
|
+
argmin func: Tensor(X.shape), the X corresponds to min func.
|
|
213
|
+
|
|
214
|
+
"""
|
|
215
|
+
t_main = time.perf_counter()
|
|
216
|
+
if len(X.shape) == 2:
|
|
217
|
+
X = X.unsqueeze(0)
|
|
218
|
+
elif len(X.shape) != 3:
|
|
219
|
+
raise ValueError(f'`X` must be 2D or 3D, but got shape [{X.shape}]')
|
|
220
|
+
n_batch, n_atom, n_dim = X.shape
|
|
221
|
+
self.batch_tensor = [n_atom, ] * n_batch
|
|
222
|
+
sample_names = [f'{i}' for i in range(n_batch)]
|
|
223
|
+
if func_kwargs is None: func_kwargs = dict()
|
|
224
|
+
if grad_func_kwargs is None: grad_func_kwargs = dict()
|
|
225
|
+
# Manage Atomic Type & Masses
|
|
226
|
+
masses, atomic_numbers = self._elems2masses(Element_list, X) # (n_batch, n_atom, n_dim)
|
|
227
|
+
# grad_func
|
|
228
|
+
if grad_func is None:
|
|
229
|
+
is_grad_func_contain_y = True
|
|
230
|
+
def grad_func_(x, y, grad_shape=None):
|
|
231
|
+
if grad_shape is None:
|
|
232
|
+
grad_shape = th.ones_like(y)
|
|
233
|
+
g = th.autograd.grad(y, x, grad_shape)
|
|
234
|
+
return g[0]
|
|
235
|
+
else:
|
|
236
|
+
grad_func_ = grad_func
|
|
237
|
+
# Selective dynamics TODO: fixed atom is not compatible with freedom degree. FIXME !!!
|
|
238
|
+
if fixed_atom_tensor is None:
|
|
239
|
+
atom_masks = th.ones_like(X, device=self.device)
|
|
240
|
+
self.n_reduce += 0
|
|
241
|
+
elif fixed_atom_tensor.shape == X.shape:
|
|
242
|
+
atom_masks = fixed_atom_tensor.to(self.device)
|
|
243
|
+
self.n_reduce += th.sum(atom_masks == 0).item()
|
|
244
|
+
else:
|
|
245
|
+
raise RuntimeError(f'fixed_atom_tensor (shape: {fixed_atom_tensor.shape}) does not match X (shape: {X.shape}).')
|
|
246
|
+
# target kinetic energy for NVT|NPT ensembles. It would be RECALCULATED under CONSTRAINTS !!
|
|
247
|
+
self.EK_TARGET = 0.5 * (n_dim * (n_atom - 1) - self.n_reduce) * 8.617333262145e-5 * self.T_init # Unit: eV/atom. Boltzmann constant kB = 8.6173332621e-5 eV/K
|
|
248
|
+
# other check
|
|
249
|
+
if (not isinstance(self.max_step, int)) or (self.max_step <= 0):
|
|
250
|
+
raise ValueError(f'Invalid value of maxiter: {self.max_step}. It would be an integer greater than 0.')
|
|
251
|
+
|
|
252
|
+
# set variables device
|
|
253
|
+
if isinstance(func, nn.Module):
|
|
254
|
+
func = func.to(self.device)
|
|
255
|
+
func.eval()
|
|
256
|
+
func.zero_grad()
|
|
257
|
+
if isinstance(grad_func_, nn.Module):
|
|
258
|
+
grad_func_ = grad_func_.to(self.device)
|
|
259
|
+
X = X.to(self.device)
|
|
260
|
+
# Generate initial Velocities
|
|
261
|
+
if V_init is not None:
|
|
262
|
+
if V_init.shape != X.shape:
|
|
263
|
+
raise ValueError(f'V_init and X must have the same shape, but got {V_init.shape} and {X.shape}')
|
|
264
|
+
if self.verbose > 0: self.logger.info('Initial velocities was given, T_init will be ignored.')
|
|
265
|
+
V_init = V_init.to(self.device)
|
|
266
|
+
V = V_init.detach() * atom_masks
|
|
267
|
+
V = V.to(self.device)
|
|
268
|
+
else:
|
|
269
|
+
V = th.normal(
|
|
270
|
+
0.,
|
|
271
|
+
th.sqrt(self.T_init * 8314.46261815324 / masses) * 1.e-5, # Unit: Ang/fs, R = kB * L = 8.31446261815324 J/(mol * K)
|
|
272
|
+
) * atom_masks
|
|
273
|
+
# split by batch_indices
|
|
274
|
+
masses_tup = (masses,)
|
|
275
|
+
V_tup = (V,)
|
|
276
|
+
masses_sum = th.sum(masses, dim=1, keepdim=True) # (n_batch, 1, n_dim)
|
|
277
|
+
|
|
278
|
+
# initialize thermostat parameters
|
|
279
|
+
# (n_batch, ), eV/atom. The initial virtual Ek_t for CSVR.
|
|
280
|
+
self.Ekt_vir = th.cat(
|
|
281
|
+
[0.5 * th.sum(_m * V_tup[_] ** 2, dim=(-2, -1)) * 103.642696562621738 for _, _m in enumerate(masses_tup)]
|
|
282
|
+
)
|
|
283
|
+
# The initial iota for Nose-Hoover
|
|
284
|
+
self.p_iota = 0.
|
|
285
|
+
# whether grad needs autograd
|
|
286
|
+
self.require_grad = require_grad
|
|
287
|
+
# custom initialization
|
|
288
|
+
self.initialize(
|
|
289
|
+
func,
|
|
290
|
+
X,
|
|
291
|
+
Element_list,
|
|
292
|
+
masses,
|
|
293
|
+
V,
|
|
294
|
+
grad_func,
|
|
295
|
+
func_args,
|
|
296
|
+
func_kwargs,
|
|
297
|
+
grad_func_args,
|
|
298
|
+
grad_func_kwargs,
|
|
299
|
+
is_grad_func_contain_y,
|
|
300
|
+
require_grad,
|
|
301
|
+
fixed_atom_tensor,
|
|
302
|
+
is_fix_mass_center
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# print Atoms Information
|
|
306
|
+
if self.verbose > 0:
|
|
307
|
+
self._print_elems_info(Element_list)
|
|
308
|
+
|
|
309
|
+
# MAIN Loop
|
|
310
|
+
with th.no_grad():
|
|
311
|
+
t_in = time.perf_counter()
|
|
312
|
+
with th.set_grad_enabled(require_grad):
|
|
313
|
+
X.requires_grad_(require_grad)
|
|
314
|
+
Energy = func(X, *func_args, **func_kwargs) # Note: func must return th.Tensor(n_batch, )
|
|
315
|
+
if is_grad_func_contain_y:
|
|
316
|
+
Forces = - grad_func_(X, Energy, *grad_func_args, **grad_func_kwargs) * atom_masks
|
|
317
|
+
else:
|
|
318
|
+
Forces = - grad_func_(X, *grad_func_args, **grad_func_kwargs) * atom_masks
|
|
319
|
+
|
|
320
|
+
ptlist = list() # test <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
321
|
+
for i in range(self.max_step):
|
|
322
|
+
# update Ek and temperature
|
|
323
|
+
Ek, temperature = self._reduce_Ek_T(masses, V, n_atom, n_dim, self.n_reduce)
|
|
324
|
+
self.Ek = Ek.squeeze() # th.sum(Ek, dim=0) # saving the real kinetic energy for VR & CSVR to avoid double counting.
|
|
325
|
+
# if self.verbose > 0:
|
|
326
|
+
if i % self.output_structures_per_step == 0:
|
|
327
|
+
# Calc. Kinetic Energy, Temperature, etc.
|
|
328
|
+
if self.verbose > 0:
|
|
329
|
+
np.set_printoptions(
|
|
330
|
+
precision = 8,
|
|
331
|
+
linewidth = 1024,
|
|
332
|
+
floatmode = 'fixed',
|
|
333
|
+
suppress = True,
|
|
334
|
+
formatter = {'float': '{:> .2f}'.format},
|
|
335
|
+
threshold = 2000
|
|
336
|
+
)
|
|
337
|
+
self.logger.info(
|
|
338
|
+
f'Step: {i:>12d}\n\t'
|
|
339
|
+
f'T = {temperature.squeeze().numpy(force=True)}\n\t'
|
|
340
|
+
f'E_tol = {np.array2string((Ek.squeeze() + Energy.squeeze()).numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n\t'
|
|
341
|
+
f'Ek = {np.array2string(Ek.squeeze().numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n\t'
|
|
342
|
+
f'Ep = {np.array2string(Energy.squeeze().numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n\t'
|
|
343
|
+
f'Time: {time.perf_counter() - t_in:>5.4f}'
|
|
344
|
+
)
|
|
345
|
+
t_in = time.perf_counter()
|
|
346
|
+
if self.verbose > 1:
|
|
347
|
+
np.set_printoptions(
|
|
348
|
+
precision=8, floatmode='fixed', suppress=True, formatter={'float': '{:> 5.10f}'.format}, threshold=3000000
|
|
349
|
+
)
|
|
350
|
+
self.logger.info('_' * 100)
|
|
351
|
+
self.logger.info(f'Configuration {i}:')
|
|
352
|
+
X_str = np.array2string(
|
|
353
|
+
X.numpy(force=True), **FLOAT_ARRAY_FORMAT
|
|
354
|
+
).replace("[", " ").replace("]", " ")
|
|
355
|
+
self.logger.info(f'{X_str}\n')
|
|
356
|
+
del X_str
|
|
357
|
+
if self.verbose > 2:
|
|
358
|
+
self.logger.info(f'Velocities {i}:')
|
|
359
|
+
V_str = np.array2string(
|
|
360
|
+
V.numpy(force=True), **FLOAT_ARRAY_FORMAT
|
|
361
|
+
).replace("[", " ").replace("]", " ")
|
|
362
|
+
self.logger.info(f'{V_str}\n')
|
|
363
|
+
del V_str
|
|
364
|
+
self.logger.info('_' * 100)
|
|
365
|
+
ptlist.append(X.numpy(force=True)) # test <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
366
|
+
|
|
367
|
+
# Update X, V
|
|
368
|
+
X, V, Energy, Forces = self._updateXV(
|
|
369
|
+
X, V, Forces,
|
|
370
|
+
func, grad_func_, func_args, func_kwargs, grad_func_args, grad_func_kwargs,
|
|
371
|
+
masses, atom_masks, is_grad_func_contain_y
|
|
372
|
+
)
|
|
373
|
+
# Correct barycentric transition TODO: solve the batch_indices problem; FIXME !!
|
|
374
|
+
if is_fix_mass_center:
|
|
375
|
+
X = X - th.sum(masses * X, dim=1, keepdim=True)/masses_sum # (n_batch, n_atom, n_dim) - (n_batch, 1, n_dim)
|
|
376
|
+
V = V - th.sum(masses * V, dim=1, keepdim=True)/masses_sum
|
|
377
|
+
# dump
|
|
378
|
+
# TODO, do a information receiver to get cells, names, etc. <<<
|
|
379
|
+
'''if self.dump_path is not None:
|
|
380
|
+
self.dumper.collect(
|
|
381
|
+
sample_names,
|
|
382
|
+
self.batch_tensor,
|
|
383
|
+
atomic_numbers,
|
|
384
|
+
|
|
385
|
+
)'''
|
|
386
|
+
|
|
387
|
+
self.time_now += self.time_step
|
|
388
|
+
|
|
389
|
+
# Finale step
|
|
390
|
+
if self.verbose > 0:
|
|
391
|
+
if self.verbose > 1:
|
|
392
|
+
np.set_printoptions(
|
|
393
|
+
precision=8, linewidth=120, floatmode='fixed', suppress=True, formatter={'float': '{:> 5.10f}'.format}, threshold=3000000
|
|
394
|
+
)
|
|
395
|
+
self.logger.info('_' * 100)
|
|
396
|
+
self.logger.info(f'Finale Configuration:')
|
|
397
|
+
X_str = np.array2string(X.numpy(force=True), **FLOAT_ARRAY_FORMAT).replace("[", " ").replace("]", " ")
|
|
398
|
+
self.logger.info(f'{X_str}\n')
|
|
399
|
+
del X_str
|
|
400
|
+
if self.verbose > 2:
|
|
401
|
+
self.logger.info(f'Finale Velocities:')
|
|
402
|
+
V_str = np.array2string(V.numpy(force=True), **FLOAT_ARRAY_FORMAT).replace("[", " ").replace("]", " ")
|
|
403
|
+
self.logger.info(f'{V_str}\n')
|
|
404
|
+
del V_str
|
|
405
|
+
self.logger.info('_' * 100)
|
|
406
|
+
th.cuda.synchronize()
|
|
407
|
+
Ek, temperature = self._reduce_Ek_T(masses, V, n_atom, n_dim)
|
|
408
|
+
self.Ek = Ek #th.sum(Ek, dim=0) # saving the real kinetic energy for VR & CSVR to avoid double counting.
|
|
409
|
+
self.logger.info(
|
|
410
|
+
f'Step: {i+1:>12d}\n\t'
|
|
411
|
+
f'T = {temperature.numpy(force=True)}\n\t'
|
|
412
|
+
f'E_tol = {np.array2string((Ek.squeeze() + Energy.squeeze()).numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n\t'
|
|
413
|
+
f'Ek = {np.array2string(Ek.numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n\t'
|
|
414
|
+
f'Ep = {np.array2string(Energy.squeeze().numpy(force=True), **SCIENTIFIC_ARRAY_FORMAT)}\n\t'
|
|
415
|
+
f'Time: {time.perf_counter() - t_in:>5.4f}'
|
|
416
|
+
)
|
|
417
|
+
self.logger.info('_' * 100 + '\n' + f'Main Loop Done. Total Time: {time.perf_counter() - t_main:>.4f}')
|
|
418
|
+
|
|
419
|
+
del self.Ekt_vir
|
|
420
|
+
return ptlist # test <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
421
|
+
|
|
422
|
+
# OVERRIDE THIS METHOD TO IMPLEMENT BatchMD UNDER VARIOUS ENSEMBLES.
|
|
423
|
+
def _updateXV(
|
|
424
|
+
self,
|
|
425
|
+
X,
|
|
426
|
+
V,
|
|
427
|
+
Force,
|
|
428
|
+
func,
|
|
429
|
+
grad_func_,
|
|
430
|
+
func_args,
|
|
431
|
+
func_kwargs,
|
|
432
|
+
grad_func_args,
|
|
433
|
+
grad_func_kwargs,
|
|
434
|
+
masses,
|
|
435
|
+
atom_masks,
|
|
436
|
+
is_grad_func_contain_y,
|
|
437
|
+
) -> (th.Tensor, th.Tensor, th.Tensor):
|
|
438
|
+
""" Update X, V, Force, and return X, V, Energy, Force. """
|
|
439
|
+
raise NotImplementedError
|
|
440
|
+
# return X, V, Energy, Force
|