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,972 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Line Search for optimizations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Copyright (c) 2024-2025.7.4, BUCToolkit.
|
|
6
|
+
# Authors: Pu Pengxin, Song Xin
|
|
7
|
+
# Version: 0.9a
|
|
8
|
+
# File: _line_search.py
|
|
9
|
+
# Environment: Python 3.12
|
|
10
|
+
|
|
11
|
+
import warnings
|
|
12
|
+
from typing import Any, Literal, Sequence, Tuple, Callable, Dict
|
|
13
|
+
import logging
|
|
14
|
+
import sys
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
import torch as th
|
|
18
|
+
from torch import nn
|
|
19
|
+
|
|
20
|
+
from BUCToolkit.utils.index_ops import index_reduce, index_inner_product
|
|
21
|
+
from BUCToolkit.utils.setup_loggers import has_any_handler,clear_all_handlers, BaseIO
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LineSearch(BaseIO):
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
method: Literal['Backtrack', 'B', 'Wolfe', 'W', 'MT', 'EXACT', 'None', 'N'] = 'Backtrack',
|
|
28
|
+
maxiter: int = 10,
|
|
29
|
+
thres: float = 0.02,
|
|
30
|
+
factor: float = 1.5,
|
|
31
|
+
verbose: int = 1,
|
|
32
|
+
hold_samples: bool = False,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Line Search for Optimizations.
|
|
36
|
+
Args:
|
|
37
|
+
method: the method to use for line search.
|
|
38
|
+
'Backtrack': Backtrack line search to satisfy Armijo's condition.
|
|
39
|
+
'B': Alias for 'Backtrack'.
|
|
40
|
+
'Wolfe': More-Thuente algorithm line search to satisfy Wolfe-Powell (weak Wolfe) condition.
|
|
41
|
+
'W': Alias for 'Wolfe'.
|
|
42
|
+
'MT': Alias for 'Wolfe'.
|
|
43
|
+
'EXACT': Exact line search by modified More-Thuente algorithm. (or some kind of Brent)
|
|
44
|
+
'None': No line search. Directly return input steplength.
|
|
45
|
+
maxiter: maximum number of line search iterations.
|
|
46
|
+
thres: directional gradient norm threshold for exact line search. DO NOT WORK FOR INEXACT SEARCH ('B', 'W', 'N').
|
|
47
|
+
factor: the factor used for backtrack line search.
|
|
48
|
+
verbose: verbosity level.
|
|
49
|
+
hold_samples: whether to hold samples during line search iterations.
|
|
50
|
+
"""
|
|
51
|
+
self.method = method
|
|
52
|
+
self.maxiter = maxiter
|
|
53
|
+
self.linesearch_thres = thres
|
|
54
|
+
self.factor = factor
|
|
55
|
+
self.factor2 = 0.9/maxiter # relaxation factor that might be used in backtracking algo.
|
|
56
|
+
self.require_grad = False
|
|
57
|
+
self.verbose = verbose
|
|
58
|
+
self._stp_list = list()
|
|
59
|
+
self._min_steplength_cache = th.scalar_tensor(1e-4)
|
|
60
|
+
self._epsilon_cache = th.scalar_tensor(1e-20)
|
|
61
|
+
self.steplength = None
|
|
62
|
+
self._hold_samples = hold_samples
|
|
63
|
+
|
|
64
|
+
ALGORITHMS = {
|
|
65
|
+
'B': self._backtrack,
|
|
66
|
+
'W': self._more_thuente,
|
|
67
|
+
'E': self._exact,
|
|
68
|
+
'N': self._fix_step,
|
|
69
|
+
}
|
|
70
|
+
# Literal['Backtrack', 'B', 'Wolfe', 'W', 'MT', 'EXACT', 'BRENT', 'None', 'N']
|
|
71
|
+
if method == 'MT': method = 'W'
|
|
72
|
+
try:
|
|
73
|
+
self._updater = ALGORITHMS[method[0].capitalize()]
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logging.fatal(f"Unknown line search method '{method}'. Error: {e}.")
|
|
76
|
+
raise RuntimeError(f"Unknown line search method '{method}'. Error: {e}.")
|
|
77
|
+
|
|
78
|
+
self.MAX_STEPLENGTH_COEFFICIENT = 3.
|
|
79
|
+
self.WOLFE_RHO = 0.001 if method[0] == 'W' else 0.4
|
|
80
|
+
self.WOLFE_BETA = 0.4
|
|
81
|
+
|
|
82
|
+
# logger
|
|
83
|
+
super().__init__()
|
|
84
|
+
self.init_logger('Main.OPT.LineSearch')
|
|
85
|
+
|
|
86
|
+
def _update_batch(self, mask: th.Tensor, func_args: Tuple, func_kwargs: Dict, grad_func_args: Tuple, grad_func_kwargs: Dict):
|
|
87
|
+
"""
|
|
88
|
+
Default update method for the input of func if the func has non-opt variables, i.e., the identical transform.
|
|
89
|
+
Args:
|
|
90
|
+
mask:
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
return func_args, func_kwargs, grad_func_args, grad_func_kwargs
|
|
96
|
+
|
|
97
|
+
def set_batch_updater(
|
|
98
|
+
self,
|
|
99
|
+
method: Callable[[th.Tensor, Tuple | None, Dict | None, Tuple | None, Dict | None], Tuple[Tuple, Dict, Tuple, Dict]] | None = None
|
|
100
|
+
) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Set a method to update the taget function when variables change.
|
|
103
|
+
If input Callables, this Callable receives a mask tensor of shape (n_batch, )
|
|
104
|
+
that only selects the `True` part to input to the function, the old
|
|
105
|
+
`func_args`, `func_kwargs`, `grad_func_args`, and `grad_func_kwargs`,
|
|
106
|
+
returns the corresponding masked new `func_args`, `func_kwargs`, `grad_func_args`, and `grad_func_kwargs`.
|
|
107
|
+
|
|
108
|
+
If input None, self._hold_samples will be set to True that toggles off the dynamic removal.
|
|
109
|
+
|
|
110
|
+
This method is used to dynamically 'remove' the samples which have been converged in a batch to avoid
|
|
111
|
+
redundant calculation of converged samples.
|
|
112
|
+
|
|
113
|
+
Default transform is identical transform (i.e., do nothing)
|
|
114
|
+
Args:
|
|
115
|
+
method: Callable(mask: Tensor, func_args: Tuple, func_kwargs: Dict, grad_func_args: Tuple, grad_func_kwargs: Dict) -> Tuple[Tuple, Dict, Tuple, Dict],
|
|
116
|
+
the method of updating function arguments for a mask.
|
|
117
|
+
|
|
118
|
+
Returns: None
|
|
119
|
+
"""
|
|
120
|
+
if method is None:
|
|
121
|
+
self._hold_samples = True
|
|
122
|
+
elif callable(method):
|
|
123
|
+
self._update_batch = method
|
|
124
|
+
else:
|
|
125
|
+
raise TypeError(f'`method` must be a callable, but {type(method)} is not.')
|
|
126
|
+
|
|
127
|
+
def _Armijo_cond_irreg(
|
|
128
|
+
self,
|
|
129
|
+
func: Any | nn.Module,
|
|
130
|
+
X0: th.Tensor,
|
|
131
|
+
y0: th.Tensor,
|
|
132
|
+
direct_grad0: th.Tensor,
|
|
133
|
+
p: th.Tensor,
|
|
134
|
+
steplength: th.Tensor, # (1, n_true_batch, 1)
|
|
135
|
+
batch_scatter_indices: th.Tensor,
|
|
136
|
+
rho: float = 0.05,
|
|
137
|
+
func_args: Sequence = tuple(),
|
|
138
|
+
func_kwargs=None
|
|
139
|
+
) -> th.Tensor:
|
|
140
|
+
"""
|
|
141
|
+
Armijo conditioned for irregular batches.
|
|
142
|
+
|
|
143
|
+
Returns: (1, n_batch, 1)
|
|
144
|
+
|
|
145
|
+
"""
|
|
146
|
+
if func_kwargs is None:
|
|
147
|
+
func_kwargs = dict()
|
|
148
|
+
steplength_long = steplength.index_select(1, batch_scatter_indices)
|
|
149
|
+
_X_cache = th.addcmul(X0, steplength_long, p, value=1.) # (1, B*A, D)
|
|
150
|
+
y1 = func(_X_cache, *func_args, **func_kwargs) # (B, )
|
|
151
|
+
#direct_grad0 = th.sum(index_inner_product(grad, p, 1, batch_scatter_indices), dim=-1) # (1, B, 1)
|
|
152
|
+
# (1, B, 1) <= (1, B, 1) + 1. * (1, B, 1) * (1, B, 1)
|
|
153
|
+
amj_mask: th.Tensor = (y1.unsqueeze(-1) <= th.addcmul(y0.unsqueeze(-1), steplength, direct_grad0, value=rho))
|
|
154
|
+
return amj_mask # (1, B, 1)
|
|
155
|
+
|
|
156
|
+
def _Armijo_cond_reg(
|
|
157
|
+
self,
|
|
158
|
+
func: Any | nn.Module,
|
|
159
|
+
X0: th.Tensor,
|
|
160
|
+
y0: th.Tensor,
|
|
161
|
+
direct_grad0: th.Tensor,
|
|
162
|
+
p: th.Tensor,
|
|
163
|
+
steplength: th.Tensor, # (1, n_true_batch, 1)
|
|
164
|
+
batch_scatter_indices: th.Tensor,
|
|
165
|
+
rho: float = 0.05,
|
|
166
|
+
func_args: Sequence = tuple(),
|
|
167
|
+
func_kwargs=None
|
|
168
|
+
) -> th.Tensor:
|
|
169
|
+
"""
|
|
170
|
+
Armijo conditioned for normal batches.
|
|
171
|
+
|
|
172
|
+
Returns: (1, n_batch, 1)
|
|
173
|
+
|
|
174
|
+
"""
|
|
175
|
+
if func_kwargs is None:
|
|
176
|
+
func_kwargs = dict()
|
|
177
|
+
_X_cache = th.addcmul(X0, steplength, p, value=1.) # (B, A, D)
|
|
178
|
+
y1 = func(_X_cache, *func_args, **func_kwargs) # (B, )
|
|
179
|
+
amj_mask: th.Tensor = (y1.reshape(-1, 1, 1) <= (y0.reshape(-1, 1, 1) + rho * steplength * direct_grad0))
|
|
180
|
+
return amj_mask # (n_batch, 1, 1)
|
|
181
|
+
|
|
182
|
+
def _inv_Armijo_cond_irreg(
|
|
183
|
+
self,
|
|
184
|
+
func: Any | nn.Module,
|
|
185
|
+
X0: th.Tensor,
|
|
186
|
+
y0: th.Tensor,
|
|
187
|
+
direct_grad0: th.Tensor,
|
|
188
|
+
p: th.Tensor,
|
|
189
|
+
steplength: th.Tensor,
|
|
190
|
+
batch_scatter_indices: th.Tensor,
|
|
191
|
+
rho: float = 0.05,
|
|
192
|
+
func_args: Sequence = tuple(),
|
|
193
|
+
func_kwargs=None
|
|
194
|
+
) -> th.Tensor:
|
|
195
|
+
"""
|
|
196
|
+
Inverse Armijo conditioned for irregular batches, i.e., the sufficient raising condition.
|
|
197
|
+
|
|
198
|
+
"""
|
|
199
|
+
if func_kwargs is None:
|
|
200
|
+
func_kwargs = dict()
|
|
201
|
+
steplength_long = steplength.index_select(1, batch_scatter_indices)
|
|
202
|
+
_X_cache = th.addcmul(X0, steplength_long, p, value=1.) # (1, B*A, D)
|
|
203
|
+
y1 = func(_X_cache, *func_args, **func_kwargs) # (B, )
|
|
204
|
+
#direct_grad0 = th.sum(index_inner_product(grad, p, 1, batch_scatter_indices), dim=-1) # (1, B, 1)
|
|
205
|
+
amj_mask: th.Tensor = (y1.unsqueeze(-1) > th.addcmul(y0.unsqueeze(-1), steplength, direct_grad0, value=rho))
|
|
206
|
+
return amj_mask # (1, B, 1)
|
|
207
|
+
|
|
208
|
+
def _Wolfe_cond_irreg(
|
|
209
|
+
self,
|
|
210
|
+
func: Any | nn.Module,
|
|
211
|
+
grad_func: Any | nn.Module,
|
|
212
|
+
X0: th.Tensor,
|
|
213
|
+
y0: th.Tensor,
|
|
214
|
+
direct_grad0: th.Tensor,
|
|
215
|
+
p: th.Tensor,
|
|
216
|
+
steplength: th.Tensor,
|
|
217
|
+
is_grad_func_contain_y: bool,
|
|
218
|
+
batch_scatter_indices: th.Tensor,
|
|
219
|
+
rho: float = 0.05,
|
|
220
|
+
beta: float = 0.9,
|
|
221
|
+
func_args: Sequence = tuple(),
|
|
222
|
+
func_kwargs=None,
|
|
223
|
+
grad_func_args: Sequence = tuple(),
|
|
224
|
+
grad_func_kwargs=None,
|
|
225
|
+
) -> Tuple[th.Tensor, th.Tensor, th.Tensor, th.Tensor]:
|
|
226
|
+
"""
|
|
227
|
+
Weak Wolfe-Powell condition.
|
|
228
|
+
Returns:
|
|
229
|
+
a: (1, B, 1), Armijo condition mask
|
|
230
|
+
b: (1, B, 1), Wolfe gradient condition mask
|
|
231
|
+
y1: (B, ), function value at current steplength
|
|
232
|
+
direct_grad1: (1, B, 1), direction gradient at current point
|
|
233
|
+
|
|
234
|
+
"""
|
|
235
|
+
if func_kwargs is None:
|
|
236
|
+
func_kwargs = dict()
|
|
237
|
+
steplength_long = steplength.index_select(1, batch_scatter_indices)
|
|
238
|
+
_X_cache = th.addcmul(X0, steplength_long, p, value=1.) # (1, B*A, D)
|
|
239
|
+
with th.set_grad_enabled(self.require_grad):
|
|
240
|
+
_X_cache.requires_grad_(self.require_grad)
|
|
241
|
+
y1 = func(_X_cache, *func_args, **func_kwargs) # (B, )
|
|
242
|
+
if is_grad_func_contain_y:
|
|
243
|
+
dy1 = grad_func(y1, _X_cache, *grad_func_args, **grad_func_kwargs)
|
|
244
|
+
else:
|
|
245
|
+
dy1 = grad_func(_X_cache, *grad_func_args, **grad_func_kwargs)
|
|
246
|
+
# detach grad graph
|
|
247
|
+
dy1 = dy1.detach() # (1, B*A, D)
|
|
248
|
+
y1 = y1.detach() # (B, )
|
|
249
|
+
direct_grad1 = th.sum(index_inner_product(dy1, p, 1, batch_scatter_indices), dim=-1, keepdim=True) # (1, B, 1)
|
|
250
|
+
amj: th.Tensor = (y1.unsqueeze(-1) <= th.addcmul(y0.unsqueeze(-1), steplength, direct_grad0, value=rho)) # descent cond, (1, B, 1)
|
|
251
|
+
curv = (direct_grad1 > beta * direct_grad0) # curve cond, (1, B, 1)
|
|
252
|
+
|
|
253
|
+
return amj, curv, y1, direct_grad1
|
|
254
|
+
|
|
255
|
+
def _Wolfe_cond_reg(
|
|
256
|
+
self,
|
|
257
|
+
func: Any | nn.Module,
|
|
258
|
+
grad_func: Any | nn.Module,
|
|
259
|
+
X0: th.Tensor,
|
|
260
|
+
y0: th.Tensor,
|
|
261
|
+
direct_grad0: th.Tensor,
|
|
262
|
+
p: th.Tensor,
|
|
263
|
+
steplength: th.Tensor, # (n_batch, 1, 1)
|
|
264
|
+
is_grad_func_contain_y: bool,
|
|
265
|
+
batch_scatter_indices: None = None, # a placeholder
|
|
266
|
+
rho: float = 0.05,
|
|
267
|
+
beta: float = 0.9,
|
|
268
|
+
func_args: Sequence = tuple(),
|
|
269
|
+
func_kwargs=None,
|
|
270
|
+
grad_func_args: Sequence = tuple(),
|
|
271
|
+
grad_func_kwargs=None,
|
|
272
|
+
) -> Tuple[th.Tensor, th.Tensor, th.Tensor, th.Tensor]:
|
|
273
|
+
"""
|
|
274
|
+
Weak Wolfe-Powell condition.
|
|
275
|
+
Returns:
|
|
276
|
+
a: (B, 1, 1), Armijo condition mask
|
|
277
|
+
b: (B, 1, 1), Wolfe gradient condition mask
|
|
278
|
+
y1: (B, ), function value at current steplength
|
|
279
|
+
direct_grad1: (B, 1, 1), direction gradient at current point
|
|
280
|
+
|
|
281
|
+
"""
|
|
282
|
+
if func_kwargs is None:
|
|
283
|
+
func_kwargs = dict()
|
|
284
|
+
_X_cache = th.addcmul(X0, steplength, p, value=1.) # (B, A, D)
|
|
285
|
+
with th.set_grad_enabled(self.require_grad):
|
|
286
|
+
_X_cache.requires_grad_(self.require_grad)
|
|
287
|
+
y1 = func(_X_cache, *func_args, **func_kwargs) # (B, )
|
|
288
|
+
if is_grad_func_contain_y:
|
|
289
|
+
dy1 = grad_func(y1, _X_cache, *grad_func_args, **grad_func_kwargs)
|
|
290
|
+
else:
|
|
291
|
+
dy1 = grad_func(_X_cache, *grad_func_args, **grad_func_kwargs)
|
|
292
|
+
# detach grad graph
|
|
293
|
+
dy1 = dy1.detach() # (B, A, D)
|
|
294
|
+
y1 = y1.detach() # (B, )
|
|
295
|
+
direct_grad1 = th.sum(dy1 * p, dim=(-2, -1), keepdim=True) # (B, 1, 1)
|
|
296
|
+
a: th.Tensor = (y1.reshape(-1, 1, 1) <= th.addcmul(y0.reshape(-1, 1, 1), steplength, direct_grad0, value=rho)) # descent cond, (B, 1, 1)
|
|
297
|
+
b = (direct_grad1 > beta * direct_grad0) # curve cond, (B, 1, 1)
|
|
298
|
+
|
|
299
|
+
return a, b, y1, direct_grad1
|
|
300
|
+
|
|
301
|
+
def _backtrack_update(self, steplength_in, i_: int):
|
|
302
|
+
# applied shrinking
|
|
303
|
+
steplength_out = (steplength_in - (steplength_in - 1e-4)*((i_ + 1)/self.maxiter)**self.factor)
|
|
304
|
+
# update factor. If not converge, decrease the factor.
|
|
305
|
+
return steplength_out
|
|
306
|
+
|
|
307
|
+
def _3ord_hermite_interp(
|
|
308
|
+
self,
|
|
309
|
+
x_h,
|
|
310
|
+
y_h,
|
|
311
|
+
g_h,
|
|
312
|
+
x_l,
|
|
313
|
+
y_l,
|
|
314
|
+
g_l
|
|
315
|
+
):
|
|
316
|
+
"""
|
|
317
|
+
Third order hermite interpolation to find the 2 stationary points.
|
|
318
|
+
Args:
|
|
319
|
+
x_h: x of the high endpoint
|
|
320
|
+
y_h: function value of the high endpoint
|
|
321
|
+
g_h: function directional gradient of the high endpoint
|
|
322
|
+
x_l: x of the low endpoint
|
|
323
|
+
y_l: function value of the low endpoint
|
|
324
|
+
g_l: function directional gradient of the low endpoint
|
|
325
|
+
Returns:
|
|
326
|
+
neg_Delta_mask: the mask shows where Delta (b^2 - 4 * a * c) < 0, leading to imag. root.
|
|
327
|
+
s1: the shorter stationary points corresponding to the smaller root of 2nd order equation of derivation.
|
|
328
|
+
s2: the longer stationary points corresponding to the larger root of 2nd order equation of derivation.
|
|
329
|
+
|
|
330
|
+
"""
|
|
331
|
+
_h = x_h - x_l
|
|
332
|
+
_gh_plus_gl = g_h + g_l
|
|
333
|
+
# _a = 6. * (y_h - y_l) - 3. * _h * (g_h + g_l)
|
|
334
|
+
_a: th.Tensor = 6. * y_h
|
|
335
|
+
_a.add_(y_l, alpha = -6.)
|
|
336
|
+
_a.addcmul_(_h, _gh_plus_gl, value=-3.)
|
|
337
|
+
# _b = - 6. * (y_h - y_l) + 2. * _h * (g_h + 2 * g_l)
|
|
338
|
+
_b: th.Tensor = -6. * y_h
|
|
339
|
+
_b.add_(y_l, alpha = 6.)
|
|
340
|
+
_gh_plus_gl.add_(g_l) # WARNING: IN-PLACE change of _gh_plus_gl.
|
|
341
|
+
_b.addcmul_(_h, _gh_plus_gl, value=2.)
|
|
342
|
+
_c = _h * g_l
|
|
343
|
+
# D = _b ** 2 - 4. * _a * _c
|
|
344
|
+
D = th.addcmul(_b**2, _a, _c, value=-4.)
|
|
345
|
+
_neg_D_mask = (D < 0.)
|
|
346
|
+
# q = - _b +- th.sqrt(D)
|
|
347
|
+
(D.clamp_min_(self._epsilon_cache)).sqrt_() # WARNING: IN-PLACE change of D.
|
|
348
|
+
_a.add_(self._epsilon_cache)
|
|
349
|
+
q1 = - _b - D
|
|
350
|
+
q2 = - _b + D
|
|
351
|
+
#interp3_1 = x_l + _h * q1 / (2 * _a.clamp_min_(self._epsilon_cache))
|
|
352
|
+
#interp3_2 = x_l + _h * q2 / (2 * _a.clamp_min_(self._epsilon_cache))
|
|
353
|
+
interp3_1 = th.addcdiv(x_l, _h * q1, _a, value=0.5)
|
|
354
|
+
interp3_2 = th.addcdiv(x_l, _h * q2, _a, value=0.5)
|
|
355
|
+
|
|
356
|
+
return _neg_D_mask, interp3_1, interp3_2
|
|
357
|
+
|
|
358
|
+
def _2ord_interp(
|
|
359
|
+
self,
|
|
360
|
+
x_h,
|
|
361
|
+
y_h,
|
|
362
|
+
x_l,
|
|
363
|
+
y_l,
|
|
364
|
+
g_l
|
|
365
|
+
):
|
|
366
|
+
"""
|
|
367
|
+
Second order interpolation to find the stationary points.
|
|
368
|
+
Args:
|
|
369
|
+
x_h:
|
|
370
|
+
y_h:
|
|
371
|
+
x_l:
|
|
372
|
+
y_l:
|
|
373
|
+
g_l:
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
"""
|
|
377
|
+
# y = x_l - (g_l * (x_h - x_l) ** 2) / (2 * (y_h - y_l - g_l * (x_h - x_l)))
|
|
378
|
+
# = x_l - (g_l * d_xhl ** 2) / (2 * (y_h - y_l - g_l * d_xhl))
|
|
379
|
+
# = {x_l - 0.5 * (g_l * d_xhl ** 2) / (y_h - {y_l + g_l * d_xhl})}
|
|
380
|
+
d_xhl: th.Tensor = x_h - x_l
|
|
381
|
+
y_add_gd = th.addcmul(y_l, g_l, d_xhl)
|
|
382
|
+
d_xhl.mul_(d_xhl)
|
|
383
|
+
y = th.addcdiv(x_l, g_l * d_xhl, (y_h - y_add_gd).add_(self._epsilon_cache), value=-0.5)
|
|
384
|
+
return y
|
|
385
|
+
|
|
386
|
+
def _backtrack(
|
|
387
|
+
self,
|
|
388
|
+
func: Any | nn.Module,
|
|
389
|
+
grad_func: Any | nn.Module,
|
|
390
|
+
X0: th.Tensor,
|
|
391
|
+
y0: th.Tensor,
|
|
392
|
+
grad: th.Tensor,
|
|
393
|
+
p: th.Tensor,
|
|
394
|
+
steplength: th.Tensor | float,
|
|
395
|
+
is_grad_func_contain_y: bool,
|
|
396
|
+
func_args: Sequence = tuple(),
|
|
397
|
+
func_kwargs=None,
|
|
398
|
+
grad_func_args: Sequence = tuple(),
|
|
399
|
+
grad_func_kwargs=None,
|
|
400
|
+
batch_indices: th.Tensor = None
|
|
401
|
+
):
|
|
402
|
+
"""
|
|
403
|
+
Simple backtracking scheme with Armijo condition.
|
|
404
|
+
|
|
405
|
+
Returns: (B, 1, 1) for regular batches, and (1, B, 1) for irregular batches.
|
|
406
|
+
|
|
407
|
+
"""
|
|
408
|
+
self.factor = max(self.factor, 0.2)
|
|
409
|
+
self._min_steplength_cache = self._min_steplength_cache.to(X0.device)
|
|
410
|
+
_steplength = steplength
|
|
411
|
+
is_converge = False
|
|
412
|
+
batch_tensor_indx_cache = th.arange(len(batch_indices), dtype=th.int64, device=X0.device)
|
|
413
|
+
batch_scatter = th.repeat_interleave(
|
|
414
|
+
batch_tensor_indx_cache,
|
|
415
|
+
batch_indices,
|
|
416
|
+
dim=0
|
|
417
|
+
)
|
|
418
|
+
converge_check = th.atleast_1d(th.full_like(steplength, False, dtype=th.bool).squeeze_())
|
|
419
|
+
if batch_indices is None:
|
|
420
|
+
direct_grad0 = th.sum(grad * p, dim=(-1, -2), keepdim=True) # (B, 1, 1)
|
|
421
|
+
Armijo_updater = self._Armijo_cond_reg
|
|
422
|
+
else:
|
|
423
|
+
direct_grad0 = th.sum(index_inner_product(grad, p, 1, batch_scatter), dim=-1, keepdim=True) # (1, B, 1)
|
|
424
|
+
Armijo_updater = self._Armijo_cond_irreg
|
|
425
|
+
|
|
426
|
+
# main loop
|
|
427
|
+
for i in range(self.maxiter):
|
|
428
|
+
if not self._hold_samples:
|
|
429
|
+
func_args_, func_kwargs_, grad_func_args_, grad_func_kwargs_ = self._update_batch(
|
|
430
|
+
~converge_check,
|
|
431
|
+
func_args,
|
|
432
|
+
func_kwargs,
|
|
433
|
+
grad_func_args,
|
|
434
|
+
grad_func_kwargs
|
|
435
|
+
)
|
|
436
|
+
if batch_indices is not None:
|
|
437
|
+
select_mask = ~converge_check
|
|
438
|
+
select_mask_long = select_mask[batch_scatter]
|
|
439
|
+
y0_ = y0[select_mask]
|
|
440
|
+
direct_grad0_ = direct_grad0[:, select_mask, :]
|
|
441
|
+
p_ = p[:, select_mask_long, :]
|
|
442
|
+
X0_ = X0[:, select_mask_long, :]
|
|
443
|
+
_steplength_ = _steplength[:, select_mask, :]
|
|
444
|
+
ori_steplength_ = steplength[:, select_mask, :]
|
|
445
|
+
batch_tensor_ = batch_indices[select_mask]
|
|
446
|
+
batch_scatter_ = th.repeat_interleave(
|
|
447
|
+
batch_tensor_indx_cache[:len(batch_tensor_)],
|
|
448
|
+
batch_tensor_,
|
|
449
|
+
dim=0
|
|
450
|
+
)
|
|
451
|
+
else:
|
|
452
|
+
select_mask = ~converge_check
|
|
453
|
+
y0_ = y0[select_mask]
|
|
454
|
+
direct_grad0_ = direct_grad0[select_mask, ...]
|
|
455
|
+
p_ = p[select_mask, ...]
|
|
456
|
+
X0_ = X0[select_mask, ...]
|
|
457
|
+
_steplength_ = _steplength[select_mask, ...]
|
|
458
|
+
ori_steplength_ = steplength[:, select_mask, :]
|
|
459
|
+
batch_tensor_ = None
|
|
460
|
+
batch_scatter_ = None
|
|
461
|
+
else:
|
|
462
|
+
func_args_, func_kwargs_, grad_func_args_, grad_func_kwargs_ = (
|
|
463
|
+
func_args,
|
|
464
|
+
func_kwargs,
|
|
465
|
+
grad_func_args,
|
|
466
|
+
grad_func_kwargs
|
|
467
|
+
)
|
|
468
|
+
select_mask = ~converge_check
|
|
469
|
+
y0_ = y0
|
|
470
|
+
direct_grad0_ = direct_grad0
|
|
471
|
+
p_ = p
|
|
472
|
+
X0_ = X0
|
|
473
|
+
_steplength_ = _steplength
|
|
474
|
+
ori_steplength_ = steplength
|
|
475
|
+
batch_tensor_ = batch_indices
|
|
476
|
+
batch_scatter_ = batch_indices
|
|
477
|
+
# Section: main update
|
|
478
|
+
mask = Armijo_updater(
|
|
479
|
+
func,
|
|
480
|
+
X0_,
|
|
481
|
+
y0_,
|
|
482
|
+
direct_grad0_,
|
|
483
|
+
p_,
|
|
484
|
+
_steplength_,
|
|
485
|
+
batch_scatter_,
|
|
486
|
+
rho=0.05,
|
|
487
|
+
func_args=func_args_,
|
|
488
|
+
func_kwargs=func_kwargs_
|
|
489
|
+
)
|
|
490
|
+
converge_check_ = th.atleast_1d(mask.squeeze())
|
|
491
|
+
if th.all(mask):
|
|
492
|
+
is_converge = True
|
|
493
|
+
break
|
|
494
|
+
# A kind of 'slow' backtrack.
|
|
495
|
+
_steplength_ = th.where(mask, _steplength_, self._backtrack_update(ori_steplength_, i))
|
|
496
|
+
if self.verbose > 1:
|
|
497
|
+
self.logger.info(
|
|
498
|
+
f"Steplength Line Search {i + 1}: "
|
|
499
|
+
f"{' '.join([f'{_: 6.4f}' for _ in th.atleast_1d(_steplength.squeeze()).tolist()])}"
|
|
500
|
+
)
|
|
501
|
+
# Section: re-write
|
|
502
|
+
if not self._hold_samples:
|
|
503
|
+
if batch_indices is not None:
|
|
504
|
+
select_indices = th.where(select_mask)[0]
|
|
505
|
+
_steplength.index_copy_(1, select_indices, _steplength_)
|
|
506
|
+
converge_check.index_copy_(0, select_indices, converge_check_)
|
|
507
|
+
else:
|
|
508
|
+
select_indices = th.where(select_mask)[0]
|
|
509
|
+
_steplength.index_copy_(0, select_indices, _steplength_)
|
|
510
|
+
converge_check.index_copy_(0, select_indices, converge_check_)
|
|
511
|
+
else:
|
|
512
|
+
_steplength = _steplength_
|
|
513
|
+
converge_check = converge_check_
|
|
514
|
+
|
|
515
|
+
_steplength.clamp_min_(self._min_steplength_cache)
|
|
516
|
+
if not is_converge:
|
|
517
|
+
self.logger.warning(f'WARNING: linear search did not converge in {self.maxiter} steps.')
|
|
518
|
+
return _steplength # (1, B, 1)/(B, 1, 1)
|
|
519
|
+
|
|
520
|
+
def _exact(
|
|
521
|
+
self,
|
|
522
|
+
func: Any | nn.Module,
|
|
523
|
+
grad_func: Any | nn.Module,
|
|
524
|
+
X0: th.Tensor,
|
|
525
|
+
y0: th.Tensor,
|
|
526
|
+
grad: th.Tensor,
|
|
527
|
+
p: th.Tensor,
|
|
528
|
+
steplength: th.Tensor | float,
|
|
529
|
+
is_grad_func_contain_y: bool,
|
|
530
|
+
func_args: Sequence = tuple(),
|
|
531
|
+
func_kwargs=None,
|
|
532
|
+
grad_func_args: Sequence = tuple(),
|
|
533
|
+
grad_func_kwargs=None,
|
|
534
|
+
batch_indices: th.Tensor = None
|
|
535
|
+
):
|
|
536
|
+
raise NotImplementedError
|
|
537
|
+
|
|
538
|
+
def _more_thuente(
|
|
539
|
+
self,
|
|
540
|
+
func: Any | nn.Module,
|
|
541
|
+
grad_func: Any | nn.Module,
|
|
542
|
+
X0: th.Tensor,
|
|
543
|
+
y0: th.Tensor,
|
|
544
|
+
grad: th.Tensor,
|
|
545
|
+
p: th.Tensor,
|
|
546
|
+
steplength: th.Tensor | float,
|
|
547
|
+
is_grad_func_contain_y: bool,
|
|
548
|
+
func_args: Sequence = tuple(),
|
|
549
|
+
func_kwargs=None,
|
|
550
|
+
grad_func_args: Sequence = tuple(),
|
|
551
|
+
grad_func_kwargs=None,
|
|
552
|
+
batch_indices: th.Tensor = None
|
|
553
|
+
):
|
|
554
|
+
"""
|
|
555
|
+
More-Thuente algorithm that search step length satisfied Wolfe condition by interpolation.
|
|
556
|
+
|
|
557
|
+
Returns: (B, 1, 1) for regular batches, and (1, B, 1) for irregular batches.
|
|
558
|
+
|
|
559
|
+
"""
|
|
560
|
+
_steplength = steplength
|
|
561
|
+
_max_steplength = self.MAX_STEPLENGTH_COEFFICIENT * steplength
|
|
562
|
+
_steplength_old = th.full_like(_steplength ,th.inf)
|
|
563
|
+
#self._stp_list.append(X0) <<<<<<<<<<< test
|
|
564
|
+
self._min_steplength_cache = self._min_steplength_cache.to(X0.device)
|
|
565
|
+
self._epsilon_cache = self._epsilon_cache.to(X0.device)
|
|
566
|
+
self.factor = max(self.factor, 0.2)
|
|
567
|
+
is_converge = False
|
|
568
|
+
if batch_indices is None:
|
|
569
|
+
batch_tensor_indx_cache = None
|
|
570
|
+
batch_scatter_indices = None
|
|
571
|
+
direct_grad_orig = th.sum(grad * p, dim=(-2, -1), keepdim=True) # (n_batch, 1, 1)
|
|
572
|
+
direct_grad0 = direct_grad_orig
|
|
573
|
+
WolfCond = self._Wolfe_cond_reg
|
|
574
|
+
_long_shape = (-1, 1, 1)
|
|
575
|
+
else:
|
|
576
|
+
batch_tensor_indx_cache = th.arange(0, len(batch_indices), dtype=th.int64, device=X0.device)
|
|
577
|
+
batch_scatter_indices = th.repeat_interleave(
|
|
578
|
+
batch_tensor_indx_cache,
|
|
579
|
+
batch_indices,
|
|
580
|
+
dim=0
|
|
581
|
+
)
|
|
582
|
+
direct_grad_orig = th.sum(index_inner_product(grad, p, 1, batch_scatter_indices), dim=-1, keepdim=True) # (1, B, 1)
|
|
583
|
+
direct_grad0 = direct_grad_orig
|
|
584
|
+
WolfCond = self._Wolfe_cond_irreg
|
|
585
|
+
_long_shape = (1, -1, 1)
|
|
586
|
+
|
|
587
|
+
# main loop
|
|
588
|
+
# initial intervals
|
|
589
|
+
_left = th.zeros_like(steplength) # step length infimum
|
|
590
|
+
_y_left = y0.reshape(_long_shape)
|
|
591
|
+
_g_left = direct_grad0
|
|
592
|
+
|
|
593
|
+
_right = _max_steplength # step length supremum
|
|
594
|
+
armijo_mask, curve_mask, _y_right, _g_right = WolfCond(
|
|
595
|
+
func,
|
|
596
|
+
grad_func,
|
|
597
|
+
X0,
|
|
598
|
+
y0,
|
|
599
|
+
direct_grad0,
|
|
600
|
+
p,
|
|
601
|
+
_max_steplength, # (B, 1, 1)
|
|
602
|
+
is_grad_func_contain_y,
|
|
603
|
+
batch_scatter_indices,
|
|
604
|
+
rho=self.WOLFE_RHO,
|
|
605
|
+
beta=self.WOLFE_BETA,
|
|
606
|
+
func_args=func_args,
|
|
607
|
+
func_kwargs=func_kwargs,
|
|
608
|
+
grad_func_args=grad_func_args,
|
|
609
|
+
grad_func_kwargs=grad_func_kwargs
|
|
610
|
+
)
|
|
611
|
+
all_armijo = th.all(armijo_mask)
|
|
612
|
+
_is_grad_neg = th.all(_g_right <= 0.)
|
|
613
|
+
if all_armijo and th.all(curve_mask):
|
|
614
|
+
is_converge = True
|
|
615
|
+
return _max_steplength
|
|
616
|
+
elif th.all(armijo_mask) and _is_grad_neg:
|
|
617
|
+
self.logger.warning(
|
|
618
|
+
f'WARNING: the function sufficiently descents even at the max steplength {_max_steplength.max().numpy(force=True).item(): .2f}. '
|
|
619
|
+
f'The neighborhood of current point may be too flat. The max steplength will be used.'
|
|
620
|
+
)
|
|
621
|
+
return _max_steplength
|
|
622
|
+
converge_mask = (armijo_mask & (curve_mask | _is_grad_neg)) # (1, B, 1)
|
|
623
|
+
converge_check = th.atleast_1d(converge_mask.squeeze()) # a short version of conv. mask
|
|
624
|
+
cumulative_mask = th.full_like(_steplength, False, dtype=th.bool)
|
|
625
|
+
backup_steplength = th.full_like(_steplength, 1.e-4)
|
|
626
|
+
_y_right = _y_right.reshape(_long_shape)
|
|
627
|
+
for i in range(self.maxiter):
|
|
628
|
+
# back up the steplength that at least met Armijo cond.
|
|
629
|
+
first_time_mask = armijo_mask & (~cumulative_mask) # the mask of first time meet Armijo
|
|
630
|
+
backup_steplength = th.where(first_time_mask, _steplength, backup_steplength)
|
|
631
|
+
cumulative_mask.bitwise_or_(armijo_mask)
|
|
632
|
+
# Section: select unconverged samples
|
|
633
|
+
if not self._hold_samples:
|
|
634
|
+
func_args_, func_kwargs_, grad_func_args_, grad_func_kwargs_ = self._update_batch(
|
|
635
|
+
~converge_check,
|
|
636
|
+
func_args,
|
|
637
|
+
func_kwargs,
|
|
638
|
+
grad_func_args,
|
|
639
|
+
grad_func_kwargs
|
|
640
|
+
)
|
|
641
|
+
if batch_scatter_indices is not None:
|
|
642
|
+
select_mask = ~converge_check
|
|
643
|
+
select_mask_long = select_mask[batch_scatter_indices]
|
|
644
|
+
y0_ = y0[select_mask]
|
|
645
|
+
direct_grad0_ = direct_grad0[:, select_mask, :]
|
|
646
|
+
p_ = p[:, select_mask_long, :]
|
|
647
|
+
X0_ = X0[:, select_mask_long, :]
|
|
648
|
+
_steplength_ = _steplength[:, select_mask, :]
|
|
649
|
+
batch_tensor_ = batch_indices[select_mask]
|
|
650
|
+
batch_scatter_ = th.repeat_interleave(
|
|
651
|
+
batch_tensor_indx_cache[:len(batch_tensor_)],
|
|
652
|
+
batch_tensor_,
|
|
653
|
+
dim=0
|
|
654
|
+
)
|
|
655
|
+
_left_ = _left[:, select_mask, :]
|
|
656
|
+
_y_left_ = _y_left[:, select_mask, :]
|
|
657
|
+
_g_left_ = _g_left[:, select_mask, :]
|
|
658
|
+
_right_ = _right[:, select_mask, :]
|
|
659
|
+
_y_right_ = _y_right[:, select_mask, :]
|
|
660
|
+
_g_right_ = _g_right[:, select_mask, :]
|
|
661
|
+
else:
|
|
662
|
+
select_mask = ~converge_check
|
|
663
|
+
y0_ = y0[select_mask]
|
|
664
|
+
direct_grad0_ = direct_grad0[select_mask, ...]
|
|
665
|
+
p_ = p[select_mask, ...]
|
|
666
|
+
X0_ = X0[select_mask, ...]
|
|
667
|
+
_steplength_ = _steplength[select_mask, ...]
|
|
668
|
+
batch_tensor_ = None
|
|
669
|
+
batch_scatter_ = None
|
|
670
|
+
_left_ = _left[select_mask, ...]
|
|
671
|
+
_y_left_ = _y_left[select_mask, ...]
|
|
672
|
+
_g_left_ = _g_left[select_mask, ...]
|
|
673
|
+
_right_ = _right[select_mask, ...]
|
|
674
|
+
_y_right_ = _y_right[select_mask, ...]
|
|
675
|
+
_g_right_ = _g_right[select_mask, ...]
|
|
676
|
+
else:
|
|
677
|
+
func_args_, func_kwargs_, grad_func_args_, grad_func_kwargs_ = (
|
|
678
|
+
func_args,
|
|
679
|
+
func_kwargs,
|
|
680
|
+
grad_func_args,
|
|
681
|
+
grad_func_kwargs
|
|
682
|
+
)
|
|
683
|
+
select_mask = ~converge_check
|
|
684
|
+
y0_ = y0
|
|
685
|
+
direct_grad0_ = direct_grad0
|
|
686
|
+
p_ = p
|
|
687
|
+
X0_ = X0
|
|
688
|
+
_steplength_ = _steplength
|
|
689
|
+
batch_tensor_ = batch_indices
|
|
690
|
+
batch_scatter_ = batch_scatter_indices
|
|
691
|
+
_left_ = _left
|
|
692
|
+
_y_left_ = _y_left
|
|
693
|
+
_g_left_ = _g_left
|
|
694
|
+
_right_ = _right
|
|
695
|
+
_y_right_ = _y_right
|
|
696
|
+
_g_right_ = _g_right
|
|
697
|
+
|
|
698
|
+
# Section: main update
|
|
699
|
+
armijo_mask_, curve_mask_, y1, direct_grad1 = WolfCond(
|
|
700
|
+
func,
|
|
701
|
+
grad_func,
|
|
702
|
+
X0_,
|
|
703
|
+
y0_,
|
|
704
|
+
direct_grad0_,
|
|
705
|
+
p_,
|
|
706
|
+
_steplength_, # (B, 1, 1)
|
|
707
|
+
is_grad_func_contain_y,
|
|
708
|
+
batch_scatter_,
|
|
709
|
+
rho=self.WOLFE_RHO,
|
|
710
|
+
beta=self.WOLFE_BETA,
|
|
711
|
+
func_args=func_args_,
|
|
712
|
+
func_kwargs=func_kwargs_,
|
|
713
|
+
grad_func_args=grad_func_args_,
|
|
714
|
+
grad_func_kwargs=grad_func_kwargs_
|
|
715
|
+
)
|
|
716
|
+
# check thres
|
|
717
|
+
converge_check_ = th.atleast_1d((armijo_mask_ & curve_mask_).squeeze())
|
|
718
|
+
if th.all(converge_check_):
|
|
719
|
+
is_converge = True
|
|
720
|
+
break
|
|
721
|
+
|
|
722
|
+
y1_long = y1.reshape(_long_shape)
|
|
723
|
+
# determine new interval
|
|
724
|
+
_is_grad_positiv = (direct_grad1 >= 0.)
|
|
725
|
+
_right_ = th.where(
|
|
726
|
+
armijo_mask_,
|
|
727
|
+
th.where(
|
|
728
|
+
_is_grad_positiv,
|
|
729
|
+
_steplength_,
|
|
730
|
+
_right_,
|
|
731
|
+
),
|
|
732
|
+
_steplength_
|
|
733
|
+
)
|
|
734
|
+
_y_right_ = th.where(
|
|
735
|
+
armijo_mask_,
|
|
736
|
+
th.where(
|
|
737
|
+
_is_grad_positiv,
|
|
738
|
+
y1_long,
|
|
739
|
+
_y_right_,
|
|
740
|
+
),
|
|
741
|
+
y1_long
|
|
742
|
+
)
|
|
743
|
+
_g_right_ = th.where(
|
|
744
|
+
armijo_mask_,
|
|
745
|
+
th.where(
|
|
746
|
+
_is_grad_positiv,
|
|
747
|
+
direct_grad1,
|
|
748
|
+
_g_right_,
|
|
749
|
+
),
|
|
750
|
+
direct_grad1
|
|
751
|
+
)
|
|
752
|
+
_left_ = th.where(
|
|
753
|
+
armijo_mask_,
|
|
754
|
+
th.where(
|
|
755
|
+
_is_grad_positiv,
|
|
756
|
+
_left_,
|
|
757
|
+
_steplength_
|
|
758
|
+
),
|
|
759
|
+
_left_
|
|
760
|
+
)
|
|
761
|
+
_y_left_ = th.where(
|
|
762
|
+
armijo_mask_,
|
|
763
|
+
th.where(
|
|
764
|
+
_is_grad_positiv,
|
|
765
|
+
_y_left_,
|
|
766
|
+
y1_long
|
|
767
|
+
),
|
|
768
|
+
_y_left_
|
|
769
|
+
)
|
|
770
|
+
_g_left_ = th.where(
|
|
771
|
+
armijo_mask_,
|
|
772
|
+
th.where(
|
|
773
|
+
_is_grad_positiv,
|
|
774
|
+
_g_left_,
|
|
775
|
+
direct_grad1
|
|
776
|
+
),
|
|
777
|
+
_g_left_
|
|
778
|
+
)
|
|
779
|
+
# interpolation step 3nd interp
|
|
780
|
+
#_h = _right - _left
|
|
781
|
+
#_a = 6. * (_y_right - _y_left) - 3. * _h * (_g_right + _g_left)
|
|
782
|
+
#_b = - 6. * (_y_right - _y_left) + 2. * _h * (_g_right + 2 * _g_left)
|
|
783
|
+
#_c = _h * _g_left
|
|
784
|
+
#D = _b**2 - 4. * _a * _c
|
|
785
|
+
#_neg_D_mask = (D < 0.)
|
|
786
|
+
#q1 = - _b - th.sqrt(D.clamp_min_(self._epsilon_cache))
|
|
787
|
+
#q2 = - _b + th.sqrt(D.clamp_min_(self._epsilon_cache))
|
|
788
|
+
#interp3_1 = _left + _h * q1 / (2 * _a.clamp_min_(self._epsilon_cache))
|
|
789
|
+
#interp3_2 = _left + _h * q2 / (2 * _a.clamp_min_(self._epsilon_cache))
|
|
790
|
+
_neg_D_mask, interp3_1 ,interp3_2 = self._3ord_hermite_interp(
|
|
791
|
+
_right_,
|
|
792
|
+
_y_right_,
|
|
793
|
+
_g_right_,
|
|
794
|
+
_left_,
|
|
795
|
+
_y_left_,
|
|
796
|
+
_g_left_,
|
|
797
|
+
)
|
|
798
|
+
# interpolation step 2nd interp
|
|
799
|
+
interp2 = self._2ord_interp(
|
|
800
|
+
_right_,
|
|
801
|
+
_y_right_,
|
|
802
|
+
_left_,
|
|
803
|
+
_y_left_,
|
|
804
|
+
_g_left_
|
|
805
|
+
)
|
|
806
|
+
# golden cut
|
|
807
|
+
golcut = th.add(_left_, (_right_ - _left_), alpha = 0.6180339887)
|
|
808
|
+
# choice steplength. A more strict interval that [left * 1.1, right * 0.9], leaving 10% redundancy.
|
|
809
|
+
_low_order_choice = th.where(
|
|
810
|
+
(interp2 > _left_ * 1.1) & (interp2 < _right_ * 0.9),
|
|
811
|
+
interp2,
|
|
812
|
+
golcut
|
|
813
|
+
)
|
|
814
|
+
_steplength_ = th.where(
|
|
815
|
+
_neg_D_mask,
|
|
816
|
+
_low_order_choice,
|
|
817
|
+
th.where(
|
|
818
|
+
(interp3_2 > _left_ * 1.1) & (interp3_2 < _right_ * 0.9), # firstly use longer steplength to avoid too short interval
|
|
819
|
+
interp3_2,
|
|
820
|
+
th.where(
|
|
821
|
+
(interp3_1 > _left_ * 1.1) & (interp3_1 < _right_ * 0.9),
|
|
822
|
+
interp3_1,
|
|
823
|
+
_low_order_choice
|
|
824
|
+
)
|
|
825
|
+
)
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
# Section: re-write
|
|
829
|
+
if not self._hold_samples:
|
|
830
|
+
if batch_scatter_indices is not None:
|
|
831
|
+
select_indices = th.where(select_mask)[0]
|
|
832
|
+
_steplength.index_copy_(1, select_indices, _steplength_)
|
|
833
|
+
_left.index_copy_(1, select_indices, _left_)
|
|
834
|
+
_y_left.index_copy_(1, select_indices, _y_left_)
|
|
835
|
+
_g_left.index_copy_(1, select_indices, _g_left_)
|
|
836
|
+
_right.index_copy_(1, select_indices, _right_)
|
|
837
|
+
_y_right.index_copy_(1, select_indices, _y_right_)
|
|
838
|
+
_g_right.index_copy_(1, select_indices, _g_right_)
|
|
839
|
+
converge_check.index_copy_(0, select_indices, converge_check_)
|
|
840
|
+
else:
|
|
841
|
+
select_indices = th.where(select_mask)[0]
|
|
842
|
+
_steplength.index_copy_(0, select_indices, _steplength_)
|
|
843
|
+
_left.index_copy_(0, select_indices, _left_)
|
|
844
|
+
_y_left.index_copy_(0, select_indices, _y_left_)
|
|
845
|
+
_g_left.index_copy_(0, select_indices, _g_left_)
|
|
846
|
+
_right.index_copy_(0, select_indices, _right_)
|
|
847
|
+
_y_right.index_copy_(0, select_indices, _y_right_)
|
|
848
|
+
_g_right.index_copy_(0, select_indices, _g_right_)
|
|
849
|
+
converge_check.index_copy_(0, select_indices, converge_check_)
|
|
850
|
+
else:
|
|
851
|
+
_steplength = _steplength_
|
|
852
|
+
_left = _left_
|
|
853
|
+
_y_left = _y_left_
|
|
854
|
+
_g_left = _g_left_
|
|
855
|
+
_right = _right_
|
|
856
|
+
_y_right = _y_right_
|
|
857
|
+
_g_right = _g_right_
|
|
858
|
+
converge_check = converge_check_
|
|
859
|
+
# reinsurance
|
|
860
|
+
if th.max(th.abs(_steplength - _steplength_old)) < 1.e-3:
|
|
861
|
+
self.logger.warning(
|
|
862
|
+
f'WARNING: linear search did not converge but steplength has fixed '
|
|
863
|
+
f'after {i + 1} steps. Try to use Armijo steplength...'
|
|
864
|
+
)
|
|
865
|
+
backup_steplength = th.where(backup_steplength < 1.2e-4, _steplength, backup_steplength) # to avoid too short steplen
|
|
866
|
+
_steplength = th.where(converge_check.reshape(_long_shape), _steplength, backup_steplength)
|
|
867
|
+
is_converge = True
|
|
868
|
+
break
|
|
869
|
+
_steplength_old.copy_(_steplength)
|
|
870
|
+
|
|
871
|
+
#self._stp_list.append(X0 + _steplength * p) <<<<<<<<<<<<<<<<<<<<<<< test
|
|
872
|
+
if self.verbose > 1:
|
|
873
|
+
self.logger.info(
|
|
874
|
+
f"Steplength Line Search {i + 1}: "
|
|
875
|
+
f"{' '.join([f'{_: 6.4f}' for _ in th.atleast_1d(_steplength.squeeze()).tolist()])}"
|
|
876
|
+
)
|
|
877
|
+
self.logger.debug(f">>>>>>>>>>>>>>> Interval {i+1}:\n{_left} ;\n{_right}\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
|
|
878
|
+
self.logger.debug(f">>>>>>>>>>>>>>> Steplength {i+1}:\n{_steplength}\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
|
|
879
|
+
self.logger.debug(f">>>>>>>>>>>>>>> Armijo mask {i+1}:\n{armijo_mask}\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
|
|
880
|
+
self.logger.debug(f">>>>>>>>>>>>>>> Curve mask {i+1}:\n{curve_mask}\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
|
|
881
|
+
|
|
882
|
+
if not is_converge:
|
|
883
|
+
self.logger.warning(f'WARNING: linear search did not converge in {self.maxiter} steps.')
|
|
884
|
+
_steplength = th.where(converge_check.reshape(_long_shape), _steplength, backup_steplength)
|
|
885
|
+
_steplength.clamp_min_(self._min_steplength_cache)
|
|
886
|
+
return _steplength # (n_batch, 1, 1)
|
|
887
|
+
|
|
888
|
+
def _fix_step(
|
|
889
|
+
self,
|
|
890
|
+
func: Any | nn.Module,
|
|
891
|
+
grad_func: Any | nn.Module,
|
|
892
|
+
X0: th.Tensor,
|
|
893
|
+
y0: th.Tensor,
|
|
894
|
+
grad: th.Tensor,
|
|
895
|
+
p: th.Tensor,
|
|
896
|
+
steplength: th.Tensor | float,
|
|
897
|
+
is_grad_func_contain_y: bool,
|
|
898
|
+
func_args: Sequence = tuple(),
|
|
899
|
+
func_kwargs=None,
|
|
900
|
+
grad_func_args: Sequence = tuple(),
|
|
901
|
+
grad_func_kwargs=None,
|
|
902
|
+
batch_scatter_indices: th.Tensor = None
|
|
903
|
+
):
|
|
904
|
+
"""
|
|
905
|
+
simple fixed step length.
|
|
906
|
+
"""
|
|
907
|
+
return steplength
|
|
908
|
+
|
|
909
|
+
def run(
|
|
910
|
+
self,
|
|
911
|
+
func: Any | nn.Module,
|
|
912
|
+
grad_func: Any | nn.Module,
|
|
913
|
+
X0: th.Tensor,
|
|
914
|
+
y0: th.Tensor,
|
|
915
|
+
grad: th.Tensor,
|
|
916
|
+
p: th.Tensor,
|
|
917
|
+
steplength: th.Tensor|float,
|
|
918
|
+
is_grad_func_contain_y: bool,
|
|
919
|
+
require_grad: bool = False,
|
|
920
|
+
func_args: Sequence = tuple(),
|
|
921
|
+
func_kwargs=None,
|
|
922
|
+
grad_func_args: Sequence = tuple(),
|
|
923
|
+
grad_func_kwargs=None,
|
|
924
|
+
batch_indices: th.Tensor=None
|
|
925
|
+
) -> th.Tensor:
|
|
926
|
+
"""
|
|
927
|
+
|
|
928
|
+
Args:
|
|
929
|
+
func:
|
|
930
|
+
grad_func:
|
|
931
|
+
X0: (1, sumN, n_dim)
|
|
932
|
+
y0: (n_batch, )
|
|
933
|
+
grad: (1, sumN, n_dim)
|
|
934
|
+
p: (1, sumN, n_dim)
|
|
935
|
+
steplength: (1, sumN, 1)
|
|
936
|
+
is_grad_func_contain_y:
|
|
937
|
+
func_args:
|
|
938
|
+
func_kwargs:
|
|
939
|
+
grad_func_args:
|
|
940
|
+
grad_func_kwargs:
|
|
941
|
+
batch_indices:
|
|
942
|
+
|
|
943
|
+
Returns:
|
|
944
|
+
|
|
945
|
+
"""
|
|
946
|
+
if func_kwargs is None:
|
|
947
|
+
func_kwargs = dict()
|
|
948
|
+
if grad_func_kwargs is None:
|
|
949
|
+
grad_func_kwargs = dict()
|
|
950
|
+
steplength = steplength.reshape(-1, 1, 1) if batch_indices is None else steplength.reshape(1, -1, 1)
|
|
951
|
+
self.steplength = steplength
|
|
952
|
+
self.require_grad = require_grad
|
|
953
|
+
with th.no_grad():
|
|
954
|
+
_result = self._updater(
|
|
955
|
+
func,
|
|
956
|
+
grad_func,
|
|
957
|
+
X0,
|
|
958
|
+
y0,
|
|
959
|
+
grad,
|
|
960
|
+
p,
|
|
961
|
+
steplength,
|
|
962
|
+
is_grad_func_contain_y,
|
|
963
|
+
func_args,
|
|
964
|
+
func_kwargs,
|
|
965
|
+
grad_func_args,
|
|
966
|
+
grad_func_kwargs,
|
|
967
|
+
batch_indices
|
|
968
|
+
)
|
|
969
|
+
if self.verbose > 0:
|
|
970
|
+
self.logger.info(f"Steplength: {' '.join([f'{_: 6.4f}' for _ in th.atleast_1d(_result.squeeze()).tolist()])}")
|
|
971
|
+
|
|
972
|
+
return _result
|