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,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configs of Printing Numpy Arrays Format.
|
|
3
|
+
|
|
4
|
+
e.g., numpy.array2string(Array, **FLOAT_ARRAY_FORMAT)
|
|
5
|
+
"""
|
|
6
|
+
# Copyright (c) 2024-2025.7.4, BUCToolkit.
|
|
7
|
+
# Authors: Pu Pengxin, Song Xin
|
|
8
|
+
# Version: 0.9a
|
|
9
|
+
# File: _print_formatter.py
|
|
10
|
+
# Environment: Python 3.12
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
FLOAT_ARRAY_FORMAT = {
|
|
15
|
+
'max_line_width': 1024,
|
|
16
|
+
'precision': 8,
|
|
17
|
+
'separator': ' ',
|
|
18
|
+
'sign': ' ',
|
|
19
|
+
'floatmode': 'fixed',
|
|
20
|
+
'threshold': 21_4748_3647,
|
|
21
|
+
'formatter': {'float_kind': lambda x: f'{x:0< 16.8f}', 'int_kind': lambda x: f'{x:> 16d}'}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
INT_ARRAY_FORMAT = {
|
|
25
|
+
'max_line_width': 1024,
|
|
26
|
+
'separator': ' ',
|
|
27
|
+
'sign': ' ',
|
|
28
|
+
'threshold': 21_4748_3647,
|
|
29
|
+
'formatter': {'int_kind': lambda x: f'{x:> 16d}'}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
STRING_ARRAY_FORMAT = {
|
|
33
|
+
'max_line_width': 1024,
|
|
34
|
+
'separator': ' ',
|
|
35
|
+
'sign': ' ',
|
|
36
|
+
'threshold': 21_4748_3647,
|
|
37
|
+
'formatter': {'str_kind': lambda x: f'{x}'}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
SCIENTIFIC_ARRAY_FORMAT = {
|
|
41
|
+
'max_line_width': 1024,
|
|
42
|
+
'precision': 8,
|
|
43
|
+
'separator': ' ',
|
|
44
|
+
'sign': ' ',
|
|
45
|
+
'floatmode': 'fixed',
|
|
46
|
+
'threshold': 21_4748_3647,
|
|
47
|
+
'formatter': {'float_kind': lambda x: f'{x:< .8e}', 'int_kind': lambda x: f'{x:> 16d}'}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
GLOBAL_SCIENTIFIC_ARRAY_FORMAT = {
|
|
51
|
+
'precision': 8,
|
|
52
|
+
'sign': ' ',
|
|
53
|
+
'floatmode': 'fixed',
|
|
54
|
+
'threshold': 21_4748_3647,
|
|
55
|
+
'formatter': {'float_kind': lambda x: f'{x:< .8e}', 'int_kind': lambda x: f'{x:> 16d}'}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
def __as_formatted_str(a: float, ): return f'{a:0< 14.8f}'
|
|
59
|
+
AS_PRINT_COORDS = np.vectorize(__as_formatted_str, otypes='U')
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Check whether the structures in `BatchStructures` have abnormalities which contain:
|
|
3
|
+
* too large Forces (if forces are not None)
|
|
4
|
+
* too large Coordinates
|
|
5
|
+
* too close distance
|
|
6
|
+
* too large or close distance between adsorbate and slab
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
# Copyright (c) 2025.7.30, BUCToolkit.
|
|
10
|
+
# Authors: Pu Pengxin, Song Xin
|
|
11
|
+
# Version: 0.9a
|
|
12
|
+
# File: check_if_converge.py
|
|
13
|
+
# Environment: Python 3.12
|
|
14
|
+
import warnings
|
|
15
|
+
from typing import List, Callable, Tuple, Any, Dict
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
import joblib as jb
|
|
20
|
+
from BUCToolkit.utils.AtomicNumber2Properties import elements_to_atomic_numbers
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def check_if_converge(
|
|
24
|
+
data,
|
|
25
|
+
forces_threshold: float = 0.05,
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
data: BatchStructures data.
|
|
31
|
+
forces_threshold: force threshold for convergence.
|
|
32
|
+
|
|
33
|
+
Returns: list of unconverged structure names.
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
# check
|
|
37
|
+
data.change_mode('L')
|
|
38
|
+
if data.Forces is None:
|
|
39
|
+
warnings.warn('Forces are not available, skipping.', RuntimeWarning)
|
|
40
|
+
return list()
|
|
41
|
+
unconverged_list = list()
|
|
42
|
+
for i, coo in enumerate(data.Forces):
|
|
43
|
+
if np.any(coo > forces_threshold):
|
|
44
|
+
unconverged_list.append(data.Sample_ids[i])
|
|
45
|
+
|
|
46
|
+
return unconverged_list
|
|
47
|
+
|
|
48
|
+
def check_if_abnormal(
|
|
49
|
+
data,
|
|
50
|
+
coo_upper_bound: float = 25.,
|
|
51
|
+
distance_threshold: float = 0.7,
|
|
52
|
+
adsorbate_indices: List[int]|None = None,
|
|
53
|
+
adsorbate_elements: List[str]|None = None,
|
|
54
|
+
exclude_indices: List[int]|None = None,
|
|
55
|
+
exclude_elements: List[str]|None = None,
|
|
56
|
+
ads_slab_dist_upper_bound: float = 5.,
|
|
57
|
+
ads_slab_dist_lower_bound: float = 1.3,
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
Check whether the structures in `BatchStructures` have abnormalities.
|
|
61
|
+
Args:
|
|
62
|
+
data: the data of BatchStructures.
|
|
63
|
+
coo_upper_bound: the upper bound of coordinates.
|
|
64
|
+
distance_threshold: the distance threshold. distance less than it would be set to abnormal.
|
|
65
|
+
adsorbate_indices: atomic indices of absorbate. the selected atoms as adsorbate are the combination of `adsorbate_indices` and `adsorbate_elements`
|
|
66
|
+
adsorbate_elements: elements of selected adsorbates. the selected atoms as adsorbate are the combination of `adsorbate_indices` and `adsorbate_elements`
|
|
67
|
+
exclude_indices: the indices of atoms that do not involve in abnormal check. the REST atom will be considered as SLAB.
|
|
68
|
+
exclude_elements: the indices of atoms that do not involve in abnormal check. the REST atom will be considered as SLAB. it will be combination with `exclude_indices`.
|
|
69
|
+
ads_slab_dist_upper_bound: distance upper bound between adsorbate and slab.
|
|
70
|
+
ads_slab_dist_lower_bound: distance lower bound between adsorbate and slab.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
# TODO: setting specific cut-off distance for different atom pairs. for example, use sum of covalent radii in a pre-stored dict.
|
|
76
|
+
def parse_indx2mask(_indices, _elements, all_elements):
|
|
77
|
+
_elements = elements_to_atomic_numbers(_elements) if _elements is not None else list()
|
|
78
|
+
all_elements_set = set(all_elements)
|
|
79
|
+
_mask = np.full_like(all_elements, False, dtype=bool)
|
|
80
|
+
if _indices is not None:
|
|
81
|
+
_mask[_indices] = True
|
|
82
|
+
for ii, _elem in enumerate(_elements):
|
|
83
|
+
if _elem in all_elements_set:
|
|
84
|
+
_tmp_mask = (all_elements == _elem)
|
|
85
|
+
_mask = _mask + _tmp_mask
|
|
86
|
+
else:
|
|
87
|
+
warnings.warn(f"The given element {_elem} not in {all_elements_set}.", RuntimeWarning)
|
|
88
|
+
|
|
89
|
+
return _mask # (n_atoms, )
|
|
90
|
+
|
|
91
|
+
data.change_mode('L')
|
|
92
|
+
data.direct2cartesian()
|
|
93
|
+
data.generate_atomic_number_list()
|
|
94
|
+
data.generate_dist_mat()
|
|
95
|
+
err_config_list = list()
|
|
96
|
+
for i, coo in enumerate(data.Coords):
|
|
97
|
+
is_overflow = np.any(coo > coo_upper_bound)
|
|
98
|
+
is_too_close = np.any(data.Dist_mat[i] + 10. * np.eye(data.Dist_mat[i].shape[-1]) < distance_threshold)
|
|
99
|
+
|
|
100
|
+
atm_num_arr = np.asarray(data.Atomic_number_list[i])
|
|
101
|
+
# parse ads.
|
|
102
|
+
ads_mask = parse_indx2mask(adsorbate_indices, adsorbate_elements, atm_num_arr)
|
|
103
|
+
exclude_mask = parse_indx2mask(exclude_indices, exclude_elements, atm_num_arr)
|
|
104
|
+
slab_mask = np.full_like(atm_num_arr, True, dtype=bool)
|
|
105
|
+
slab_mask[exclude_mask] = False
|
|
106
|
+
slab_mask[ads_mask] = False
|
|
107
|
+
if np.any(ads_mask) and np.any(slab_mask):
|
|
108
|
+
ads_coo = coo[ads_mask]
|
|
109
|
+
rest_coo = coo[slab_mask]
|
|
110
|
+
ads_slab_dist_mat = np.linalg.norm(ads_coo[:, None, ...] - rest_coo[None, ...], axis=-1)
|
|
111
|
+
is_ads_too_far = np.all(ads_slab_dist_mat > ads_slab_dist_upper_bound)
|
|
112
|
+
is_ads_too_close = np.any(ads_slab_dist_mat < ads_slab_dist_lower_bound)
|
|
113
|
+
else:
|
|
114
|
+
is_ads_too_far = False
|
|
115
|
+
is_ads_too_close = False
|
|
116
|
+
|
|
117
|
+
if is_overflow or is_too_close or is_ads_too_far or is_ads_too_close:
|
|
118
|
+
err_config_list.append(data.Sample_ids[i])
|
|
119
|
+
|
|
120
|
+
return err_config_list
|
|
121
|
+
|
|
122
|
+
def batched_check_files(
|
|
123
|
+
path: str,
|
|
124
|
+
file_loader: Callable,
|
|
125
|
+
file_loader_args: Tuple[Any] = tuple(),
|
|
126
|
+
file_loader_kwargs: Dict[str, Any] | None =None,
|
|
127
|
+
file_list: List[str] | None = None,
|
|
128
|
+
n_core: int = 1,
|
|
129
|
+
is_check_convergence: bool = True,
|
|
130
|
+
force_threshold: float = 0.05,
|
|
131
|
+
is_check_configuration: bool = True,
|
|
132
|
+
coo_upper_bound: float = 25.,
|
|
133
|
+
distance_threshold: float = 0.7,
|
|
134
|
+
adsorbate_indices: List[int]|None = None,
|
|
135
|
+
adsorbate_elements: List[str]|None = None,
|
|
136
|
+
exclude_indices: List[int]|None = None,
|
|
137
|
+
exclude_elements: List[str]|None = None,
|
|
138
|
+
ads_slab_dist_upper_bound: float = 5.,
|
|
139
|
+
ads_slab_dist_lower_bound: float = 1.3,
|
|
140
|
+
) -> Tuple[Dict, Dict]:
|
|
141
|
+
"""
|
|
142
|
+
Check multiple files in given path in parallel.
|
|
143
|
+
Args:
|
|
144
|
+
path: the path of files to check.
|
|
145
|
+
file_loader: the function that takes a file path and returns an `BatchStructure` type data. All data will be loaded in memory in priori, and then checking.
|
|
146
|
+
file_loader_args: arguments passed to `file_loader`.
|
|
147
|
+
file_loader_kwargs: keyword arguments passed to `file_loader`.
|
|
148
|
+
file_list: the list of files to check. if None, all files in the given path are checked.
|
|
149
|
+
n_core: the number of CPU cores to use.
|
|
150
|
+
is_check_convergence: whether to check forces convergence.
|
|
151
|
+
force_threshold: if is_check_convergence is True, the force threshold to check.
|
|
152
|
+
is_check_configuration: whether to check configurations abnormalities.
|
|
153
|
+
coo_upper_bound: the upper bound of coordinates.
|
|
154
|
+
distance_threshold: the distance threshold. distance less than it would be set to abnormal.
|
|
155
|
+
adsorbate_indices: atomic indices of absorbate. the selected atoms as adsorbate are the combination of `adsorbate_indices` and `adsorbate_elements`
|
|
156
|
+
adsorbate_elements: elements of selected adsorbates. the selected atoms as adsorbate are the combination of `adsorbate_indices` and `adsorbate_elements`
|
|
157
|
+
exclude_indices: the indices of atoms that do not involve in abnormal check. the REST atom will be considered as SLAB.
|
|
158
|
+
exclude_elements: the indices of atoms that do not involve in abnormal check. the REST atom will be considered as SLAB. it will be combination with `exclude_indices`.
|
|
159
|
+
ads_slab_dist_upper_bound: distance upper bound between adsorbate and slab.
|
|
160
|
+
ads_slab_dist_lower_bound: distance lower bound between adsorbate and slab.
|
|
161
|
+
|
|
162
|
+
Returns: Two Dict of {file_name: name_of_structures_not_converged} and {file_name: name_of_abnormal_structures}
|
|
163
|
+
|
|
164
|
+
"""
|
|
165
|
+
if file_loader_kwargs is None:
|
|
166
|
+
file_loader_kwargs = dict()
|
|
167
|
+
if file_list is None:
|
|
168
|
+
file_list = os.listdir(path)
|
|
169
|
+
file_list = [os.path.join(path, _f) for _f in file_list]
|
|
170
|
+
|
|
171
|
+
def _check_single(file_name):
|
|
172
|
+
data = file_loader(file_name, *file_loader_args, **file_loader_kwargs)
|
|
173
|
+
if is_check_convergence:
|
|
174
|
+
converge_info = check_if_converge(data, force_threshold)
|
|
175
|
+
else:
|
|
176
|
+
converge_info = list()
|
|
177
|
+
if is_check_configuration:
|
|
178
|
+
abnormal_info = check_if_abnormal(
|
|
179
|
+
data,
|
|
180
|
+
coo_upper_bound = coo_upper_bound,
|
|
181
|
+
distance_threshold = distance_threshold,
|
|
182
|
+
adsorbate_indices = adsorbate_indices,
|
|
183
|
+
adsorbate_elements = adsorbate_elements,
|
|
184
|
+
exclude_indices = exclude_indices,
|
|
185
|
+
exclude_elements = exclude_elements,
|
|
186
|
+
ads_slab_dist_upper_bound = ads_slab_dist_upper_bound,
|
|
187
|
+
ads_slab_dist_lower_bound = ads_slab_dist_lower_bound,
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
abnormal_info = list()
|
|
191
|
+
basename = os.path.basename(file_name)
|
|
192
|
+
return {basename: converge_info}, {basename: abnormal_info}
|
|
193
|
+
|
|
194
|
+
_para = jb.parallel.Parallel(n_core, )
|
|
195
|
+
check_dict_list = _para(jb.delayed(_check_single)(filename) for filename in file_list)
|
|
196
|
+
if check_dict_list is None:
|
|
197
|
+
raise RuntimeError('Occurred None data.')
|
|
198
|
+
check_dict_conv = dict()
|
|
199
|
+
check_dict_abnormal = dict()
|
|
200
|
+
for _check_dict in check_dict_list:
|
|
201
|
+
check_dict_conv.update(_check_dict[0])
|
|
202
|
+
check_dict_abnormal.update(_check_dict[1])
|
|
203
|
+
|
|
204
|
+
return check_dict_conv, check_dict_abnormal
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
utils used for functions and models, including wrapper, preloader, etc.
|
|
3
|
+
"""
|
|
4
|
+
# Copyright (c) 2024-2025.7.4, BUCToolkit.
|
|
5
|
+
# Authors: Pu Pengxin, Song Xin
|
|
6
|
+
# Version: 0.9a
|
|
7
|
+
# File: function_utils.py
|
|
8
|
+
# Environment: Python 3.12
|
|
9
|
+
|
|
10
|
+
from typing import Tuple, Dict, List, Any, Mapping, Sequence, Callable
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
|
|
13
|
+
import torch as th
|
|
14
|
+
import torch.nn as nn
|
|
15
|
+
|
|
16
|
+
__all__ = ['MeanModel', 'preload_func']
|
|
17
|
+
|
|
18
|
+
class MeanModel(nn.Module):
|
|
19
|
+
"""
|
|
20
|
+
calculate the mean value of multiple models as new output.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
models: sequence of models
|
|
24
|
+
hyperparams: hyperparameters of models. if Dict, all models would share the same hyperparameters.
|
|
25
|
+
"""
|
|
26
|
+
def __init__(self, models: Tuple[Any, ...] | List[Any], hyperparams: Tuple[Dict, ...] | Dict, model_output_key: Tuple[str, ...] | None = None):
|
|
27
|
+
super().__init__()
|
|
28
|
+
if isinstance(hyperparams, Sequence):
|
|
29
|
+
if len(models) != len(hyperparams):
|
|
30
|
+
raise RuntimeError(f'Number of hyperparameters {len(hyperparams)} and models {len(models)} does not match.')
|
|
31
|
+
self.models = nn.ModuleList([(models[_])(**hparams) for _, hparams in enumerate(hyperparams)])
|
|
32
|
+
elif isinstance(hyperparams, Dict):
|
|
33
|
+
self.models = nn.ModuleList([_model(**hyperparams) for _model in models])
|
|
34
|
+
self.output_key = model_output_key
|
|
35
|
+
|
|
36
|
+
def load_state_tuple(self, state_dict: Tuple[Mapping[str, Any]] | Mapping[str, Any], strict: bool = True, assign: bool = False):
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
state_dict:
|
|
41
|
+
strict:
|
|
42
|
+
assign:
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
if not isinstance(state_dict, Mapping):
|
|
48
|
+
_state_dict = dict()
|
|
49
|
+
for i, stdict in enumerate(state_dict):
|
|
50
|
+
_state_dict.update({f'models.{i}.{key}': val for key, val in stdict.items()})
|
|
51
|
+
else:
|
|
52
|
+
_state_dict = state_dict
|
|
53
|
+
self.load_state_dict(_state_dict, strict, assign)
|
|
54
|
+
|
|
55
|
+
def forward(self, X: Any):
|
|
56
|
+
if self.output_key is None:
|
|
57
|
+
y = 0.
|
|
58
|
+
if self.training:
|
|
59
|
+
pred_list = list()
|
|
60
|
+
for mod in self.models:
|
|
61
|
+
res = mod(X)
|
|
62
|
+
pred_list.append(res)
|
|
63
|
+
return pred_list
|
|
64
|
+
else:
|
|
65
|
+
for mod in self.models:
|
|
66
|
+
y = y + mod(X)
|
|
67
|
+
else:
|
|
68
|
+
y = {key: 0. for key in self.output_key}
|
|
69
|
+
if self.training:
|
|
70
|
+
pred_list = list()
|
|
71
|
+
for mod in self.models:
|
|
72
|
+
res = mod(X)
|
|
73
|
+
pred_list.append(res)
|
|
74
|
+
return pred_list
|
|
75
|
+
else:
|
|
76
|
+
l = len(self.models)
|
|
77
|
+
for mod in self.models:
|
|
78
|
+
y = {key: y[key] + mod(X)[key]/l for key in self.output_key}
|
|
79
|
+
|
|
80
|
+
return y
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class _BaseWrapper(ABC):
|
|
84
|
+
|
|
85
|
+
def __init__(self, model):
|
|
86
|
+
self._model = model
|
|
87
|
+
self.forces = None
|
|
88
|
+
self.X = None
|
|
89
|
+
|
|
90
|
+
@abstractmethod
|
|
91
|
+
def Energy(self, *args, **kwargs) -> th.Tensor:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
@abstractmethod
|
|
95
|
+
def Grad(self, *args, **kwargs) -> th.Tensor:
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
def to(self, *args, **kwargs):
|
|
99
|
+
"""
|
|
100
|
+
wrap the `to` method of wrapped model. If the wrapped model has not `to` method, simply passed.
|
|
101
|
+
Args:
|
|
102
|
+
*args:
|
|
103
|
+
**kwargs:
|
|
104
|
+
|
|
105
|
+
Returns: the obj that the wrapped model would return
|
|
106
|
+
|
|
107
|
+
"""
|
|
108
|
+
if hasattr(self._model, 'to') and callable(self._model.to):
|
|
109
|
+
self._model = self._model.to(*args, **kwargs)
|
|
110
|
+
|
|
111
|
+
return self
|
|
112
|
+
|
|
113
|
+
def eval(self):
|
|
114
|
+
"""
|
|
115
|
+
wrap the `eval` method of wrapped model. If the wrapped model has not `eval` method, simply passed.
|
|
116
|
+
Returns: None
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
if hasattr(self._model, 'eval'):
|
|
120
|
+
self._model.eval()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def preload_func(func: Callable, device: Any) -> Callable:
|
|
124
|
+
"""
|
|
125
|
+
Apply the `func.to(device)` and `func.eval()` methods to the input function if it has such method.
|
|
126
|
+
if `func` is just a subclass of torch.nn.Module, `func.zero_grad()` will be called, too.
|
|
127
|
+
Args:
|
|
128
|
+
func:
|
|
129
|
+
device:
|
|
130
|
+
|
|
131
|
+
Returns: callable, the function self.
|
|
132
|
+
|
|
133
|
+
"""
|
|
134
|
+
if isinstance(func, nn.Module):
|
|
135
|
+
func = func.to(device)
|
|
136
|
+
func.eval()
|
|
137
|
+
func.zero_grad()
|
|
138
|
+
else:
|
|
139
|
+
if hasattr(func, 'to'):
|
|
140
|
+
_ = func.to(device)
|
|
141
|
+
func = _ if _ is not None else func
|
|
142
|
+
if hasattr(func, 'eval'):
|
|
143
|
+
func.eval()
|
|
144
|
+
if hasattr(func, 'zero_grad'):
|
|
145
|
+
func.zero_grad()
|
|
146
|
+
|
|
147
|
+
return func
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
""" functions of gradient-related calculations. """
|
|
2
|
+
# Copyright (c) 2025.10.17, BUCToolkit.
|
|
3
|
+
# Authors: Pu Pengxin, Song Xin
|
|
4
|
+
# Version: 0.9a
|
|
5
|
+
# File: jac_and_hvp.py
|
|
6
|
+
# Environment: Python 3.12
|
|
7
|
+
|
|
8
|
+
import torch as th
|
|
9
|
+
from typing import Callable, Tuple
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def jvp_wrapper(func: Callable, X:Tuple[th.Tensor, ...], v:Tuple[th.Tensor, ...], has_aux=False):
|
|
13
|
+
"""
|
|
14
|
+
A function to map func, X, v to J_func_X @ v, i.e., the jvp.
|
|
15
|
+
Args:
|
|
16
|
+
func:
|
|
17
|
+
X:
|
|
18
|
+
v:
|
|
19
|
+
has_aux:
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
if has_aux:
|
|
25
|
+
_, y, aux = th.func.jvp(func, X, v, has_aux=has_aux)
|
|
26
|
+
return y, aux
|
|
27
|
+
else:
|
|
28
|
+
_, y = th.func.jvp(func, X, v, has_aux=has_aux)
|
|
29
|
+
return y
|
|
30
|
+
|
|
31
|
+
def bjvp(func: Callable, X:Tuple[th.Tensor, ...], v:Tuple[th.Tensor, ...], has_aux=False):
|
|
32
|
+
"""
|
|
33
|
+
Batched Jacobian-vector product.
|
|
34
|
+
Args:
|
|
35
|
+
func: The function to calculate the batched JVP.
|
|
36
|
+
X: the input of the function.
|
|
37
|
+
v: the vector that Jacobian product to.
|
|
38
|
+
has_aux: whether to output auxiliary variables.
|
|
39
|
+
|
|
40
|
+
Returns: Tuple[th.Tensor, th.Tensor] | Tuple[th.Tensor, th.Tensor, th.Tensor]
|
|
41
|
+
if `has_aux` is True, return function values y, jvp, aux
|
|
42
|
+
otherwise, return function values y and jvp.
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
if has_aux:
|
|
46
|
+
val, jvp, aux = th.vmap(th.func.jvp, (None, (0, ), (0, )))(func, X, v, has_aux=has_aux)
|
|
47
|
+
return val, jvp, aux
|
|
48
|
+
else:
|
|
49
|
+
val, jvp = th.vmap(th.func.jvp, (None, (0,), (0,)))(func, X, v, has_aux=has_aux)
|
|
50
|
+
return val, jvp
|
|
51
|
+
|
|
52
|
+
def bhvp(func: Callable, X:Tuple[th.Tensor, ...], v:Tuple[th.Tensor, ...], has_aux:bool=False):
|
|
53
|
+
"""
|
|
54
|
+
Batched Hessian-vector product.
|
|
55
|
+
Args:
|
|
56
|
+
func: The function to calculate the batched HVP.
|
|
57
|
+
X: the input of the function.
|
|
58
|
+
v: the vector that Hessian product to.
|
|
59
|
+
has_aux: whether to output auxiliary variables.
|
|
60
|
+
|
|
61
|
+
Returns: th.Tensor | Tuple[th.Tensor, th.Tensor], if `has_aux` is True: hvp, jvp; otherwise, hvp.
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
if has_aux:
|
|
65
|
+
y_, jvp_ = th.vmap(th.func.jacrev(jvp_wrapper, argnums=1, has_aux=has_aux), (None, (0,), (0,), None))(func, X, v, has_aux)
|
|
66
|
+
return y_[0], jvp_
|
|
67
|
+
else:
|
|
68
|
+
y_ = th.vmap(th.func.jacrev(jvp_wrapper, argnums=1, has_aux=has_aux), (None, (0,), (0,), None))(func, X, v, has_aux)
|
|
69
|
+
return y_[0]
|