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,510 @@
1
+ """ Canonical ensemble (NVT) Molecular Dynamics via Verlet algo. """
2
+
3
+ # Copyright (c) 2024-2025.7.4, BUCToolkit.
4
+ # Authors: Pu Pengxin, Song Xin
5
+ # Version: 0.9a
6
+ # File: NVT.py
7
+ # Environment: Python 3.12
8
+
9
+ from typing import Iterable, Dict, Any, List, Literal, Optional, Callable, Sequence, Tuple # noqa: F401
10
+ import warnings
11
+ import math
12
+
13
+ import numpy as np
14
+ import torch as th
15
+ from torch import nn
16
+
17
+ from BUCToolkit.utils.index_ops import index_reduce
18
+ from ._BaseMD import _BaseMD
19
+
20
+
21
+ class NVT(_BaseMD):
22
+ """
23
+ Canonical ensemble (NVT) molecular dynamics.
24
+
25
+ Parameters:
26
+ time_step: float, time per step (ps).
27
+ max_step: maxmimum steps.
28
+ thermostat: str, the thermostat of NVT ensemble.
29
+ thermostat_config: Dict|None, configs of thermostat. {'damping_coeff': float} for Langevin, {'time_const': float} for CSVR, {'virt_mass': float} for Nose-Hoover.
30
+ 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.
31
+ output_structures_per_step: int, output structures per output_structures_per_step steps.
32
+ device: device that the program rum on.
33
+ verbose: control the detailed degree of output information. 0 for silence, 1 for output Energy and Forces per step, 2 for output all structures.
34
+ is_compile: whether to use jit to compile integrator or not.
35
+ compile_kwargs: keyword arguments passed to compile. Only work when is_compile is True.
36
+ Methods:
37
+ run: run the NVT ensemble BatchMD.
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ time_step: float,
43
+ max_step: int,
44
+ thermostat: Literal['Langevin', 'VR', 'Nose-Hoover', 'CSVR'],
45
+ thermostat_config: Dict[Literal['damping_coeff', 'time_const', 'virt_mass'], float] | None = None,
46
+ T_init: float = 298.15,
47
+ output_file: str | None = None,
48
+ output_structures_per_step: int = 1,
49
+ device: str | th.device = 'cpu',
50
+ verbose: int = 2,
51
+ is_compile: bool = False,
52
+ compile_kwargs: dict | None = None,
53
+ ) -> None:
54
+ """
55
+ Parameters:
56
+ time_step: float, time per step (ps).
57
+ max_step: maxmimum steps.
58
+ thermostat: str, the thermostat of NVT ensemble.
59
+ thermostat_config: Dict|None, configs of thermostat. {'damping_coeff': float} for Langevin, {'time_const': float} for CSVR, {'virt_mass': float} for Nose-Hoover.
60
+ 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.
61
+ output_structures_per_step: int, output structures per output_structures_per_step steps.
62
+ device: device that program run on.
63
+ verbose: control the detailed degree of output information. 0 for silence, 1 for output Energy and Forces per step, 2 for output all structures.
64
+ """
65
+ super().__init__(
66
+ time_step,
67
+ max_step,
68
+ T_init,
69
+ output_file,
70
+ output_structures_per_step,
71
+ device,
72
+ verbose,
73
+ is_compile,
74
+ compile_kwargs
75
+ )
76
+ __ENSEMBLES_DICT = {'Langevin': None, 'VR': None, 'Nose-Hoover': None, 'CSVR': None}
77
+ if thermostat not in {'Langevin', 'Langevin_old', 'test', 'VR', 'CSVR', 'Nose-Hoover'}:
78
+ raise ValueError(f'Unknown Thermostat {thermostat}')
79
+ self.thermostat = thermostat
80
+ if thermostat_config is None:
81
+ thermostat_config = dict()
82
+ self.thermostat_config = thermostat_config
83
+ self.update_scheme = None # lazy loaded in self.initialize
84
+ self.half_time_step_const = 0.5 * self.time_step * 9.64853329045427e-3
85
+
86
+ def __resolve_update_scheme(self, batch_indices):
87
+ """
88
+ resolve different iteration scheme & initialize corresponding parameters.
89
+ Returns:
90
+
91
+ """
92
+ if self.thermostat == "Langevin":
93
+ damp_coeff = self.thermostat_config.get('damping_coeff', 0.01) # Unit: fs^-1
94
+ self.alpha = math.exp(- damp_coeff * self.time_step)
95
+ return self.__Langevin
96
+ elif self.thermostat == "VR":
97
+ return self.__VR
98
+ elif self.thermostat == "Nose-Hoover":
99
+ # read thermostat config
100
+ smass = self.thermostat_config.get('virt_mass', self.free_degree * 8.617333262145e-5 * self.T_init * (40. * self.time_step) ** 2)
101
+ if isinstance(smass, float):
102
+ smass = th.as_tensor(smass, device=self.device).view(1, 1, 1)
103
+ elif isinstance(smass, th.Tensor):
104
+ smass = smass.to(self.device)
105
+ smass = smass.view(1, -1, 1) if self.batch_tensor is not None else smass.view(-1, 1, 1)
106
+ self.smass = smass
107
+ if batch_indices is not None:
108
+ self.long_free_degree = self.free_degree.reshape(1, -1, 1)
109
+ else:
110
+ self.long_free_degree = self.free_degree.reshape(-1, 1, 1)
111
+ return self.__NoseHoover
112
+ elif self.thermostat == "CSVR":
113
+ # read thermostat configs
114
+ self.time_const = self.thermostat_config.get('time_const', 10 * self.time_step) # Unit: fs^-1
115
+ # NVE Step
116
+ dtT = self.time_step
117
+ tauT = self.time_const
118
+ # c = exp(-dt/tau) (scalar)
119
+ c = th.exp(th.scalar_tensor(-dtT / tauT, device=self.device, dtype=th.float32))
120
+ self.sqrt_c = th.sqrt(c)
121
+ self.one_sub_c = 1 - c
122
+ # avoid divide-by-zero in K
123
+ self.epsK = th.scalar_tensor(1e-12, device=self.device, dtype=th.float32)
124
+ return self.__CSVR
125
+ else:
126
+ raise NotImplementedError("Unknown Thermostat Type.")
127
+
128
+ def initialize(
129
+ self,
130
+ func: Any | nn.Module,
131
+ X: th.Tensor,
132
+ Element_list: List[List[str]] | List[List[int]],
133
+ masses: th.Tensor,
134
+ V_init: th.Tensor | None = None,
135
+ grad_func: Any | nn.Module = None,
136
+ func_args: Sequence = tuple(),
137
+ func_kwargs: Dict | None = None,
138
+ grad_func_args: Sequence = tuple(),
139
+ grad_func_kwargs: Dict | None = None,
140
+ is_grad_func_contain_y: bool = True,
141
+ require_grad: bool = False,
142
+ batch_indices: List[int] | Tuple[int, ...] | th.Tensor | np.ndarray | None = None,
143
+ fixed_atom_tensor: Optional[th.Tensor] = None,
144
+ is_fix_mass_center: bool = False
145
+ ) -> None:
146
+ self.update_scheme = self.__resolve_update_scheme(batch_indices)
147
+
148
+ def __Langevin(
149
+ self,
150
+ X,
151
+ V,
152
+ Force,
153
+ func,
154
+ grad_func_,
155
+ func_args,
156
+ func_kwargs,
157
+ grad_func_args,
158
+ grad_func_kwargs,
159
+ masses,
160
+ atom_masks,
161
+ is_grad_func_contain_y,
162
+ batch_indices
163
+ )-> Tuple[th.Tensor, th.Tensor, th.Tensor, th.Tensor]:
164
+ # read thermostat configs
165
+ X = X.detach()
166
+ with th.no_grad():
167
+ alpha = self.alpha
168
+ # half-step
169
+ V.addcdiv_(Force, masses, value=0.5 * self.time_step * 9.64853329045427e-3)
170
+ X.add_(V, alpha = 0.5 * self.time_step)
171
+ # stochastic update velocity
172
+ V.mul_(alpha)
173
+ V.add_(th.sqrt((8314.462618 * self.T_init * (1 - alpha ** 2)) / masses) * 1e-5 * th.randn_like(V))
174
+ #V = alpha * V + th.sqrt((8314.462618 * self.T_init * (1 - alpha ** 2)) / masses) * 1e-5 * th.randn_like(V)
175
+ # the rest half-step
176
+ X.add_(V, alpha = 0.5 * self.time_step)
177
+ V.addcdiv_(Force, masses, value=0.5 * self.time_step * 9.64853329045427e-3)
178
+ # update energy & forces
179
+ Energy, Force = self._calc_EF(
180
+ X,
181
+ func,
182
+ func_args,
183
+ func_kwargs,
184
+ grad_func_,
185
+ grad_func_args,
186
+ grad_func_kwargs,
187
+ is_grad_func_contain_y
188
+ )
189
+ Force.mul_(atom_masks)
190
+
191
+ return X, V, Energy, Force
192
+
193
+ def __VR(
194
+ self,
195
+ X,
196
+ V,
197
+ Force,
198
+ func,
199
+ grad_func_,
200
+ func_args,
201
+ func_kwargs,
202
+ grad_func_args,
203
+ grad_func_kwargs,
204
+ masses,
205
+ atom_masks,
206
+ is_grad_func_contain_y,
207
+ batch_indices
208
+ )-> Tuple[th.Tensor, th.Tensor, th.Tensor, th.Tensor]:
209
+ # NVE Step
210
+ X = X.detach()
211
+ with th.no_grad():
212
+ # X = X + V * self.time_step + (Force / (2. * masses)) * self.time_step ** 2 * 9.64853329045427e-3
213
+ V.addcdiv_(Force, masses, value=0.5 * self.time_step * 9.64853329045427e-3)
214
+ X.add_(V, alpha=self.time_step)
215
+ # V = V + (Force / (2. * masses)) * self.time_step * 9.64853329045427e-3 # half-step veloc. update, to avoid saving 2 Forces Tensors.
216
+ # Update V
217
+ Energy, Force = self._calc_EF(
218
+ X,
219
+ func,
220
+ func_args,
221
+ func_kwargs,
222
+ grad_func_,
223
+ grad_func_args,
224
+ grad_func_kwargs,
225
+ is_grad_func_contain_y
226
+ )
227
+ Force.mul_(atom_masks)
228
+
229
+ # V = V + (Force / (2. * masses)) * self.time_step * 9.64853329045427e-3
230
+ V.addcdiv_(Force, masses, value=0.5 * self.time_step * 9.64853329045427e-3)
231
+ if batch_indices is not None:
232
+ # Rescaling factor
233
+ alpha = th.sqrt(self.EK_TARGET / self.Ek).unsqueeze(-1).unsqueeze(-1) # (n_batch, 1, 1) | (irregular n_batch, 1, 1)
234
+ V *= alpha.transpose(0, 1)[:, self.batch_scatter, :]
235
+ else:
236
+ # Rescaling factor
237
+ alpha = th.sqrt(self.EK_TARGET / self.Ek).unsqueeze(-1).unsqueeze(-1) # (n_batch, 1, 1) | (irregular n_batch, 1, 1)
238
+ V *= alpha # (n_batch, n_atom, n_dim) * (n_batch, 1, 1)
239
+
240
+ return X, V, Energy, Force
241
+
242
+ def __CSVR_old(
243
+ self,
244
+ X,
245
+ V,
246
+ Force,
247
+ func,
248
+ grad_func_,
249
+ func_args,
250
+ func_kwargs,
251
+ grad_func_args,
252
+ grad_func_kwargs,
253
+ masses,
254
+ atom_masks,
255
+ is_grad_func_contain_y,
256
+ batch_indices
257
+ )-> Tuple[th.Tensor, th.Tensor, th.Tensor, th.Tensor]:
258
+ n_batch, n_atom, n_dim = X.shape
259
+ # read thermostat configs
260
+ time_const = self.thermostat_config.get('time_const', 10 * self.time_step) # Unit: fs^-1
261
+ # NVE Step
262
+ X: th.Tensor = X.detach()
263
+ with th.no_grad():
264
+ #X += V * self.time_step + (Force / (2. * masses)) * self.time_step ** 2 * 9.64853329045427e-3
265
+ X.add_(V, alpha=self.time_step)
266
+ X.add_(Force / (2. * masses), alpha=self.time_step ** 2 * 9.64853329045427e-3)
267
+ #V += (Force / (2. * masses)) * self.time_step * 9.64853329045427e-3 # half-step veloc. update, to avoid saving 2 Forces Tensors.
268
+ V.add_(Force / (2. * masses), alpha=self.time_step * 9.64853329045427e-3)
269
+ with th.set_grad_enabled(self.require_grad):
270
+ X.requires_grad_(self.require_grad)
271
+ Energy = func(X, *func_args, **func_kwargs)
272
+ if is_grad_func_contain_y:
273
+ Force = - grad_func_(X, Energy, *grad_func_args, **grad_func_kwargs) * atom_masks
274
+ else:
275
+ Force = - grad_func_(X, *grad_func_args, **grad_func_kwargs) * atom_masks
276
+ #V += (Force / (2. * masses)) * self.time_step * 9.64853329045427e-3
277
+ V.add_(Force / (2. * masses), alpha=self.time_step * 9.64853329045427e-3)
278
+
279
+ # Kinetic Energy Redistribution, Rescale Velocities
280
+ # sigma = th.sqrt((2. * masses * self.T_init / (self.time_step * time_const)) * (0.138064853 / (6.022140857 * 1.602176634 ** 2)))
281
+ sigma = self.time_step ** 0.5
282
+ if batch_indices is not None:
283
+ self.Ekt_vir += (
284
+ (self.EK_TARGET - self.Ek) * self.time_step / time_const
285
+ + 2. * th.sqrt(self.EK_TARGET * self.Ek / ((3 * self.batch_tensor - 3) * time_const))
286
+ * th.normal(0., sigma, size=(len(batch_indices),), device=self.device)
287
+ ) # Unit: eV/Ang
288
+ self.Ekt_vir = th.where(self.Ekt_vir <= 0., 1e-6, self.Ekt_vir)
289
+ # Rescaling factor
290
+ alpha = th.sqrt(self.Ekt_vir / self.Ek).unsqueeze(-1).unsqueeze(-1) # (n_batch, 1, 1) | (irregular n_batch, 1, 1)
291
+ V *= alpha.transpose(0, 1)[:, self.batch_scatter, :]
292
+ else:
293
+ self.Ekt_vir += (
294
+ (self.EK_TARGET - self.Ek) * self.time_step / time_const
295
+ + 2. * th.sqrt(self.EK_TARGET * self.Ek / ((3 * n_atom - 3) * time_const))
296
+ * th.normal(0., sigma, size=(n_batch,), device=self.device)
297
+ ) # Unit: eV/Ang
298
+ # Rescaling factor
299
+ alpha = th.sqrt(self.Ekt_vir / self.Ek).unsqueeze(-1).unsqueeze(-1) # (n_batch, 1, 1) | (irregular n_batch, 1, 1)
300
+ V *= alpha # (n_batch, n_atom, n_dim) * (n_batch, 1, 1)
301
+
302
+ return X, V, Energy, Force
303
+
304
+ def __CSVR(
305
+ self,
306
+ X,
307
+ V,
308
+ Force,
309
+ func,
310
+ grad_func_,
311
+ func_args,
312
+ func_kwargs,
313
+ grad_func_args,
314
+ grad_func_kwargs,
315
+ masses,
316
+ atom_masks,
317
+ is_grad_func_contain_y,
318
+ batch_indices
319
+ ) -> Tuple[th.Tensor, th.Tensor, th.Tensor, th.Tensor]:
320
+ """
321
+ The Analytic solution of CSVR that uses Chi^2 distribution and exp.
322
+ Returns:
323
+
324
+ """
325
+
326
+ with th.no_grad():
327
+ # X = X + V * self.time_step + (Force / (2. * masses)) * self.time_step ** 2 * 9.64853329045427e-3
328
+ V.addcdiv_(Force, masses, value=0.5 * self.time_step * 9.64853329045427e-3)
329
+ X.add_(V, alpha=self.time_step)
330
+ # V = V + (Force / (2. * masses)) * self.time_step * 9.64853329045427e-3 # half-step veloc. update, to avoid saving 2 Forces Tensors.
331
+ # Update V
332
+ Energy, Force = self._calc_EF(
333
+ X,
334
+ func,
335
+ func_args,
336
+ func_kwargs,
337
+ grad_func_,
338
+ grad_func_args,
339
+ grad_func_kwargs,
340
+ is_grad_func_contain_y
341
+ )
342
+ Force.mul_(atom_masks)
343
+
344
+ # V = V + (Force / (2. * masses)) * self.time_step * 9.64853329045427e-3
345
+ V.addcdiv_(Force, masses, value=0.5 * self.time_step * 9.64853329045427e-3)
346
+ Nf = self.free_degree # shape (n_batch,)
347
+ if batch_indices is not None:
348
+ K = th.clamp(self.Ek, min=self.epsK) # shape (n_batch,)
349
+ K0 = self.EK_TARGET # (n_batch, )
350
+
351
+ # f = (1-c) * K0 / (Nf*K)
352
+ f = self.one_sub_c * K0 / (Nf * K)
353
+
354
+ # R ~ N(0,1)
355
+ R = th.randn_like(K)
356
+
357
+ # S ~ Chi2(df=Nf-1)
358
+ df = th.clamp(Nf - 1.0, min=1.0)
359
+ S = th.distributions.chi2.Chi2(df=df).sample() # shape (n_batch,)
360
+
361
+ # alpha^2 = (sqrt(c)+sqrt(f)R)^2 + f S
362
+ sqrt_f = th.sqrt(th.clamp(f, min=0.0))
363
+ alpha2: th.Tensor = th.addcmul(self.sqrt_c, sqrt_f, R)**2
364
+ alpha2.addcmul_(f, S).clamp_min_(self.epsK)
365
+
366
+ # (optional bookkeeping: post-thermostat kinetic energy)
367
+ #self.Ekt_vir = alpha2 * K
368
+
369
+ alpha = th.sqrt(alpha2).reshape(1, -1, 1) # (n_batch, 1, 1)
370
+ V *= alpha.index_select(1, self.batch_scatter)
371
+
372
+ else:
373
+ K = th.clamp(self.Ek, min=self.epsK) # (n_batch,)
374
+ K0 = self.EK_TARGET # scalar or (n_batch,)
375
+
376
+ f = self.one_sub_c * K0 / (Nf * K)
377
+
378
+ R = th.randn_like(K)
379
+ df = th.clamp(Nf - 1.0, min=1.0)
380
+ # sample per-batch
381
+ S = th.distributions.chi2.Chi2(df=df).sample()
382
+
383
+ sqrt_f = th.sqrt(th.clamp(f, min=0.0))
384
+ alpha2: th.Tensor = th.addcmul(self.sqrt_c, sqrt_f, R) ** 2
385
+ alpha2.addcmul_(f, S).clamp_min_(self.epsK)
386
+
387
+ #self.Ekt_vir = alpha2 * K
388
+
389
+ alpha = th.sqrt(alpha2).reshape(-1, 1, 1) # (n_batch, 1, 1)
390
+ V *= alpha
391
+
392
+ return X, V, Energy, Force
393
+
394
+ def __NoseHoover(
395
+ self,
396
+ X,
397
+ V,
398
+ Force,
399
+ func,
400
+ grad_func_,
401
+ func_args,
402
+ func_kwargs,
403
+ grad_func_args,
404
+ grad_func_kwargs,
405
+ masses,
406
+ atom_masks,
407
+ is_grad_func_contain_y,
408
+ batch_indices
409
+ ) -> Tuple[th.Tensor, th.Tensor, th.Tensor, th.Tensor]:
410
+ n_batch, n_atom, n_dim = X.shape
411
+ smass = self.smass
412
+ # Main update
413
+ with th.no_grad():
414
+ if batch_indices is not None:
415
+ _iota = self.p_iota[:, self.batch_scatter, :]
416
+ else:
417
+ _iota = self.p_iota
418
+ V.addcdiv_(Force, masses, value=0.5 * self.time_step * 9.64853329045427e-3)
419
+ V.mul_(th.exp(- _iota * 0.5 * self.time_step))
420
+ X.add_(V, alpha=self.time_step)
421
+
422
+ Energy, Force = self._calc_EF(
423
+ X,
424
+ func,
425
+ func_args,
426
+ func_kwargs,
427
+ grad_func_,
428
+ grad_func_args,
429
+ grad_func_kwargs,
430
+ is_grad_func_contain_y
431
+ )
432
+ Force.mul_(atom_masks)
433
+
434
+ if batch_indices is not None: # for cuda, it would be further optimized by Graph.replay
435
+ reduced_Ek = th.sum(
436
+ index_reduce(masses * V ** 2 * 103.642696562621738, self.batch_scatter, 1, out_size=self.scatter_dim_out_size),
437
+ dim=-1,
438
+ keepdim=True
439
+ )
440
+ # self.p_iota = p_iota + 0.5 * dt * (reducedEk - Nf * T)/smass
441
+ self.p_iota.addcdiv_(
442
+ th.sub(reduced_Ek, self.long_free_degree, alpha=self.T_init * 8.617333262145e-5),
443
+ smass, value=self.time_step * 0.5
444
+ )
445
+ _iota = self.p_iota[:, self.batch_scatter, :] # (1, n_batch*n_atom, 1)
446
+ V.addcdiv_(Force, masses, value=0.5 * self.time_step * 9.64853329045427e-3)
447
+ V.mul_(th.exp(- _iota * 0.5 * self.time_step))
448
+ reduced_Ek = th.sum(
449
+ index_reduce(masses * V ** 2 * 103.642696562621738, self.batch_scatter, 1, out_size=self.scatter_dim_out_size),
450
+ dim=-1,
451
+ keepdim=True
452
+ )
453
+ # self.p_iota = p_iota + 0.5 * dt * (reducedEk - Nf * T)/smass
454
+ self.p_iota.addcdiv_(
455
+ th.sub(reduced_Ek, self.long_free_degree, alpha=self.T_init * 8.617333262145e-5),
456
+ smass, value=self.time_step * 0.5
457
+ )
458
+ else:
459
+ reduced_Ek = th.sum(masses * V ** 2 * 103.642696562621738, dim=(-2, -1), keepdim=True)
460
+ # self.p_iota = p_iota + 0.5 * dt * (reducedEk - Nf * T)/smass
461
+ self.p_iota.addcdiv_(
462
+ th.sub(reduced_Ek, self.long_free_degree, alpha=self.T_init * 8.617333262145e-5),
463
+ smass, value=self.time_step * 0.5
464
+ )
465
+ V.addcdiv_(Force, masses, value=0.5 * self.time_step * 9.64853329045427e-3)
466
+ V.mul_(th.exp(- _iota * 0.5 * self.time_step))
467
+ reduced_Ek = th.sum(masses * V ** 2 * 103.642696562621738, dim=(-2, -1), keepdim=True)
468
+ # self.p_iota = p_iota + 0.5 * dt * (reducedEk - Nf * T)/smass
469
+ self.p_iota.addcdiv_(
470
+ th.sub(reduced_Ek, self.long_free_degree, alpha=self.T_init * 8.617333262145e-5),
471
+ smass, value=self.time_step * 0.5
472
+ )
473
+
474
+ return X, V, Energy, Force
475
+
476
+
477
+ def _updateXV(
478
+ self,
479
+ X,
480
+ V,
481
+ Force,
482
+ func,
483
+ grad_func_,
484
+ func_args,
485
+ func_kwargs,
486
+ grad_func_args,
487
+ grad_func_kwargs,
488
+ masses,
489
+ atom_masks,
490
+ is_grad_func_contain_y,
491
+ batch_indices
492
+ ) -> Tuple[th.Tensor, th.Tensor, th.Tensor, th.Tensor]:
493
+
494
+ x, v, energy, forces = self.update_scheme(
495
+ X,
496
+ V,
497
+ Force,
498
+ func,
499
+ grad_func_,
500
+ func_args,
501
+ func_kwargs,
502
+ grad_func_args,
503
+ grad_func_kwargs,
504
+ masses,
505
+ atom_masks,
506
+ is_grad_func_contain_y,
507
+ batch_indices
508
+ )
509
+
510
+ return x, v, energy, forces