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
BUCToolkit/api/Losses.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Build-in Losses function
|
|
3
|
+
"""
|
|
4
|
+
# Copyright (c) 2024-2025.7.4, BUCToolkit.
|
|
5
|
+
# Authors: Pu Pengxin, Song Xin
|
|
6
|
+
# Version: 0.9a
|
|
7
|
+
# File: Losses.py
|
|
8
|
+
# Environment: Python 3.12
|
|
9
|
+
|
|
10
|
+
from typing import Literal, List, Dict, Sequence, Tuple
|
|
11
|
+
import math
|
|
12
|
+
import numpy as np
|
|
13
|
+
import torch as th
|
|
14
|
+
import torch.nn as nn
|
|
15
|
+
import torch.nn.functional as F
|
|
16
|
+
|
|
17
|
+
class Energy_Force_Loss(nn.Module):
|
|
18
|
+
"""
|
|
19
|
+
A loss function that evaluates both predicted energy and forces.
|
|
20
|
+
Both energy & force losses would be averaged over each atom.
|
|
21
|
+
|
|
22
|
+
Parameters:
|
|
23
|
+
loss_E: the loss function of energy.
|
|
24
|
+
loss_F: the loss function of forces.
|
|
25
|
+
coeff_E: the coefficient of energy.
|
|
26
|
+
coeff_F: the coefficient of forces.
|
|
27
|
+
|
|
28
|
+
forward:
|
|
29
|
+
pred: Dict[Literal['energy'], th.Tensor], output of models;
|
|
30
|
+
label: Dict[Literal['energy'], th.Tensor], labels.
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
def __init__(self,
|
|
34
|
+
loss_E: Literal['MAE', 'MSE']|nn.Module='MAE',
|
|
35
|
+
loss_F: Literal['MAE', 'MSE']|nn.Module='MAE',
|
|
36
|
+
coeff_E: float=1.,
|
|
37
|
+
coeff_F: float=1.) -> None:
|
|
38
|
+
super().__init__()
|
|
39
|
+
if loss_E == 'MAE':
|
|
40
|
+
self.loss_E = nn.SmoothL1Loss(reduction='sum')
|
|
41
|
+
elif loss_E == 'MSE':
|
|
42
|
+
self.loss_E = nn.MSELoss(reduction='sum')
|
|
43
|
+
elif loss_E == 'SmoothMAE':
|
|
44
|
+
self.loss_E = nn.SmoothL1Loss(reduction='sum')
|
|
45
|
+
else:
|
|
46
|
+
self.loss_E = loss_E
|
|
47
|
+
|
|
48
|
+
if loss_F == 'MAE':
|
|
49
|
+
self.loss_F = nn.SmoothL1Loss(reduction='sum')
|
|
50
|
+
elif loss_F == 'MSE':
|
|
51
|
+
self.loss_F = nn.MSELoss(reduction='sum')
|
|
52
|
+
else:
|
|
53
|
+
self.loss_F = loss_F
|
|
54
|
+
|
|
55
|
+
self.coeff_E = coeff_E
|
|
56
|
+
self.coeff_F = coeff_F
|
|
57
|
+
|
|
58
|
+
def forward(self, pred:Dict[Literal['energy', 'forces'], th.Tensor], label:Dict[Literal['energy', 'forces'], th.Tensor]):
|
|
59
|
+
n_atom = math.prod(pred['forces'].shape[:-1])
|
|
60
|
+
loss = (self.coeff_E * self.loss_E(pred['energy'], label['energy']) +
|
|
61
|
+
self.coeff_F * self.loss_F(pred['forces'], label['forces']))/n_atom
|
|
62
|
+
return loss
|
|
63
|
+
|
|
64
|
+
class Energy_Loss(nn.Module):
|
|
65
|
+
"""
|
|
66
|
+
A loss function that evaluates both predicted energy and forces.
|
|
67
|
+
|
|
68
|
+
Parameters:
|
|
69
|
+
loss_E: the loss function of energy.
|
|
70
|
+
|
|
71
|
+
forward:
|
|
72
|
+
pred: Dict[Literal['energy'], th.Tensor], output of models;
|
|
73
|
+
label: Dict[Literal['energy'], th.Tensor], labels.
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
def __init__(self, loss_E: Literal['MAE', 'MSE', 'SmoothMAE', 'Huber'] | nn.Module = 'MAE',) -> None:
|
|
77
|
+
super().__init__()
|
|
78
|
+
if loss_E == 'MAE':
|
|
79
|
+
self.loss_E = nn.L1Loss()
|
|
80
|
+
elif loss_E == 'SmoothMAE':
|
|
81
|
+
self.loss_E = nn.SmoothL1Loss()
|
|
82
|
+
elif loss_E == 'MSE':
|
|
83
|
+
self.loss_E = nn.MSELoss()
|
|
84
|
+
elif loss_E == 'Huber':
|
|
85
|
+
self.loss_E = nn.HuberLoss()
|
|
86
|
+
else:
|
|
87
|
+
self.loss_E = loss_E
|
|
88
|
+
|
|
89
|
+
def forward(self, pred: Dict[Literal['energy'], th.Tensor], label: Dict[Literal['energy'], th.Tensor]):
|
|
90
|
+
loss = self.loss_E(pred['energy'], label['energy'])
|
|
91
|
+
return loss
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class WrapperBoostLoss(nn.Module):
|
|
95
|
+
def __init__(self, loss_E: Literal['MAE', 'MSE', 'SmoothMAE', 'Huber'] | nn.Module = 'MAE', loss_F = None) -> None:
|
|
96
|
+
super().__init__()
|
|
97
|
+
if loss_E == 'MAE':
|
|
98
|
+
self.loss_E = nn.L1Loss()
|
|
99
|
+
elif loss_E == 'SmoothMAE':
|
|
100
|
+
self.loss_E = nn.SmoothL1Loss()
|
|
101
|
+
elif loss_E == 'MSE':
|
|
102
|
+
self.loss_E = nn.MSELoss()
|
|
103
|
+
elif loss_E == 'Huber':
|
|
104
|
+
self.loss_E = nn.HuberLoss()
|
|
105
|
+
else:
|
|
106
|
+
self.loss_E = loss_E
|
|
107
|
+
|
|
108
|
+
if loss_F is None:
|
|
109
|
+
self.has_loss_f = False
|
|
110
|
+
else:
|
|
111
|
+
if loss_F == 'MAE':
|
|
112
|
+
self.loss_F = nn.SmoothL1Loss(reduction='sum')
|
|
113
|
+
elif loss_F == 'MSE':
|
|
114
|
+
self.loss_F = nn.MSELoss(reduction='sum')
|
|
115
|
+
else:
|
|
116
|
+
self.loss_F = loss_F
|
|
117
|
+
|
|
118
|
+
def forward(self, pred: Dict[Literal['energy', 'forces'], List[th.Tensor]], label: Dict[Literal['energy', 'forces'], th.Tensor]):
|
|
119
|
+
loss = 0.
|
|
120
|
+
res = label['energy']
|
|
121
|
+
for pred_ in pred['energy']:
|
|
122
|
+
loss = loss + self.loss_E(res, pred_)
|
|
123
|
+
res = res - pred_
|
|
124
|
+
if self.has_loss_f:
|
|
125
|
+
res_f = label['forces']
|
|
126
|
+
for pred_ in pred['forces']:
|
|
127
|
+
loss = loss + self.loss_F(res_f, pred_)
|
|
128
|
+
res_f = res - pred_
|
|
129
|
+
|
|
130
|
+
return loss
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class WrapperMeanLoss(nn.Module):
|
|
134
|
+
def __init__(
|
|
135
|
+
self,
|
|
136
|
+
loss_E: Literal['MAE', 'MSE', 'SmoothMAE', 'Huber'] | nn.Module = 'MAE',
|
|
137
|
+
loss_F: Literal['MAE', 'MSE', 'SmoothMAE', 'Huber'] | nn.Module | None = None
|
|
138
|
+
) -> None:
|
|
139
|
+
super().__init__()
|
|
140
|
+
if loss_E == 'MAE':
|
|
141
|
+
self.loss_E = nn.L1Loss()
|
|
142
|
+
elif loss_E == 'SmoothMAE':
|
|
143
|
+
self.loss_E = nn.SmoothL1Loss()
|
|
144
|
+
elif loss_E == 'MSE':
|
|
145
|
+
self.loss_E = nn.MSELoss()
|
|
146
|
+
elif loss_E == 'Huber':
|
|
147
|
+
self.loss_E = nn.HuberLoss()
|
|
148
|
+
else:
|
|
149
|
+
self.loss_E = loss_E
|
|
150
|
+
|
|
151
|
+
if loss_F is None:
|
|
152
|
+
self.has_loss_f = False
|
|
153
|
+
else:
|
|
154
|
+
self.has_loss_f = True
|
|
155
|
+
if loss_F == 'MAE':
|
|
156
|
+
self.loss_F = nn.SmoothL1Loss(reduction='sum')
|
|
157
|
+
elif loss_F == 'MSE':
|
|
158
|
+
self.loss_F = nn.MSELoss(reduction='sum')
|
|
159
|
+
else:
|
|
160
|
+
self.loss_F = loss_F
|
|
161
|
+
|
|
162
|
+
def forward(self, pred: Dict[Literal['energy', 'forces'], List[th.Tensor]], label: Dict[Literal['energy', 'forces'], th.Tensor]):
|
|
163
|
+
loss = 0.
|
|
164
|
+
for pred_ in pred['energy']:
|
|
165
|
+
loss = loss + self.loss_E(label['energy'], pred_)
|
|
166
|
+
if self.has_loss_f:
|
|
167
|
+
for pred_ in pred['forces']:
|
|
168
|
+
loss = loss + self.loss_F(label['forces'], pred_)
|
|
169
|
+
|
|
170
|
+
return loss
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
""" Metrics """
|
|
2
|
+
# Copyright (c) 2024-2025.7.4, BUCToolkit.
|
|
3
|
+
# Authors: Pu Pengxin, Song Xin
|
|
4
|
+
# Version: 0.9a
|
|
5
|
+
# File: Metrics.py
|
|
6
|
+
# Environment: Python 3.12
|
|
7
|
+
|
|
8
|
+
from typing import Literal, Dict, List, Tuple
|
|
9
|
+
import warnings
|
|
10
|
+
import torch as th
|
|
11
|
+
import torch.nn.functional as F
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _r2_score(y_pred: th.Tensor, y_true: th.Tensor) -> th.Tensor:
|
|
15
|
+
"""
|
|
16
|
+
Calculate R^2
|
|
17
|
+
"""
|
|
18
|
+
# initialize
|
|
19
|
+
y1 = y_true.float()
|
|
20
|
+
y2 = y_pred.float()
|
|
21
|
+
if y1.dim() == 1:
|
|
22
|
+
y1 = y1.view(-1, 1)
|
|
23
|
+
y2 = y2.view(-1, 1)
|
|
24
|
+
|
|
25
|
+
# SS_res
|
|
26
|
+
ss_res = th.sum((y1 - y2) ** 2, dim=0)
|
|
27
|
+
|
|
28
|
+
# SS_tol
|
|
29
|
+
y1_mean = th.mean(y1, dim=0, keepdim=True)
|
|
30
|
+
ss_tot = th.sum((y1 - y1_mean) ** 2, dim=0)
|
|
31
|
+
|
|
32
|
+
# Main
|
|
33
|
+
r2 = th.where(
|
|
34
|
+
ss_tot != 0,
|
|
35
|
+
1 - (ss_res / ss_tot),
|
|
36
|
+
th.where(
|
|
37
|
+
ss_res != 0,
|
|
38
|
+
-th.inf,
|
|
39
|
+
th.ones_like(ss_res)
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return r2.squeeze()
|
|
44
|
+
|
|
45
|
+
def _rmse(x, y, reduction='mean') -> th.Tensor:
|
|
46
|
+
return th.sqrt(F.mse_loss(x, y, reduction=reduction))
|
|
47
|
+
|
|
48
|
+
def E_MAE(pred: Dict[Literal['energy', 'forces'], th.Tensor],
|
|
49
|
+
label: Dict[Literal['energy', 'forces'], th.Tensor],
|
|
50
|
+
reduction: Literal['mean', 'sum', 'none'] = 'mean'):
|
|
51
|
+
with th.no_grad():
|
|
52
|
+
if isinstance(pred['energy'], th.Tensor):
|
|
53
|
+
mae = F.l1_loss(pred['energy'], label['energy'], reduction=reduction)
|
|
54
|
+
elif isinstance(pred['energy'], List|Tuple):
|
|
55
|
+
mae = sum(F.l1_loss(pred_, label['energy'], reduction=reduction) for pred_ in pred['energy'])
|
|
56
|
+
|
|
57
|
+
return mae
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def E_R2(pred: Dict[Literal['energy', 'forces'], th.Tensor],
|
|
61
|
+
label: Dict[Literal['energy', 'forces'], th.Tensor],):
|
|
62
|
+
if len(pred['energy']) <= 2:
|
|
63
|
+
r2 = th.tensor([1.], dtype=th.float32)
|
|
64
|
+
warnings.warn('Input samples less than 2, r2 was set to 1.', RuntimeWarning)
|
|
65
|
+
else:
|
|
66
|
+
with th.no_grad():
|
|
67
|
+
r2 = _r2_score(pred['energy'], label['energy'])
|
|
68
|
+
|
|
69
|
+
return r2
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def F_MAE(pred: Dict[Literal['energy', 'forces'], th.Tensor],
|
|
73
|
+
label: Dict[Literal['energy', 'forces'], th.Tensor],
|
|
74
|
+
reduction: Literal['mean', 'sum', 'none'] = 'mean'):
|
|
75
|
+
with th.no_grad():
|
|
76
|
+
mae = F.l1_loss(pred['forces'], label['forces'], reduction=reduction)
|
|
77
|
+
return mae
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def F_MaxE(pred: Dict[Literal['energy', 'forces'], th.Tensor],
|
|
81
|
+
label: Dict[Literal['energy', 'forces'], th.Tensor]):
|
|
82
|
+
with th.no_grad():
|
|
83
|
+
max_ = th.max(pred['forces'] - label['forces'])
|
|
84
|
+
return max_
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# Copyright (c) 2026.3.29, BUCToolkit.
|
|
2
|
+
# Authors: Pu Pengxin, Song Xin
|
|
3
|
+
# Version: 1.0b
|
|
4
|
+
# File: ModelOptims.py
|
|
5
|
+
# Environment: Python 3.12
|
|
6
|
+
import math
|
|
7
|
+
from collections import deque
|
|
8
|
+
|
|
9
|
+
import torch as th
|
|
10
|
+
from torch.optim import Optimizer
|
|
11
|
+
|
|
12
|
+
class FIRELikeOptimizer(Optimizer):
|
|
13
|
+
"""
|
|
14
|
+
FIRE-like optimizer based on damped Newton dynamics.
|
|
15
|
+
When power = -grad @ velocities > 0, which means loss descents, n_count += 1;
|
|
16
|
+
when power <= 0, apply an infinite pulse that sets velocity and N_min to zero, and lr to initial lr.
|
|
17
|
+
Once n_count >= N_min, lr *= fi (fi > 1.), i.e., increase learning rate.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
params: model parameters.
|
|
21
|
+
lr: initial learning rate, i.e., time steplength.
|
|
22
|
+
mass: hyperparameter of mass.
|
|
23
|
+
N_min: steps that keep lr. If contiguous loss descent step number >= N_min, increase lr.
|
|
24
|
+
tolerance: steps that keep lr & veloc. If contiguous loss increment step number >= tolerance, reset lr to the default and veloc. to 0.
|
|
25
|
+
fi: learning rate increment factor
|
|
26
|
+
max_lr: maximum learning rate.
|
|
27
|
+
"""
|
|
28
|
+
def __init__(self, params, lr=1e-2, mass=1.0, N_min=10, tolerance=5, fi=1.1, max_lr=None):
|
|
29
|
+
if lr <= 0:
|
|
30
|
+
raise ValueError(f"Invalid lr: {lr}")
|
|
31
|
+
else:
|
|
32
|
+
lr = float(lr)
|
|
33
|
+
if mass <= 0:
|
|
34
|
+
raise ValueError(f"mass must be positive: {mass}")
|
|
35
|
+
else:
|
|
36
|
+
mass = float(mass)
|
|
37
|
+
if N_min < 1:
|
|
38
|
+
raise ValueError(f"N_min must be >= 1: {N_min}")
|
|
39
|
+
else:
|
|
40
|
+
N_min = int(N_min)
|
|
41
|
+
if tolerance < 0:
|
|
42
|
+
raise ValueError(f"tolerance must be positive: {tolerance}")
|
|
43
|
+
else:
|
|
44
|
+
tolerance = int(tolerance)
|
|
45
|
+
if fi <= 1.0:
|
|
46
|
+
raise ValueError(f"fi must be > 1.0: {fi}")
|
|
47
|
+
else:
|
|
48
|
+
fi = float(fi)
|
|
49
|
+
if max_lr is None:
|
|
50
|
+
max_lr = lr * 10.
|
|
51
|
+
elif max_lr < lr:
|
|
52
|
+
raise ValueError("max_lr must be >= initial lr")
|
|
53
|
+
else:
|
|
54
|
+
max_lr = float(max_lr)
|
|
55
|
+
|
|
56
|
+
defaults = dict(lr=lr, mass=mass, N_min=N_min, tolerance=tolerance, fi=fi, max_lr=max_lr)
|
|
57
|
+
super().__init__(params, defaults)
|
|
58
|
+
|
|
59
|
+
# store 'lr' as the baseline, and 'lr_now' initialized to current really used lr.
|
|
60
|
+
for group in self.param_groups:
|
|
61
|
+
group['lr_now'] = group['lr']
|
|
62
|
+
|
|
63
|
+
# decrement counter
|
|
64
|
+
self.n_count_dec = 0
|
|
65
|
+
# increment counter
|
|
66
|
+
self.n_count_inc = 0
|
|
67
|
+
|
|
68
|
+
def step(self, closure=None):
|
|
69
|
+
loss = None
|
|
70
|
+
if closure is not None:
|
|
71
|
+
loss = closure()
|
|
72
|
+
|
|
73
|
+
# ---------- 1. Power P = -sum(grad · velocity) ----------
|
|
74
|
+
power = None
|
|
75
|
+
for group in self.param_groups:
|
|
76
|
+
for p in group['params']:
|
|
77
|
+
if p.grad is None:
|
|
78
|
+
continue
|
|
79
|
+
grad = p.grad.data
|
|
80
|
+
state = self.state[p]
|
|
81
|
+
# lazy launch initial veloc.
|
|
82
|
+
if 'velocity' not in state:
|
|
83
|
+
state['velocity'] = th.zeros_like(p.data)
|
|
84
|
+
if power is None: power = th.scalar_tensor(0.0, dtype=p.data.dtype, device=p.device)
|
|
85
|
+
vel = state['velocity']
|
|
86
|
+
#power -= th.dot(grad.view(-1), vel.view(-1)).item()
|
|
87
|
+
power: th.Tensor
|
|
88
|
+
power -= th.sum(vel * grad) # DO NOT USE `.item()` which causes global synchronization
|
|
89
|
+
|
|
90
|
+
# ---------- 2. infinite damp pulse ----------
|
|
91
|
+
# scalar Tensor allows directly comparison
|
|
92
|
+
if power <= 0: # loss increment
|
|
93
|
+
# sets velocity and N_min to zero, and lr to initial lr.
|
|
94
|
+
self.n_count_dec = 0
|
|
95
|
+
self.n_count_inc += 1
|
|
96
|
+
for group in self.param_groups:
|
|
97
|
+
if self.n_count_inc >= group['tolerance']:
|
|
98
|
+
group['lr_now'] = group['lr']
|
|
99
|
+
for p in group['params']:
|
|
100
|
+
state = self.state.get(p)
|
|
101
|
+
if (state is not None) and ('velocity' in state):
|
|
102
|
+
state['velocity'].zero_()
|
|
103
|
+
else:
|
|
104
|
+
self.n_count_dec += 1
|
|
105
|
+
self.n_count_inc = 0
|
|
106
|
+
# check if contiguously decreased at least N_min steps
|
|
107
|
+
for i, group in enumerate(self.param_groups):
|
|
108
|
+
if self.n_count_dec >= self.param_groups[i]['N_min']:
|
|
109
|
+
group['lr_now'] = min(group['lr_now'] * group['fi'], group['max_lr'])
|
|
110
|
+
|
|
111
|
+
# ---------- 3. semi-implicit Euler update ----------
|
|
112
|
+
for group in self.param_groups:
|
|
113
|
+
lr = group['lr_now']
|
|
114
|
+
mass = group['mass']
|
|
115
|
+
for p in group['params']:
|
|
116
|
+
if p.grad is None:
|
|
117
|
+
continue
|
|
118
|
+
grad = p.grad.data
|
|
119
|
+
state = self.state[p]
|
|
120
|
+
vel = state['velocity']
|
|
121
|
+
# a = -grad / mass
|
|
122
|
+
neg_acc = grad / mass # here avoid one O(n) tensor ops
|
|
123
|
+
# v: v = v + a * dt
|
|
124
|
+
vel.add_(neg_acc, alpha=-lr)
|
|
125
|
+
# p: p = p + v * dt
|
|
126
|
+
p.data.add_(vel, alpha=lr)
|
|
127
|
+
|
|
128
|
+
return loss
|
|
129
|
+
|
|
130
|
+
# ---------- store/resume states ----------
|
|
131
|
+
def state_dict(self):
|
|
132
|
+
state = super().state_dict()
|
|
133
|
+
state['n'] = self.n_count_dec
|
|
134
|
+
return state
|
|
135
|
+
|
|
136
|
+
def load_state_dict(self, state_dict):
|
|
137
|
+
self.n_count_dec = state_dict.pop('n')
|
|
138
|
+
super().load_state_dict(state_dict)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class LangevinOptimizer(Optimizer):
|
|
142
|
+
r"""
|
|
143
|
+
Langevin optimizer based on Newton dynamics with Langevin thermostat.
|
|
144
|
+
Use Equipartition theorem or Virial theorem <x_i \partial H / \partial x_i> == k_B T to
|
|
145
|
+
judge whether equilibrium reached, wherein k_B = 1. If equilibrium reached, temperature decreases.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
params: model parameters.
|
|
149
|
+
lr: initial learning rate, i.e., time steplength.
|
|
150
|
+
mass: hyperparameter of mass.
|
|
151
|
+
alpha: damping coefficient.
|
|
152
|
+
temperature: initial virtual temperature.
|
|
153
|
+
anneal_coeff: anneal coefficient to descent temperature.
|
|
154
|
+
balance_tol: relative tolerance of differences between k_B T and the Virial, which calculates as `(Virial - T)/T`
|
|
155
|
+
time_window_size: time window size to count the average <x_i \partial H / \partial x_i>.
|
|
156
|
+
heating_max_steps: the maximum heating steps. Once the number of calling `self.step()` over it, use normal SGD with momenta instead.
|
|
157
|
+
momenta_in_sgd: momentum decay coefficient after annealing.
|
|
158
|
+
"""
|
|
159
|
+
def __init__(
|
|
160
|
+
self,
|
|
161
|
+
params,
|
|
162
|
+
lr=1e-3,
|
|
163
|
+
mass=1.0,
|
|
164
|
+
alpha=50.,
|
|
165
|
+
temperature=1.,
|
|
166
|
+
anneal_coeff=0.9,
|
|
167
|
+
balance_tol=0.1,
|
|
168
|
+
time_window_size=500,
|
|
169
|
+
heating_max_steps=100000,
|
|
170
|
+
momenta_in_sgd=0.6,
|
|
171
|
+
):
|
|
172
|
+
if lr <= 0:
|
|
173
|
+
raise ValueError(f"Invalid lr: {lr}")
|
|
174
|
+
else:
|
|
175
|
+
lr = float(lr)
|
|
176
|
+
if mass <= 0:
|
|
177
|
+
raise ValueError(f"mass must be positive: {mass}")
|
|
178
|
+
else:
|
|
179
|
+
mass = th.scalar_tensor(mass, dtype=th.float32)
|
|
180
|
+
if anneal_coeff > 1. or anneal_coeff < 0.:
|
|
181
|
+
raise ValueError(f"anneal_coeff must be in [0, 1]: {anneal_coeff}")
|
|
182
|
+
time_window_size = int(time_window_size)
|
|
183
|
+
if time_window_size < 1:
|
|
184
|
+
raise ValueError(f"time_window_size must be positive: {time_window_size}")
|
|
185
|
+
|
|
186
|
+
# Virial queue
|
|
187
|
+
self.virials = deque([0.] * time_window_size, maxlen=time_window_size)
|
|
188
|
+
self.Virial_now = 0.
|
|
189
|
+
self.heating_max_steps = heating_max_steps
|
|
190
|
+
self.heating_step_now = 0
|
|
191
|
+
|
|
192
|
+
damp = math.exp(- alpha * lr)
|
|
193
|
+
defaults = dict(
|
|
194
|
+
lr=lr,
|
|
195
|
+
mass=mass,
|
|
196
|
+
damp=damp,
|
|
197
|
+
temperature=temperature,
|
|
198
|
+
anneal_coeff=anneal_coeff,
|
|
199
|
+
balance_tol=balance_tol,
|
|
200
|
+
momenta_in_sgd=momenta_in_sgd
|
|
201
|
+
)
|
|
202
|
+
super().__init__(params, defaults)
|
|
203
|
+
|
|
204
|
+
def step(self, closure=None):
|
|
205
|
+
loss = None
|
|
206
|
+
if closure is not None:
|
|
207
|
+
loss = closure()
|
|
208
|
+
# ---------- ABOBA update ----------
|
|
209
|
+
with th.no_grad():
|
|
210
|
+
for group in self.param_groups:
|
|
211
|
+
if self.heating_step_now >= self.heating_max_steps: # enough annealing, use trivial SGD instead
|
|
212
|
+
lr = group['lr']
|
|
213
|
+
for p in group['params']:
|
|
214
|
+
if p.grad is None:
|
|
215
|
+
continue
|
|
216
|
+
grad = p.grad.data
|
|
217
|
+
state = self.state[p]
|
|
218
|
+
# lazy launch initial veloc.
|
|
219
|
+
if 'velocity' not in state:
|
|
220
|
+
state['velocity'] = th.zeros_like(p.data)
|
|
221
|
+
vel = state['velocity']
|
|
222
|
+
vel.mul_(group['momenta_in_sgd']).add_(grad)
|
|
223
|
+
p.add_(vel, alpha=-lr)
|
|
224
|
+
|
|
225
|
+
else: # keep annealing
|
|
226
|
+
lr = group['lr']
|
|
227
|
+
mass = group['mass']
|
|
228
|
+
damp = group['damp']
|
|
229
|
+
temp = group['temperature']
|
|
230
|
+
anneal_coeff = group['anneal_coeff']
|
|
231
|
+
balance_tol = group['balance_tol']
|
|
232
|
+
xpHpx = 0.
|
|
233
|
+
sizes = 0
|
|
234
|
+
_vir = 0.
|
|
235
|
+
for p in group['params']:
|
|
236
|
+
if p.grad is None:
|
|
237
|
+
continue
|
|
238
|
+
grad = p.grad
|
|
239
|
+
state = self.state[p]
|
|
240
|
+
# lazy launch initial veloc.
|
|
241
|
+
if 'velocity' not in state:
|
|
242
|
+
state['velocity'] = th.randn_like(p) * th.sqrt(temp / mass)
|
|
243
|
+
vel = state['velocity']
|
|
244
|
+
# Calc. Virial
|
|
245
|
+
#xpHpx += th.sum(p * grad) # x_i * \partial H / \partial x_i
|
|
246
|
+
sizes += p.numel()
|
|
247
|
+
_vir += (vel.pow(2).sum() * 0.5 * mass).item()
|
|
248
|
+
mass = mass.to(p.dtype).to(p.device)
|
|
249
|
+
# ABOBA
|
|
250
|
+
vel.addcdiv_(grad, mass, value=- 0.5 * lr)
|
|
251
|
+
p.add_(vel, alpha=0.5 * lr)
|
|
252
|
+
# stochastic update velocity
|
|
253
|
+
vel.mul_(damp)
|
|
254
|
+
vel.add_(th.sqrt((temp * (1 - damp ** 2)) / mass) * th.randn_like(vel))
|
|
255
|
+
# V = damp * V + th.sqrt((self.T_init * (1 - damp ** 2)) / masses) * th.randn_like(V)
|
|
256
|
+
# the rest half-step
|
|
257
|
+
p.add_(vel, alpha=0.5 * lr)
|
|
258
|
+
vel.addcdiv_(grad, mass, value=-0.5 * lr)
|
|
259
|
+
|
|
260
|
+
# Annealing, check the Equipartition theorem
|
|
261
|
+
self.Virial_now -= self.virials[0] # sub the head
|
|
262
|
+
#_vir = xpHpx / sizes
|
|
263
|
+
_vir /= sizes
|
|
264
|
+
self.virials.append(_vir)
|
|
265
|
+
self.Virial_now += _vir
|
|
266
|
+
#print(f"Virial: {(self.Virial_now/len(self.virials)): <.4e}, T: {temp}")
|
|
267
|
+
#avg_grad_norm = sum(p.grad.norm().item() for p in group['params'] if p.grad is not None) / len(group['params'])
|
|
268
|
+
#avg_param_norm = sum(p.norm().item() for p in group['params']) / len(group['params'])
|
|
269
|
+
#print(f"Step {self.heating_step_now}: Virial={self.Virial_now/(0.5 * len(self.virials)):.4e}, T={temp:.4e}")
|
|
270
|
+
|
|
271
|
+
#if abs((self.Virial_now/len(self.virials) - temp)/temp) <= balance_tol:
|
|
272
|
+
# group['temperature'] *= anneal_coeff
|
|
273
|
+
if abs((self.Virial_now/(0.5 * len(self.virials)) - temp) / temp) <= balance_tol:
|
|
274
|
+
group['temperature'] *= anneal_coeff
|
|
275
|
+
|
|
276
|
+
self.heating_step_now += 1
|
|
277
|
+
|
|
278
|
+
return loss
|
|
279
|
+
|
|
280
|
+
# ---------- store/resume states ----------
|
|
281
|
+
def state_dict(self):
|
|
282
|
+
state = super().state_dict()
|
|
283
|
+
state.update({
|
|
284
|
+
'virials': self.virials, 'Virial_now': self.Virial_now,
|
|
285
|
+
'heating_step_now': self.heating_step_now,
|
|
286
|
+
'heating_max_steps': self.heating_max_steps,
|
|
287
|
+
})
|
|
288
|
+
return state
|
|
289
|
+
|
|
290
|
+
def load_state_dict(self, state_dict):
|
|
291
|
+
self.virials = state_dict.pop('virials')
|
|
292
|
+
self.virials = deque(self.virials, maxlen=len(self.virials))
|
|
293
|
+
self.Virial_now = state_dict.pop('Virial_now')
|
|
294
|
+
self.heating_step_now = state_dict.pop('heating_step_now')
|
|
295
|
+
self.heating_max_steps = state_dict.pop('heating_max_steps')
|
|
296
|
+
super().load_state_dict(state_dict)
|