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,484 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025.7.4, BUCToolkit.
|
|
2
|
+
# Authors: Pu Pengxin, Song Xin
|
|
3
|
+
# Version: 0.9a
|
|
4
|
+
# File: StructureOptimization.py
|
|
5
|
+
# Environment: Python 3.12
|
|
6
|
+
import logging
|
|
7
|
+
import math
|
|
8
|
+
import os
|
|
9
|
+
import time
|
|
10
|
+
import traceback
|
|
11
|
+
from typing import Any, Literal, Dict, List
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import torch as th
|
|
15
|
+
from torch import nn
|
|
16
|
+
|
|
17
|
+
from BUCToolkit import BatchOptim
|
|
18
|
+
from BUCToolkit.BatchOptim.minimize import CG, QN, FIRE
|
|
19
|
+
from BUCToolkit.BatchOptim.TS.Dimer import Dimer
|
|
20
|
+
from BUCToolkit.api._io import _CONFIGS, _LoggingEnd, _Model_Wrapper_pyg, _Model_Wrapper_dgl, PygBatchUpdater
|
|
21
|
+
from BUCToolkit.utils._print_formatter import FLOAT_ARRAY_FORMAT
|
|
22
|
+
from BUCToolkit.utils._Element_info import ATOMIC_NUMBER
|
|
23
|
+
from BUCToolkit.utils._CheckModules import check_module
|
|
24
|
+
from BUCToolkit.utils.ElemListReduce import elem_list_reduce
|
|
25
|
+
from BUCToolkit.utils.setup_loggers import has_any_handler
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StructureOptimization(_CONFIGS):
|
|
29
|
+
"""
|
|
30
|
+
The class of structure optimization for relaxation and transition state (only for single-point TS search, e.g. DIMER).
|
|
31
|
+
Users need to set the dataset and dataloader manually.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
config_file: the path of input file.
|
|
35
|
+
data_type: graph data type. 'pyg' for torch-geometric BatchData, 'dgl' for dgl DGLGraph.
|
|
36
|
+
|
|
37
|
+
Input file parameters:
|
|
38
|
+
# For relaxation tasks:
|
|
39
|
+
RELAXATION:
|
|
40
|
+
ALGO: Literal[CG, BFGS, FIRE], the optimization algo.
|
|
41
|
+
REQUIRE_GRAD: bool, if True, autograd will be turned on for func(X, *func_args, **func_kwargs). Default: False.
|
|
42
|
+
ITER_SCHEME: Literal['PR+', 'FR', 'PR', 'WYL'], only use for ALGO=CG, the iteration scheme of CG. Default: PR+.
|
|
43
|
+
E_THRES: float, threshold of Energy difference (eV). Default: 1e-4.
|
|
44
|
+
F_THRES: float, threshold of max Force (eV/Ang). Default: 5e-2.
|
|
45
|
+
MAXITER: int, the maximum iteration numbers. Default: 300.
|
|
46
|
+
STEPLENGTH: float, the initial step length for line search. Default: 0.5.
|
|
47
|
+
USE_BB: bool, if True, use Barzilai-Borwein steplength (i.e., BB1 or long BB) as initial steplength instead of fixed `STEPLENGTH`.
|
|
48
|
+
|
|
49
|
+
LINESEARCH: Literal[Backtrack, Golden, Wolfe], only used for ALGO=CG, BFGS or MIX.
|
|
50
|
+
'Backtrack' with Armijo's cond., 'Golden' for exact line search by golden sec. Algo., 'Wolfe' for advance & retreat algo. With weak Wolfe cond.
|
|
51
|
+
LINESEARCH_MAXITER: the maximum iteration numbers of line search, only used for CG, BFGS and MIX. Default: 10.
|
|
52
|
+
LINESEARCH_THRES: float, threshold of exact line search. Only used for LINESEARCH=Golden.
|
|
53
|
+
LINESEARCH_FACTOR: A factor in linesearch. Shrinkage factor for "Backtrack" & "Wolfe", scaling factor in interval search for "Golden". Default: 0.6.
|
|
54
|
+
|
|
55
|
+
# Following parameters are only for ALGO=FIRE
|
|
56
|
+
ALPHA:
|
|
57
|
+
ALPHA_FAC:
|
|
58
|
+
FAC_INC:
|
|
59
|
+
FAC_DEC: float = 0.5
|
|
60
|
+
N_MIN: int = 5
|
|
61
|
+
MASS: float = 20.0
|
|
62
|
+
|
|
63
|
+
# For transition state tasks:
|
|
64
|
+
TRANSITION_STATE:
|
|
65
|
+
ALGO: Literal[DIMER, DIMER_LS], the optimization algo.
|
|
66
|
+
X_DIFF_ATTR: str, the name of the attribute of dimer direction guess for initial coordinate X in data.
|
|
67
|
+
the dimer direction `X_diff` will be set by `X_diff = data.X_DIFF_ATTR`.
|
|
68
|
+
If `X_DIFF_ATTR` not a data's attribute, a random vector will be generated.
|
|
69
|
+
E_THRES: float, threshold of Energy difference (eV). Default: 1e-4.
|
|
70
|
+
TORQ_THRES: float, the threshold of max torque of Dimer. Default: 1e-2.
|
|
71
|
+
CURVATURE_THRES: float, the threshold of curvature for rotation convergence. Once the curvatures less than `CURVATURE_THRES`,
|
|
72
|
+
convergence will be viewed as reached. Default: - 0.1.
|
|
73
|
+
F_THRES: float, threshold of max Force (eV/Ang). Default: 5e-2.
|
|
74
|
+
MAXITER_TRANS: int, the maximum iteration numbers of transition steps. Default: 300.
|
|
75
|
+
MAXITER_ROT: int, the maximum iteration numbers of rotation steps. Default: 10.
|
|
76
|
+
MAX_STEPLENGTH: float, the maximum step length for dimer transitions. Default: 0.5.
|
|
77
|
+
DX: float, the step length of finite difference. Default: 1.e-2.
|
|
78
|
+
|
|
79
|
+
# Following parameters are only for ALGO=DIMER_LS
|
|
80
|
+
LINESEARCH_MAXITER: int, the maximum iteration numbers of line search, only used for CG, BFGS and MIX. Default: 10.
|
|
81
|
+
STEPLENGTH: float, the initial step length for line search of transition steps. Default: 0.5.
|
|
82
|
+
MOMENTA_COEFF: float, the coefficient of momentum in transition steps. Default: 0.
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
config_file: str,
|
|
89
|
+
data_type: Literal['pyg', 'dgl'] = 'pyg',
|
|
90
|
+
*args,
|
|
91
|
+
**kwargs
|
|
92
|
+
) -> None:
|
|
93
|
+
super().__init__(config_file)
|
|
94
|
+
|
|
95
|
+
self.dX = None
|
|
96
|
+
self.config_file = config_file
|
|
97
|
+
assert data_type in {'pyg', 'dgl'}, f'Invalid data type {data_type}. It must be "pyg" or "dgl".'
|
|
98
|
+
if data_type == 'pyg':
|
|
99
|
+
self.pygData = check_module('torch_geometric.data.batch')
|
|
100
|
+
else:
|
|
101
|
+
self.pygData = None
|
|
102
|
+
self.data_type = data_type
|
|
103
|
+
|
|
104
|
+
if self.VERBOSE: self.logger.info('Config File Was Successfully Read.')
|
|
105
|
+
self.param = None
|
|
106
|
+
self._has_load_data = False
|
|
107
|
+
self._data_loader = None
|
|
108
|
+
|
|
109
|
+
__relax_dict = {'CG': CG, 'BFGS': QN, 'FIRE': FIRE, 'DIMER': Dimer}
|
|
110
|
+
if self.RELAXATION is not None:
|
|
111
|
+
if self.TRANSITION_STATE is not None:
|
|
112
|
+
self.logger.warning(
|
|
113
|
+
'** WARNING: Both `RELAXATION` and `TRANSITION_STATE` were set. Hence, `TRANSITION_STATE` will be ignored. **'
|
|
114
|
+
)
|
|
115
|
+
self.TRANSITION_STATE = None
|
|
116
|
+
if self.RELAXATION['ALGO'] == 'BFGS':
|
|
117
|
+
_iterschem = 'BFGS'
|
|
118
|
+
iterschem = 'iter_scheme'
|
|
119
|
+
elif self.RELAXATION['ALGO'] == 'MIX':
|
|
120
|
+
_iterschem = self.RELAXATION.get('ITER_SCHEME', 'PR+')
|
|
121
|
+
iterschem = 'iterschem_CG' # iter_scheme QN was automatically set to 'BFGS'
|
|
122
|
+
elif self.RELAXATION['ALGO'] == 'CG':
|
|
123
|
+
_iterschem = self.RELAXATION.get('ITER_SCHEME', 'PR+')
|
|
124
|
+
iterschem = 'iter_scheme'
|
|
125
|
+
else:
|
|
126
|
+
_iterschem = 'FIRE'
|
|
127
|
+
iterschem = 'iter_scheme'
|
|
128
|
+
self.Stru_Opt = __relax_dict[self.RELAXATION['ALGO']]
|
|
129
|
+
# judge whether ALGO is minimization algo.
|
|
130
|
+
self.require_grad = self.RELAXATION.get('REQUIRE_GRAD', False)
|
|
131
|
+
if self.RELAXATION['ALGO'] not in {'CG', 'BFGS', 'FIRE'}:
|
|
132
|
+
self.logger.error(f'{self.RELAXATION['ALGO']} is NOT RELAXATION ALGORITHM!!!')
|
|
133
|
+
raise ValueError(f'{self.RELAXATION['ALGO']} is NOT RELAXATION ALGORITHM!!!')
|
|
134
|
+
if self.RELAXATION['ALGO'] != 'FIRE':
|
|
135
|
+
self.Stru_Opt_config = {iterschem: _iterschem,
|
|
136
|
+
'E_threshold': float(self.RELAXATION.get('E_THRES', 1.e-4)),
|
|
137
|
+
'F_threshold': float(self.RELAXATION.get('F_THRES', 0.05)),
|
|
138
|
+
'maxiter': int(self.RELAXATION.get('MAXITER', 300)),
|
|
139
|
+
'linesearch': self.RELAXATION.get('LINESEARCH', 'Backtrack'),
|
|
140
|
+
'linesearch_maxiter': int(self.RELAXATION.get('LINESEARCH_MAXITER', 10)),
|
|
141
|
+
'linesearch_thres': float(self.RELAXATION.get('LINESEARCH_THRES', 0.05)),
|
|
142
|
+
'linesearch_factor': float(self.RELAXATION.get('LINESEARCH_FACTOR', 0.6)),
|
|
143
|
+
'use_bb': bool(self.RELAXATION.get('USE_BB', True)),
|
|
144
|
+
'steplength': float(self.RELAXATION.get('STEPLENGTH', 0.5)),
|
|
145
|
+
'device': self.DEVICE,
|
|
146
|
+
'verbose': self.VERBOSE}
|
|
147
|
+
else:
|
|
148
|
+
self.Stru_Opt_config = {'E_threshold': float(self.RELAXATION.get('E_THRES', 1.e-4)),
|
|
149
|
+
'F_threshold': float(self.RELAXATION.get('F_THRES', 0.05)),
|
|
150
|
+
'maxiter': int(self.RELAXATION.get('MAXITER', 300)),
|
|
151
|
+
'steplength': float(self.RELAXATION.get('STEPLENGTH', 0.5)),
|
|
152
|
+
'alpha': float(self.RELAXATION.get('ALPHA', 0.1)),
|
|
153
|
+
'alpha_fac': float(self.RELAXATION.get('ALPHA_FAC', 0.99)),
|
|
154
|
+
'fac_inc': float(self.RELAXATION.get('FAC_INC', 1.1)),
|
|
155
|
+
'fac_dec': float(self.RELAXATION.get('FAC_DEC', 0.5)),
|
|
156
|
+
'N_min': int(self.RELAXATION.get('ALPHA_FAC', 5)),
|
|
157
|
+
'mass': float(self.RELAXATION.get('MASS', 20)),
|
|
158
|
+
'device': self.DEVICE,
|
|
159
|
+
'verbose': self.VERBOSE}
|
|
160
|
+
elif self.TRANSITION_STATE is not None:
|
|
161
|
+
self.Stru_Opt = __relax_dict[self.TRANSITION_STATE['ALGO']]
|
|
162
|
+
self.require_grad = self.TRANSITION_STATE.get('REQUIRE_GRAD', False)
|
|
163
|
+
self.x_diff_attr = self.TRANSITION_STATE.get('X_DIFF_ATTR', 'x_dimer')
|
|
164
|
+
self.Stru_Opt_config = {'E_threshold': self.TRANSITION_STATE.get('E_THRES', 1.e-4),
|
|
165
|
+
'Torque_thres': self.TRANSITION_STATE.get('TORQ_THRES', 1e-2),
|
|
166
|
+
'Curvature_thres': self.TRANSITION_STATE.get('CURVATURE_THRES', -0.1),
|
|
167
|
+
'F_threshold': self.TRANSITION_STATE.get('F_THRES', 0.05),
|
|
168
|
+
'maxiter_trans': int(self.TRANSITION_STATE.get('MAXITER_TRANS', 300)),
|
|
169
|
+
'maxiter_rot': int(self.TRANSITION_STATE.get('MAXITER_ROT', 10)),
|
|
170
|
+
'max_steplength': self.TRANSITION_STATE.get('MAX_STEPLENGTH', 0.5),
|
|
171
|
+
'dx': self.TRANSITION_STATE.get('DX', 1.e-2),
|
|
172
|
+
'device': self.DEVICE,
|
|
173
|
+
'verbose': self.VERBOSE}
|
|
174
|
+
|
|
175
|
+
if self.TRANSITION_STATE['ALGO'] == 'DIMER_LS':
|
|
176
|
+
self.Stru_Opt_config.update(
|
|
177
|
+
{
|
|
178
|
+
'maxiter_linsearch': int(self.TRANSITION_STATE.get('LINESEARCH_MAXITER', 10)),
|
|
179
|
+
'steplength': float(self.TRANSITION_STATE.get('STEPLENGTH', 0.5)),
|
|
180
|
+
'momenta_coeff': float(self.TRANSITION_STATE.get('MOMENTA_COEFF', 0.)),
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if (self.RELAXATION is None) and (self.TRANSITION_STATE is None):
|
|
185
|
+
self.logger.error('** ERROR: Both `RELAXATION` and `TRANSITION_STATE` were NOT set. NO TASK HERE, BYE!!! **')
|
|
186
|
+
raise RuntimeError('** ERROR: Both `RELAXATION` and `TRANSITION_STATE` were NOT set. NO TASK HERE, BYE!!! **')
|
|
187
|
+
|
|
188
|
+
def set_dimer_init_direction(self, directions: th.Tensor):
|
|
189
|
+
"""
|
|
190
|
+
Set the initial direction guess for the dimer algorithm.
|
|
191
|
+
Returns: None
|
|
192
|
+
|
|
193
|
+
"""
|
|
194
|
+
self._dimer_init_direction = directions
|
|
195
|
+
|
|
196
|
+
def run(self, model, mode: Literal['minimize', 'ts']):
|
|
197
|
+
"""
|
|
198
|
+
Run structure optimization algorithms to search local minima or saddle point (TS).
|
|
199
|
+
Parameters:
|
|
200
|
+
model: the input model which is `uninstantiated` nn.Module class.
|
|
201
|
+
mode: choose whether `minimize` or `ts`.
|
|
202
|
+
"""
|
|
203
|
+
# check logger
|
|
204
|
+
if not has_any_handler(self.logger): self.logger.addHandler(self.log_handler)
|
|
205
|
+
# check vars
|
|
206
|
+
_model: nn.Module = model(**self.MODEL_CONFIG)
|
|
207
|
+
if (self.START == 'resume') or (self.START == 1) or (self.START == 2):
|
|
208
|
+
chk_data = th.load(self.LOAD_CHK_FILE_PATH, weights_only=True)
|
|
209
|
+
if self.param is None:
|
|
210
|
+
_model.load_state_dict(chk_data['model_state_dict'], strict=self.STRICT_LOAD)
|
|
211
|
+
else:
|
|
212
|
+
_model.load_state_dict(self.param, self.is_strict, self.is_assign)
|
|
213
|
+
elif self.START == 'from_scratch' or self.START == 0:
|
|
214
|
+
self.logger.warning(
|
|
215
|
+
'** WARNING: The model was not read the trained parameters from checkpoint file. I HOPE YOU KNOW WHAT YOU ARE DOING! **'
|
|
216
|
+
)
|
|
217
|
+
if self.param is not None:
|
|
218
|
+
_model.load_state_dict(self.param, self.is_strict, self.is_assign)
|
|
219
|
+
else:
|
|
220
|
+
raise ValueError('Invalid START value. It should be "from_scratch" / 0 or "resume" / 1 ')
|
|
221
|
+
if mode not in {'minimize', 'ts'}:
|
|
222
|
+
raise ValueError(f'Invalid mode value. It should be "minimize" or "ts", but got {mode}')
|
|
223
|
+
if (mode == 'minimize') and self.RELAXATION is None:
|
|
224
|
+
self.logger.error('** ERROR: `mode` is "minimize" but the configs `RELAXATION` is None! NO TASK HERE, BYE!!! **')
|
|
225
|
+
raise RuntimeError('** ERROR: `mode` is "minimize" but the configs `RELAXATION` is None! NO TASK HERE, BYE!!! **')
|
|
226
|
+
elif (mode == 'ts') and self.TRANSITION_STATE is None:
|
|
227
|
+
self.logger.error('** ERROR: `mode` is "ts" but the configs `TRANSITION_STATE` is None! NO TASK HERE, BYE!!! **')
|
|
228
|
+
raise RuntimeError('** ERROR: `mode` is "ts" but the configs `TRANSITION_STATE` is None! NO TASK HERE, BYE!!! **')
|
|
229
|
+
# model vars
|
|
230
|
+
_model = _model.to(self.DEVICE)
|
|
231
|
+
|
|
232
|
+
# preprocessing data # TODO
|
|
233
|
+
if self._data_loader is None: raise RuntimeError('Please Set the DataLoader.')
|
|
234
|
+
if not self._has_load_data: raise RuntimeError('Please Set the Data to Predict.')
|
|
235
|
+
|
|
236
|
+
# initialize
|
|
237
|
+
self.n_samp = len(self.TRAIN_DATA['data']) # sample number
|
|
238
|
+
self.n_batch = math.ceil(self.n_samp / self.BATCH_SIZE) # total batch number per epoch
|
|
239
|
+
|
|
240
|
+
# I/O
|
|
241
|
+
try:
|
|
242
|
+
if self.VERBOSE > 0:
|
|
243
|
+
self.logout_task_information(_model, mode, self.Stru_Opt_config, self.n_samp)
|
|
244
|
+
|
|
245
|
+
time_tol = time.perf_counter()
|
|
246
|
+
_model.eval()
|
|
247
|
+
# MAIN LOOP
|
|
248
|
+
# define the model wrapper & batch size getter & cell vector getter for different data type
|
|
249
|
+
if self.data_type == 'pyg':
|
|
250
|
+
model_wrap = _Model_Wrapper_pyg(_model)
|
|
251
|
+
|
|
252
|
+
def get_batch_size(data):
|
|
253
|
+
return len(data)
|
|
254
|
+
|
|
255
|
+
def get_cell_vec(data):
|
|
256
|
+
return data.cell.numpy(force=True)
|
|
257
|
+
|
|
258
|
+
def get_atomic_number(data):
|
|
259
|
+
return data.atomic_numbers.unsqueeze(0)
|
|
260
|
+
|
|
261
|
+
def get_indx(data):
|
|
262
|
+
_indx: Dict = getattr(data, 'idx', None)
|
|
263
|
+
return _indx
|
|
264
|
+
|
|
265
|
+
def get_fixed_mask(data):
|
|
266
|
+
return data.fixed.unsqueeze(0)
|
|
267
|
+
|
|
268
|
+
def get_batch_indx(data):
|
|
269
|
+
return [len(dat.pos) for dat in data.to_data_list()]
|
|
270
|
+
|
|
271
|
+
def get_init_dX(data):
|
|
272
|
+
""" get dimer initial guess """
|
|
273
|
+
return getattr(data, self.x_diff_attr, None)
|
|
274
|
+
|
|
275
|
+
self.__check_old: th.Tensor|None = None
|
|
276
|
+
update_batch = PygBatchUpdater()
|
|
277
|
+
update_batch_rot = PygBatchUpdater()
|
|
278
|
+
|
|
279
|
+
else:
|
|
280
|
+
model_wrap = _Model_Wrapper_dgl(_model)
|
|
281
|
+
|
|
282
|
+
def get_batch_size(data):
|
|
283
|
+
return data.num_nodes('atom')
|
|
284
|
+
|
|
285
|
+
def get_cell_vec(data):
|
|
286
|
+
return data.nodes['cell'].data['cell'].numpy(force=True)
|
|
287
|
+
|
|
288
|
+
def get_atomic_number(data):
|
|
289
|
+
return data.nodes['atom'].data['Z'].unsqueeze(0)
|
|
290
|
+
|
|
291
|
+
def get_indx(data):
|
|
292
|
+
_indx: Dict = data.nodes['atom'].data
|
|
293
|
+
return _indx.get('idx', None)
|
|
294
|
+
|
|
295
|
+
def get_fixed_mask(data):
|
|
296
|
+
return data.nodes['atom'].data.get('fix', None)
|
|
297
|
+
|
|
298
|
+
def get_batch_indx(data):
|
|
299
|
+
return data.batch_num_nodes('atom').tolist()
|
|
300
|
+
|
|
301
|
+
def get_init_dX(data):
|
|
302
|
+
""" get dimer initial guess """
|
|
303
|
+
return data.nodes['atom'].data['dx'].unsqueeze(0)
|
|
304
|
+
|
|
305
|
+
def update_batch(
|
|
306
|
+
converge_check,
|
|
307
|
+
func_args,
|
|
308
|
+
func_kwargs,
|
|
309
|
+
grad_func_args,
|
|
310
|
+
grad_func_kwargs
|
|
311
|
+
):
|
|
312
|
+
# TODO <<<<<<<<<
|
|
313
|
+
pass
|
|
314
|
+
|
|
315
|
+
update_batch_rot = None
|
|
316
|
+
raise NotImplementedError
|
|
317
|
+
|
|
318
|
+
optimizer = self.Stru_Opt(**self.Stru_Opt_config)
|
|
319
|
+
val_set: Any = self._data_loader(self.TRAIN_DATA, self.BATCH_SIZE, self.DEVICE, is_train=False, **self._data_loader_configs)
|
|
320
|
+
n_c = 1 # number of cycles. each for-loop += 1.
|
|
321
|
+
n_s = 0 # number of calculated samples. each sample in batches in each for-loop += 1.
|
|
322
|
+
for val_data, val_label in val_set:
|
|
323
|
+
try: # Catch error in each loop & continue, instead of directly exit.
|
|
324
|
+
# to avoid get an empty batch
|
|
325
|
+
if get_batch_size(val_data) <= 0:
|
|
326
|
+
if self.VERBOSE: self.logger.info(f'An empty batch occurred. Skipped.')
|
|
327
|
+
continue
|
|
328
|
+
# batch indices
|
|
329
|
+
batch_indx:List = get_batch_indx(val_data)
|
|
330
|
+
# initial atom coordinates
|
|
331
|
+
if self.data_type == 'pyg':
|
|
332
|
+
X_init = val_data.pos.unsqueeze(0) if val_data.pos.dim() == 2 else val_data.pos
|
|
333
|
+
else:
|
|
334
|
+
_ = val_data.nodes['atom'].data['pos']
|
|
335
|
+
X_init = _.unsqueeze(0) if _.dim() == 2 else _
|
|
336
|
+
del _
|
|
337
|
+
# initialize X_diff
|
|
338
|
+
if mode == 'ts':
|
|
339
|
+
X_diff = get_init_dX(val_data)
|
|
340
|
+
if X_diff is None:
|
|
341
|
+
self.logger.warning(
|
|
342
|
+
f'WARNING: No initial dimer direction given. A random direction will be used.'
|
|
343
|
+
)
|
|
344
|
+
elif not isinstance(X_diff, th.Tensor):
|
|
345
|
+
X_diff = None
|
|
346
|
+
self.logger.warning(
|
|
347
|
+
f'WARNING: The dimer direction `X_diff` is expected to be a torch.Tensor, but got {type(X_diff)}, '
|
|
348
|
+
f'so it will be reset randomly.'
|
|
349
|
+
)
|
|
350
|
+
else:
|
|
351
|
+
if X_diff.dim() == 2:
|
|
352
|
+
X_diff = X_diff.unsqueeze(0)
|
|
353
|
+
X_diff = X_diff.to(X_init.dtype)
|
|
354
|
+
if X_diff.shape != X_init.shape:
|
|
355
|
+
X_diff = None # TS (Dimer) method will automatically create a random X_diff when input `X_diff = None`.
|
|
356
|
+
self.logger.warning(
|
|
357
|
+
f'WARNING: The dimer direction X_diff has different shape from `X_init`, so it will be reset randomly.'
|
|
358
|
+
)
|
|
359
|
+
else:
|
|
360
|
+
X_diff = None # placeholder
|
|
361
|
+
# cells & fixations
|
|
362
|
+
CELL = get_cell_vec(val_data)
|
|
363
|
+
fixed_mask = get_fixed_mask(val_data)
|
|
364
|
+
# get id
|
|
365
|
+
idx = get_indx(val_data)
|
|
366
|
+
idx = idx if idx is not None else [f'Untitled{_}' for _ in range(n_s, n_s + len(batch_indx))]
|
|
367
|
+
n_s += len(batch_indx)
|
|
368
|
+
element_tensor = get_atomic_number(val_data)
|
|
369
|
+
if self.VERBOSE > 0:
|
|
370
|
+
self.logger.info('*' * 89)
|
|
371
|
+
self.logger.info(f'Running Batch {n_c}.')
|
|
372
|
+
cell_str = np.array2string(
|
|
373
|
+
CELL, **FLOAT_ARRAY_FORMAT
|
|
374
|
+
).replace("[", " ").replace("]", " ")
|
|
375
|
+
self.logger.info(f'Structure names: {idx}\n')
|
|
376
|
+
self.logger.info(f'Cell Vectors:\n{cell_str}\n')
|
|
377
|
+
# print structures titles with elements
|
|
378
|
+
self.logout_element_information(element_tensor, batch_indx)
|
|
379
|
+
# relax
|
|
380
|
+
with th.no_grad():
|
|
381
|
+
update_batch.initialize()
|
|
382
|
+
update_batch_rot.initialize()
|
|
383
|
+
if mode == 'minimize':
|
|
384
|
+
optimizer: BatchOptim.FIRE
|
|
385
|
+
optimizer.set_batch_updater(update_batch, update_batch_rot)
|
|
386
|
+
fin_ener, fin_x, fin_grad = optimizer.run(
|
|
387
|
+
func=model_wrap.Energy,
|
|
388
|
+
X=X_init,
|
|
389
|
+
grad_func=model_wrap.Grad,
|
|
390
|
+
func_args=(val_data,),
|
|
391
|
+
grad_func_args=(val_data,),
|
|
392
|
+
is_grad_func_contain_y=False,
|
|
393
|
+
output_grad=True,
|
|
394
|
+
require_grad = self.require_grad,
|
|
395
|
+
batch_indices=batch_indx,
|
|
396
|
+
fixed_atom_tensor=fixed_mask,
|
|
397
|
+
)
|
|
398
|
+
else: # i.e., mode == 'ts'
|
|
399
|
+
optimizer: BatchOptim.Dimer
|
|
400
|
+
optimizer.set_batch_updater(update_batch, update_batch_rot)
|
|
401
|
+
fin_ener, fin_x, fin_grad = optimizer.run(
|
|
402
|
+
func=model_wrap.Energy,
|
|
403
|
+
X=X_init,
|
|
404
|
+
X_diff=X_diff,
|
|
405
|
+
grad_func=model_wrap.Grad,
|
|
406
|
+
func_args=(val_data,),
|
|
407
|
+
grad_func_args=(val_data,),
|
|
408
|
+
is_grad_func_contain_y=False,
|
|
409
|
+
output_grad=True,
|
|
410
|
+
require_grad=self.require_grad,
|
|
411
|
+
batch_indices=batch_indx,
|
|
412
|
+
fixed_atom_tensor=fixed_mask,
|
|
413
|
+
)
|
|
414
|
+
fin_ener = fin_ener.detach()#.squeeze(0)
|
|
415
|
+
fin_x = fin_x.detach().squeeze(0)
|
|
416
|
+
fin_grad = fin_grad.detach().squeeze(0)
|
|
417
|
+
# Postprocessing & save TODO: reformat it in future to apply to all functions.
|
|
418
|
+
self.dumper.collect(
|
|
419
|
+
batch_indx,
|
|
420
|
+
idx,
|
|
421
|
+
get_atomic_number(val_data).squeeze(0),
|
|
422
|
+
CELL,
|
|
423
|
+
fin_x,
|
|
424
|
+
fixed_mask[0],
|
|
425
|
+
fin_ener,
|
|
426
|
+
- fin_grad,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
# Print info
|
|
430
|
+
if self.VERBOSE > 0:
|
|
431
|
+
self.logger.info('-' * 100)
|
|
432
|
+
n_c += 1
|
|
433
|
+
|
|
434
|
+
except Exception as e:
|
|
435
|
+
self.logger.warning(f'** WARNING: An error occurred in the {n_c}-th batch. Error: {e}. **')
|
|
436
|
+
if self.VERBOSE > 0:
|
|
437
|
+
excp = traceback.format_exc()
|
|
438
|
+
self.logger.warning(f"Traceback:\n{excp}")
|
|
439
|
+
n_c += 1
|
|
440
|
+
|
|
441
|
+
if self.VERBOSE: self.logger.info(f'RELAXATION DONE. Total Time: {time.perf_counter() - time_tol:<.4f}')
|
|
442
|
+
if self.SAVE_PREDICTIONS:
|
|
443
|
+
t_save = time.perf_counter()
|
|
444
|
+
with _LoggingEnd(self.log_handler):
|
|
445
|
+
if self.VERBOSE: self.logger.info(f'SAVING RESULTS TO {self.PREDICTIONS_SAVE_FILE} ...')
|
|
446
|
+
self.dumper.flush()
|
|
447
|
+
if self.VERBOSE: self.logger.info(f'Done. Saving Time: {time.perf_counter() - t_save:<.4f}')
|
|
448
|
+
|
|
449
|
+
except Exception as e:
|
|
450
|
+
th.cuda.synchronize()
|
|
451
|
+
excp = traceback.format_exc()
|
|
452
|
+
self.logger.exception(f'An ERROR occurred:\n\t{e}\nTraceback:\n{excp}')
|
|
453
|
+
|
|
454
|
+
finally:
|
|
455
|
+
th.cuda.synchronize()
|
|
456
|
+
self.logger.removeHandler(self.log_handler)
|
|
457
|
+
if isinstance(self.log_handler, logging.FileHandler):
|
|
458
|
+
self.log_handler.close()
|
|
459
|
+
self.dumper.close()
|
|
460
|
+
|
|
461
|
+
def relax(self, model):
|
|
462
|
+
"""
|
|
463
|
+
Run minimization algorithms to relax the structures.
|
|
464
|
+
Alias of `self.run(model, mode='minimize')`.
|
|
465
|
+
Args:
|
|
466
|
+
model: the input model which is `uninstantiated` nn.Module class.
|
|
467
|
+
|
|
468
|
+
"""
|
|
469
|
+
return self.run(model, mode='minimize')
|
|
470
|
+
|
|
471
|
+
def transition_state(self, model):
|
|
472
|
+
"""
|
|
473
|
+
Use single-point methods (e.g., Dimer) to search the transition states.
|
|
474
|
+
Alias of `self.run(model, mode='ts')`.
|
|
475
|
+
Args:
|
|
476
|
+
model: the input model which is `uninstantiated` nn.Module class.
|
|
477
|
+
"""
|
|
478
|
+
return self.run(model, mode='ts')
|
|
479
|
+
|
|
480
|
+
def ts(self, model):
|
|
481
|
+
"""
|
|
482
|
+
Alias of `self.transition_state`.
|
|
483
|
+
"""
|
|
484
|
+
return self.transition_state(model)
|