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.
Files changed (88) hide show
  1. BUCToolkit/BatchGenerate/CheckStructures.py +198 -0
  2. BUCToolkit/BatchGenerate/__init__.py +6 -0
  3. BUCToolkit/BatchGenerate/coords_interp.py +127 -0
  4. BUCToolkit/BatchGenerate/initial_ads_structures.py +351 -0
  5. BUCToolkit/BatchGenerate/random_replace.py +88 -0
  6. BUCToolkit/BatchMC/MetropolisMC.py +210 -0
  7. BUCToolkit/BatchMC/_BaseMC.py +863 -0
  8. BUCToolkit/BatchMC/__init__.py +8 -0
  9. BUCToolkit/BatchMD/BiasedMD.py +276 -0
  10. BUCToolkit/BatchMD/NVE.py +92 -0
  11. BUCToolkit/BatchMD/NVT.py +510 -0
  12. BUCToolkit/BatchMD/_BaseMD.py +1251 -0
  13. BUCToolkit/BatchMD/__init__.py +18 -0
  14. BUCToolkit/BatchMD/constrained_md/BiasedMD.py +273 -0
  15. BUCToolkit/BatchMD/constrained_md/ConstrNVE.py +169 -0
  16. BUCToolkit/BatchMD/constrained_md/ConstrNVT.py +462 -0
  17. BUCToolkit/BatchMD/constrained_md/NVE.py +78 -0
  18. BUCToolkit/BatchMD/constrained_md/NVT.py +346 -0
  19. BUCToolkit/BatchMD/constrained_md/_BaseMD.py +440 -0
  20. BUCToolkit/BatchMD/constrained_md/_ConstrBaseMD.py +607 -0
  21. BUCToolkit/BatchMD/constrained_md/__init__.py +18 -0
  22. BUCToolkit/BatchOptim/TS/CI_NEB.py +286 -0
  23. BUCToolkit/BatchOptim/TS/Dimer.py +950 -0
  24. BUCToolkit/BatchOptim/TS/__init__.py +10 -0
  25. BUCToolkit/BatchOptim/_BaseOpt.py +702 -0
  26. BUCToolkit/BatchOptim/__init__.py +22 -0
  27. BUCToolkit/BatchOptim/_utils/__init__.py +6 -0
  28. BUCToolkit/BatchOptim/_utils/_line_search.py +972 -0
  29. BUCToolkit/BatchOptim/_utils/_warnings.py +11 -0
  30. BUCToolkit/BatchOptim/frequency.py +496 -0
  31. BUCToolkit/BatchOptim/minimize/CG.py +256 -0
  32. BUCToolkit/BatchOptim/minimize/FIRE.py +310 -0
  33. BUCToolkit/BatchOptim/minimize/QN.py +205 -0
  34. BUCToolkit/BatchOptim/minimize/__init__.py +17 -0
  35. BUCToolkit/BatchStructures/BatchStructuresBase.py +2261 -0
  36. BUCToolkit/BatchStructures/StructuresIO.py +1394 -0
  37. BUCToolkit/BatchStructures/__init__.py +15 -0
  38. BUCToolkit/Preprocessing/__init__.py +7 -0
  39. BUCToolkit/Preprocessing/load_files.py +1680 -0
  40. BUCToolkit/Preprocessing/preprocessing.py +923 -0
  41. BUCToolkit/Preprocessing/sampling.py +299 -0
  42. BUCToolkit/Preprocessing/supercells.py +140 -0
  43. BUCToolkit/Preprocessing/write_files.py +524 -0
  44. BUCToolkit/StandardTemplate.py +417 -0
  45. BUCToolkit/__init__.py +33 -0
  46. BUCToolkit/_version.py +7 -0
  47. BUCToolkit/api/ConstrainedMolecularDynamics.py +347 -0
  48. BUCToolkit/api/DataLoaders.py +321 -0
  49. BUCToolkit/api/Losses.py +170 -0
  50. BUCToolkit/api/Metrics.py +84 -0
  51. BUCToolkit/api/ModelOptims.py +296 -0
  52. BUCToolkit/api/MolecularDynamics.py +282 -0
  53. BUCToolkit/api/MonteCarlo.py +280 -0
  54. BUCToolkit/api/NEB.py +325 -0
  55. BUCToolkit/api/Predictor.py +292 -0
  56. BUCToolkit/api/StructureOptimization.py +484 -0
  57. BUCToolkit/api/Trainer.py +635 -0
  58. BUCToolkit/api/VibrationAnalysis.py +279 -0
  59. BUCToolkit/api/__init__.py +38 -0
  60. BUCToolkit/api/_io.py +1026 -0
  61. BUCToolkit/cli/__init__.py +20 -0
  62. BUCToolkit/cli/cli.py +934 -0
  63. BUCToolkit/cli/input_stub.py +140 -0
  64. BUCToolkit/cli/main.py +368 -0
  65. BUCToolkit/cli/print_logo.py +149 -0
  66. BUCToolkit/cli/template_model.py +6 -0
  67. BUCToolkit/io.py +30 -0
  68. BUCToolkit/utils/AtomSelector.py +64 -0
  69. BUCToolkit/utils/AtomicNumber2Properties.py +39 -0
  70. BUCToolkit/utils/ElemListReduce.py +52 -0
  71. BUCToolkit/utils/Ewald.py +210 -0
  72. BUCToolkit/utils/IrregularTensorReformat.py +59 -0
  73. BUCToolkit/utils/_CheckModules.py +30 -0
  74. BUCToolkit/utils/_Element_info.py +527 -0
  75. BUCToolkit/utils/__init__.py +22 -0
  76. BUCToolkit/utils/_para_flatt_list.py +131 -0
  77. BUCToolkit/utils/_print_formatter.py +59 -0
  78. BUCToolkit/utils/check_structures.py +204 -0
  79. BUCToolkit/utils/function_utils.py +147 -0
  80. BUCToolkit/utils/grad_functions.py +69 -0
  81. BUCToolkit/utils/index_ops.py +432 -0
  82. BUCToolkit/utils/setup_loggers.py +137 -0
  83. buctoolkit-1.0b0.dist-info/METADATA +28 -0
  84. buctoolkit-1.0b0.dist-info/RECORD +88 -0
  85. buctoolkit-1.0b0.dist-info/WHEEL +5 -0
  86. buctoolkit-1.0b0.dist-info/entry_points.txt +6 -0
  87. buctoolkit-1.0b0.dist-info/licenses/LICENSE +21 -0
  88. 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