BUCToolkit 1.0b0__py3-none-any.whl

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