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,256 @@
1
+ """
2
+ Conjugate Gradient Algorithm for optimization.
3
+ """
4
+ # Copyright (c) 2024-2025.7.4, BUCToolkit.
5
+ # Authors: Pu Pengxin, Song Xin
6
+ # Version: 0.9a
7
+ # File: CG.py
8
+ # Environment: Python 3.12
9
+
10
+ from typing import Literal
11
+ import os
12
+
13
+ import torch as th
14
+
15
+ from BUCToolkit.BatchOptim._BaseOpt import _BaseOpt
16
+ from BUCToolkit.utils.index_ops import index_inner_product
17
+
18
+ os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True'
19
+
20
+
21
+ class CG(_BaseOpt):
22
+ """
23
+ Conjugate Gradient Algo.
24
+ Args:
25
+ iter_scheme: Literal['PR+', 'FR', 'SD'],
26
+ E_threshold: float = 1e-3,
27
+ F_threshold: float = 0.05,
28
+ maxiter: int = 100,
29
+ linesearch: Literal['Backtrack', 'Wolfe', 'NWolfe', '2PT', '3PT', 'Golden', 'Newton', 'None'] = 'Backtrack',
30
+ linesearch_maxiter: int = 10,
31
+ linesearch_thres: float = 0.02,
32
+ linesearch_factor: float = 0.6,
33
+ steplength: float = 0.5,
34
+ use_bb: whether to use Barzilai-Borwein steplength (BB1 or long BB) as initial steplength instead of fixed one.
35
+ device: str | th.device = 'cpu',
36
+ verbose: int = 2
37
+
38
+ Methods:
39
+ run: launch the optimization.
40
+ set_update_batch: setting the method to update the taget function when variables change.
41
+ """
42
+ def __init__(
43
+ self,
44
+ iter_scheme: Literal['PR+', 'FR', 'SD'],
45
+ E_threshold: float = 1e-3,
46
+ F_threshold: float = 0.05,
47
+ maxiter: int = 100,
48
+ linesearch: Literal['Backtrack', 'B', 'Wolfe', 'W', 'MT', 'EXACT', 'None', 'N'] = 'Backtrack',
49
+ linesearch_maxiter: int = 10,
50
+ linesearch_thres: float = 0.02,
51
+ linesearch_factor: float = 0.6,
52
+ steplength: float = 0.5,
53
+ use_bb: bool = True,
54
+ output_file: str | None = None,
55
+ device: str | th.device = 'cpu',
56
+ verbose: int = 2
57
+ ):
58
+ super().__init__(
59
+ iter_scheme,
60
+ E_threshold,
61
+ F_threshold,
62
+ maxiter,
63
+ linesearch,
64
+ linesearch_maxiter,
65
+ linesearch_thres,
66
+ linesearch_factor,
67
+ steplength,
68
+ use_bb,
69
+ output_file,
70
+ device,
71
+ verbose
72
+ )
73
+ self.__update_scheme = None
74
+ self.MIN_CACHE = th.scalar_tensor(1.e-20, device=self.device)
75
+
76
+ @staticmethod
77
+ def __check_restart(gg, ggo, g, p, beta):
78
+ """
79
+ check if restarting is needed.
80
+
81
+ """
82
+ ortho_check = th.abs(ggo) > 0.1 * gg
83
+ return ortho_check
84
+
85
+ def __PRP_irreg(
86
+ self,
87
+ g: th.Tensor,
88
+ g_old: th.Tensor,
89
+ p: th.Tensor,
90
+ X: th.Tensor,
91
+ batch_scatter_indices: th.Tensor | None,
92
+ ) -> th.Tensor:
93
+ """
94
+ POLAK-RIBIERE CG ALGORITHM for irregular batch.
95
+ """
96
+ gogo = th.sum(index_inner_product(g_old, g_old, 1, batch_scatter_indices), dim=-1, keepdim=True).clamp_min_(self.MIN_CACHE) # (1, B, 1)
97
+ ggo = th.sum(index_inner_product(g, g_old, 1, batch_scatter_indices), dim=-1, keepdim=True)
98
+ gg = th.sum(index_inner_product(g, g, 1, batch_scatter_indices), dim=-1, keepdim=True)
99
+ beta = (gg - ggo) / gogo # (1, B, 1)
100
+ beta.clamp_min_(self.MIN_CACHE)
101
+ # Restart
102
+ is_restart = self.__check_restart(gg, ggo, g, p, beta)
103
+ beta.masked_fill_(is_restart, 0.)
104
+ beta = beta.index_select(1, batch_scatter_indices)
105
+ if self.verbose > 1:
106
+ self.logger.info(f" Restart: {is_restart.flatten().cpu().numpy()}")
107
+ # update directions
108
+ #p = -g + beta * p # (n_batch, n_atom, n_dim)
109
+ th.addcmul(-g, beta, p, out=p)
110
+
111
+ return p
112
+
113
+ def __PRP_reg(
114
+ self ,
115
+ g: th.Tensor,
116
+ g_old: th.Tensor,
117
+ p: th.Tensor,
118
+ X: th.Tensor,
119
+ batch_scatter_
120
+ ) -> th.Tensor:
121
+ """
122
+ POLAK-RIBIERE CG ALGORITHM for regular batch.
123
+ """
124
+ gogo = th.sum(g_old * g_old, dim=(-2, -1), keepdim=True).clamp_min_(self.MIN_CACHE) # to avoid divide 0
125
+ ggo = th.sum(g * g_old, dim=(-2, -1), keepdim=True) # (n_batch, 1, 1)
126
+ gg = th.sum(g * g, dim=(-2, -1), keepdim=True) # (n_batch, 1, 1)
127
+ beta = (gg - ggo) / gogo # (1, B, 1)
128
+ beta.clamp_min_(self.MIN_CACHE)
129
+ # Restart
130
+ is_restart = self.__check_restart(gg, ggo, g, p, beta)
131
+ beta.masked_fill_(is_restart, 0.)
132
+ if self.verbose > 1:
133
+ self.logger.info(f" Restart: {is_restart.flatten().cpu().numpy()}")
134
+ # update directions
135
+ #p = -g + beta * p # (n_batch, n_atom, n_dim)
136
+ th.addcmul(-g, beta, p, out=p)
137
+
138
+ return p
139
+
140
+ def __FR_irreg(
141
+ self,
142
+ g: th.Tensor,
143
+ g_old: th.Tensor,
144
+ p: th.Tensor,
145
+ X: th.Tensor,
146
+ batch_scatter_indices
147
+ ) -> th.Tensor:
148
+ """ FLETCHER-REEVES ALGO. for irregular batch. """
149
+ gogo = th.sum(index_inner_product(g_old, g_old, 1, batch_scatter_indices), dim=-1, keepdim=True).clamp_min_(self.MIN_CACHE) # (1, B, 1)
150
+ ggo = th.sum(index_inner_product(g, g_old, 1, batch_scatter_indices), dim=-1, keepdim=True)
151
+ gg = th.sum(index_inner_product(g, g, 1, batch_scatter_indices), dim=-1, keepdim=True)
152
+ beta = gg / gogo
153
+ # Restart
154
+ is_restart = self.__check_restart(gg, ggo, g, p, beta)
155
+ beta.masked_fill_(is_restart, 0.) # (1, B, 1)
156
+ beta = beta.index_select(1, batch_scatter_indices) # (1, sumN, 1)
157
+ if self.verbose > 1:
158
+ self.logger.info(f" Restart: {is_restart.flatten().cpu().numpy()}")
159
+ # update directions
160
+ th.addcmul(-g, beta, p, out=p)
161
+
162
+ return p
163
+
164
+ def __FR_reg(
165
+ self,
166
+ g: th.Tensor,
167
+ g_old: th.Tensor,
168
+ p: th.Tensor,
169
+ X: th.Tensor,
170
+ batch_scatter_
171
+ ) -> th.Tensor:
172
+ """ FLETCHER-REEVES ALGO. for regular batch. """
173
+ gogo = th.sum(g_old * g_old, dim=(-2, -1), keepdim=True).clamp_min_(self.MIN_CACHE) # to avoid divide 0
174
+ ggo = th.sum(g * g_old, dim=(-2, -1), keepdim=True) # (n_batch, 1, 1)
175
+ gg = th.sum(g * g, dim=(-2, -1), keepdim=True) # (n_batch, 1, 1)
176
+ beta = gg / gogo
177
+ # Restart
178
+ is_restart = self.__check_restart(gg, ggo, g, p, beta)
179
+ beta.masked_fill_(is_restart, 0.)
180
+ if self.verbose > 1:
181
+ self.logger.info(f" Restart: {is_restart.flatten().cpu().numpy()}")
182
+ # update directions
183
+ th.addcmul(-g, beta, p, out=p) # (n_batch, n_atom, n_dim)
184
+
185
+ return p
186
+
187
+ @staticmethod
188
+ def __SD(g: th.Tensor, g_old: th.Tensor, p: th.Tensor, X: th.Tensor, batch_scatter_) -> th.Tensor:
189
+ """ Steepest descent """
190
+ return -g
191
+
192
+
193
+ def initialize_algo_param(self):
194
+ """
195
+ Override this method to initialize attribute variables for self._update_direction.
196
+
197
+ Returns: None
198
+ """
199
+ if self.iterform == "SD":
200
+ self.__update_scheme = self.__SD
201
+ return
202
+ if self.is_concat_X:
203
+ if self.iterform == "PR+":
204
+ self.__update_scheme = self.__PRP_irreg
205
+ elif self.iterform == "FR":
206
+ self.__update_scheme = self.__FR_irreg
207
+ else:
208
+ raise NotImplementedError("Unknown Iterative Scheme.")
209
+ else:
210
+ if self.iterform == "PR+":
211
+ self.__update_scheme = self.__PRP_reg
212
+ elif self.iterform == "FR":
213
+ self.__update_scheme = self.__FR_reg
214
+ else:
215
+ raise NotImplementedError("Unknown Iterative Scheme.")
216
+
217
+ def _update_direction(
218
+ self,
219
+ g: th.Tensor,
220
+ g_old: th.Tensor,
221
+ p: th.Tensor,
222
+ X: th.Tensor,
223
+ batch_scatter_: th.Tensor | None,
224
+ ) -> th.Tensor:
225
+ """
226
+ Override this method to implement X update algorithm.
227
+ Args:
228
+ g: (n_batch, n_atom, n_dim), the gradient of X at this step
229
+ g_old: (n_batch, n_atom, n_dim), the gradient of X at last step
230
+ p: (n_batch, n_atom, n_dim), the update direction of X at last step
231
+ X: (n_batch, n_atom, n_dim), the independent vars X.
232
+
233
+ Returns:
234
+ p: th.Tensor, the new update direction of X.
235
+ """
236
+ p = self.__update_scheme(g, g_old, p, X, batch_scatter_)
237
+
238
+ return p
239
+
240
+ def _update_algo_param(
241
+ self,
242
+ select_mask: th.Tensor,
243
+ select_mask_short,
244
+ batch_scatter_indices,
245
+ g: th.Tensor,
246
+ g_old: th.Tensor,
247
+ p: th.Tensor,
248
+ displace: th.Tensor
249
+ ) -> None:
250
+ """
251
+ Override this method to update the parameters of X update algorithm i.e., self.iterform.
252
+
253
+ Returns: None
254
+ """
255
+ pass
256
+
@@ -0,0 +1,310 @@
1
+ """
2
+ FIRE Optimization Algorithm. Phys. Rev. Lett., 2006, 97:170201.
3
+ """
4
+ import copy
5
+ # Copyright (c) 2024-2025.7.4, BUCToolkit.
6
+ # Authors: Pu Pengxin, Song Xin
7
+ # Version: 0.9a
8
+ # File: FIRE.py
9
+ # Environment: Python 3.12
10
+
11
+ import os
12
+ from typing import Iterable, Dict, Any, List, Literal, Optional, Callable, Sequence, Tuple # noqa: F401
13
+
14
+ import torch as th
15
+ from torch import nn
16
+
17
+ from BUCToolkit.utils._Element_info import MASS, N_MASS
18
+ from .._BaseOpt import _BaseOpt
19
+ from BUCToolkit.utils.index_ops import index_inner_product
20
+
21
+ os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True'
22
+
23
+
24
+ class FIRE(_BaseOpt):
25
+ def __init__(
26
+ self,
27
+ E_threshold: float = 1e-3,
28
+ F_threshold: float = 0.05,
29
+ maxiter: int = 100,
30
+ steplength: float = 1.,
31
+ alpha: float = 0.1,
32
+ alpha_fac: float = 0.99,
33
+ fac_inc: float = 1.1,
34
+ fac_dec: float = 0.5,
35
+ N_min: int = 5,
36
+ output_file: str | None = None,
37
+ device: str | th.device = 'cpu',
38
+ verbose: int = 2,
39
+ _hold_samples: bool = False,
40
+ **kwargs
41
+ ):
42
+ r"""
43
+ FIRE Algorithm for optimization.
44
+
45
+ Args:
46
+ E_threshold: float, threshold of difference of func between 2 iterations.
47
+ F_threshold: float, threshold of gradient of func.
48
+ maxiter: int, the maximum iteration steps.
49
+ steplength: The initial step length, i.e. the BatchMD time step.
50
+ alpha: FIRE arg. alpha.
51
+ alpha_fac: FIRE arg. the factor of alpha change.
52
+ fac_inc: FIRE arg. the increment factor.
53
+ fac_dec: FIRE arg. the decrement factor.
54
+ N_min: FIRE arg. the minimum number that keeps steplength static.
55
+ device: The device that program runs on.
56
+ verbose: amount of print information.
57
+ _hold_samples: ONLY FOR SPECIAL USE (e.g., CI-NEB or DEBUG).
58
+ If True, FIRE optimizer will not remove any sample in a batch even if the sample has converged.
59
+
60
+ Method:
61
+ run: running the main optimization program.
62
+ """
63
+ super().__init__(
64
+ 'FIRE',
65
+ E_threshold,
66
+ F_threshold,
67
+ maxiter,
68
+ 'None',
69
+ 1,
70
+ 1.,
71
+ 1.,
72
+ 1.,
73
+ use_bb = False,
74
+ output_file=output_file,
75
+ device = device,
76
+ verbose = verbose,
77
+ _hold_samples=_hold_samples
78
+ )
79
+ self.MAX_STEPLENGTH = th.scalar_tensor(10 * steplength, device=self.device)
80
+ self.FIRE_t_init = steplength
81
+ if not (0. < alpha_fac < 1.):
82
+ raise ValueError('alpha_fac must between 0 and 1.')
83
+ if not (0. < fac_dec < 1.):
84
+ raise ValueError('fac_dec must between 0 and 1.')
85
+ if fac_inc <= 1.:
86
+ raise ValueError('fac_inc must be greater than 1.')
87
+
88
+ self.alpha = alpha
89
+ self.alpha_fac = alpha_fac
90
+ self.fac_inc = fac_inc
91
+ self.fac_dec = fac_dec
92
+ self.N_min = N_min
93
+
94
+ self.v: th.Tensor | None = None # shape as X
95
+ self.v_: th.Tensor | None = None #
96
+ self.masses: th.Tensor | None = None # shape as X
97
+ self.masses_: th.Tensor | None = None #
98
+ self.t: th.Tensor | None = None # (1, sumN, 1) or (B, 1, 1)
99
+ self.t_: th.Tensor | None = None
100
+ self.a: th.Tensor | None = None # (1, sumN, 1) or (B, 1, 1)
101
+ self.n_count: th.Tensor | None = None # (1, sumN, 1) or (B, 1, 1)
102
+
103
+ def initialize_algo_param(self):
104
+ """ """
105
+ if self.is_concat_X:
106
+ self.t = th.full((1, self.n_atom, 1), self.FIRE_t_init, device=self.device)
107
+ self.a = th.full((1, self.n_atom, 1), self.alpha, device=self.device)
108
+ self.n_count = th.zeros((1, self.n_atom, 1), dtype=th.int, device=self.device)
109
+ else:
110
+ self.t = th.full((self.n_true_batch, 1, 1), self.FIRE_t_init, device=self.device)
111
+ self.a = th.full((self.n_true_batch, 1, 1), self.alpha, device=self.device)
112
+ self.n_count = th.zeros((self.n_true_batch, 1, 1), dtype=th.int, device=self.device)
113
+
114
+ def _update_algo_param(
115
+ self,
116
+ select_mask,
117
+ select_mask_short,
118
+ batch_scatter_indices,
119
+ X_grad_,
120
+ X_grad_old_,
121
+ p,
122
+ displace_
123
+ ):
124
+ F = - X_grad_
125
+ if self.is_concat_X:
126
+ self.masses_ = self.masses[:, select_mask, :]
127
+ self.t_: th.Tensor = self.t[:, select_mask, :] # (1, sumN, 1)
128
+ a_ = self.a[:, select_mask, :] # (1, sumN, 1)
129
+ n_count_ = self.n_count[:, select_mask, :]
130
+ self.v_ = self.v[:, select_mask, :]
131
+ # (1, sumN, n_dim)
132
+ F_hat = F / th.sum(index_inner_product(
133
+ F,
134
+ F,
135
+ dim=1,
136
+ batch_indices=batch_scatter_indices
137
+ ), dim=-1, keepdim=True).sqrt_().index_select(1, batch_scatter_indices)
138
+ # (1, sumN, 1)
139
+ momenta = th.sum(index_inner_product(
140
+ F,
141
+ self.v_,
142
+ dim=1,
143
+ batch_indices=batch_scatter_indices
144
+ ), dim=-1, keepdim=True).index_select(1, batch_scatter_indices)
145
+ # (1, sumN, 1)
146
+ v_norm = th.sum(index_inner_product(
147
+ self.v_,
148
+ self.v_,
149
+ dim=1,
150
+ batch_indices=batch_scatter_indices
151
+ ), dim=-1, keepdim=True).sqrt_().index_select(1, batch_scatter_indices)
152
+ # update velocity: v = v * (1 - a) + a * |v| * \hat{F}
153
+ F_hat.mul_(v_norm)
154
+ self.v_.mul_((1. - a_))
155
+ self.v_.addcmul_(a_, F_hat)
156
+ # if P > 0
157
+ n_count_ += th.where(momenta > 0., 1, -n_count_) # (1, sumN, 1)
158
+ is_ncount_gt_Nmin = n_count_ >= self.N_min
159
+ #
160
+ new_t_ = (self.t_ * self.fac_inc).clamp_max_(self.MAX_STEPLENGTH)
161
+ self.t_ = th.where(is_ncount_gt_Nmin, new_t_, self.t_)
162
+ a_ = th.where(is_ncount_gt_Nmin, (a_ * self.alpha_fac), a_)
163
+ # if P <= 0.
164
+ is_p_lt_0 = momenta <= 0.
165
+ self.t_ = th.where(is_p_lt_0, (self.t_ * self.fac_dec), self.t_)
166
+ self.v_.masked_fill_(
167
+ is_p_lt_0,
168
+ 0.
169
+ )
170
+ a_.masked_fill_(is_p_lt_0, self.alpha)
171
+ # re-write
172
+ select_indices = th.where(select_mask)[0]
173
+ self.t.index_copy_(1, select_indices, self.t_)
174
+ self.a.index_copy_(1, select_indices, a_)
175
+ self.n_count.index_copy_(1, select_indices, n_count_)
176
+
177
+ else:
178
+ self.v_ = self.v[select_mask, ...]
179
+ self.masses_ = self.masses[select_mask, ...]
180
+ self.t_ = self.t[select_mask, ...]
181
+ a_ = self.a[select_mask, ...]
182
+ n_count_ = self.n_count[select_mask, ...]
183
+
184
+ F_hat = F / (th.linalg.norm(F, dim=(-2, -1), keepdim=True) + 1e-20)
185
+ # (n_batch, n_dim, n_atom) @ (n_batch, n_atom, n_dim) -> (n_batch, 1, 1)
186
+ momenta = th.sum(F * self.v_, dim=(-1, -2), keepdim=True)
187
+ # update velocity
188
+ v_norm = th.linalg.norm(self.v_, dim=(-2, -1), keepdim=True)
189
+ F_hat.mul_(v_norm)
190
+ self.v_.mul_((1. - a_))
191
+ self.v_.addcmul_(a_, F_hat)
192
+ # if P > 0.
193
+ n_count_ += th.where(momenta > 0., 1, -n_count_)
194
+ is_ncount_gt_Nmin = n_count_ >= self.N_min
195
+ new_t_ = (self.t_ * self.fac_inc).clamp_max_(self.MAX_STEPLENGTH)
196
+ self.t_ = th.where(is_ncount_gt_Nmin, new_t_, self.t_)
197
+ a_ = th.where(is_ncount_gt_Nmin, (a_ * self.alpha_fac), a_)
198
+ # if P <= 0.
199
+ is_p_lt_0 = momenta <= 0.
200
+ self.t_ = th.where(is_p_lt_0, (self.t_ * self.fac_dec), self.t_)
201
+ self.v_.masked_fill_(
202
+ is_p_lt_0,
203
+ 0.
204
+ )
205
+ a_.masked_fill_(is_p_lt_0, self.alpha)
206
+ # re-write
207
+ select_indices = th.where(select_mask)[0]
208
+ self.t.index_copy_(0, select_indices, self.t_)
209
+ self.a.index_copy_(0, select_indices, a_)
210
+ self.n_count.index_copy_(0, select_indices, n_count_)
211
+ pass
212
+
213
+ def _update_direction(
214
+ self,
215
+ g: th.Tensor,
216
+ g_old: th.Tensor,
217
+ p: th.Tensor,
218
+ X: th.Tensor,
219
+ batch_scatter_indices: th.Tensor | None,
220
+ ) -> th.Tensor:
221
+ self.v_: th.Tensor
222
+ self.v_.addcdiv_(g * self.t_, self.masses_, value=-9.64853329045427e-3)
223
+ disp = self.v_ * self.t_
224
+ return disp
225
+
226
+ def _update_algo_batches(
227
+ self,
228
+ select_indices: th.Tensor,
229
+ select_indices_short: th.Tensor | None,
230
+ ):
231
+ if self.is_concat_X:
232
+ self.v.index_copy_(1, select_indices, self.v_)
233
+ else:
234
+ self.v.index_copy_(0, select_indices, self.v_)
235
+
236
+ def run(
237
+ self,
238
+ func: Any | nn.Module,
239
+ X: th.Tensor,
240
+ grad_func: Any | nn.Module = None,
241
+ func_args: Sequence = tuple(),
242
+ func_kwargs: Dict | None = None,
243
+ grad_func_args: Sequence = tuple(),
244
+ grad_func_kwargs: Dict | None = None,
245
+ is_grad_func_contain_y: bool = True,
246
+ require_grad: bool = False,
247
+ output_grad: bool = False,
248
+ fixed_atom_tensor: Optional[th.Tensor] = None,
249
+ batch_indices: None | List[int] | Tuple[int, ...] | th.Tensor = None,
250
+ elements: List[List[str | int]] | None = None,
251
+ ) -> Tuple[th.Tensor, th.Tensor] | Tuple[th.Tensor, th.Tensor, th.Tensor]:
252
+ r"""
253
+ Run the Conjugate gradient
254
+
255
+ Args:
256
+ func: the main function of instantiated torch.nn.Module class.
257
+ X: Tensor[n_batch, n_atom, 3], the optimization variables (e.g., atom coordinates) that input to func.
258
+ grad_func: user-defined function that grad_func(X, ...) returns the func's gradient at X. if None, grad_func(X, ...) = th.autograd.grad(func(X, ...), X).
259
+ func_args: optional, other input of func.
260
+ func_kwargs: optional, other input of func.
261
+ grad_func_args: optional, other input of grad_func.
262
+ grad_func_kwargs: optional, other input of grad_func.
263
+ is_grad_func_contain_y: bool, if True, grad_func contains output of func followed by X i.e., grad = grad_func(X, y, ...), else grad = grad_func(X, ...)
264
+ require_grad: bool, if True, autograd will be turned on for func(X, *func_args, **func_kwargs) calculation.
265
+ output_grad: bool, whether output gradient of last step.
266
+ fixed_atom_tensor: Optional[th.Tensor], the indices of X that fixed. An integer tensor with the same shape as X where 1 is for free and 0 is for fixation.
267
+ batch_indices: Sequence | th.Tensor | np.ndarray | None, the split points for given X, Element_list & V_init, must be 1D integer array_like.
268
+ the format of batch_indices is the same as `split_size_or_sections` in torch.split:
269
+ batch_indices = (n1, n2, ..., nN) will split X, Element_list & V_init into N parts, and ith parts has ni atoms. sum(n1, ..., nN) = X.shape[1]
270
+ elements: Optional[List[List[str | int]]], the Element of each given atom in X.
271
+
272
+ Returns:
273
+ min func: Tensor(n_batch, ), the minimum of func.
274
+ argmin func: Tensor(X.shape), the X corresponds to min func.
275
+ grad of argmin func: Tensor(X.shape), only output when `output_grad` == True. The gradient of X corresponding to minimum.
276
+ """
277
+ # manage Atomic Type & Masses
278
+ if len(X.shape) == 2:
279
+ X = X.unsqueeze(0)
280
+ elif len(X.shape) != 3:
281
+ raise ValueError(f'`X` must be 2D or 3D, but got shape [{X.shape}]')
282
+ if elements is None:
283
+ self.masses = th.ones_like(X)
284
+ elif isinstance(elements, Sequence):
285
+ masses = list()
286
+ for _Elem in elements:
287
+ masses.append([MASS[__elem] if isinstance(__elem, str) else N_MASS[__elem] for __elem in _Elem])
288
+ masses = th.tensor(masses, dtype=th.float32, device=self.device)
289
+ self.masses = masses.unsqueeze(-1).expand_as(X).clone() # (n_batch, n_atom, n_dim)
290
+ else:
291
+ raise TypeError(f'Expected masses is a Sequence[Sequence[...]], but occurred {type(elements)}.')
292
+ self.v = th.zeros_like(X, device=self.device)
293
+
294
+ _results = super().run(
295
+ func,
296
+ X,
297
+ grad_func,
298
+ func_args,
299
+ func_kwargs,
300
+ grad_func_args,
301
+ grad_func_kwargs,
302
+ is_grad_func_contain_y=is_grad_func_contain_y,
303
+ require_grad=require_grad,
304
+ output_grad=output_grad,
305
+ fixed_atom_tensor=fixed_atom_tensor,
306
+ batch_indices=batch_indices,
307
+ )
308
+
309
+ return _results
310
+