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