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,279 @@
1
+ """ Calculating Normal Mode Vibration Frequencies """
2
+ # Copyright (c) 2025.7.4, BUCToolkit.
3
+ # Authors: Pu Pengxin, Song Xin
4
+ # Version: 0.9a
5
+ # File: VibrationAnalysis.py
6
+ # Environment: Python 3.12
7
+
8
+ import logging
9
+ import math
10
+ import time
11
+ import traceback
12
+ import warnings
13
+ from typing import Any, Literal, Dict
14
+
15
+ import numpy as np
16
+ import torch as th
17
+ from torch import nn
18
+
19
+ from BUCToolkit.BatchOptim.frequency import Frequency
20
+ from BUCToolkit.api._io import _CONFIGS, _LoggingEnd, _Model_Wrapper_regularBatch_pyg, _Model_Wrapper_dgl
21
+ from BUCToolkit.utils._print_formatter import FLOAT_ARRAY_FORMAT
22
+ from BUCToolkit.utils._Element_info import ATOMIC_NUMBER, MASS, N_MASS
23
+
24
+ REVISED_FLOAT_ARRAY_FORMAT = FLOAT_ARRAY_FORMAT # reduce the line_width to exhibit eigen-frequencies.
25
+ REVISED_FLOAT_ARRAY_FORMAT['max_line_width'] = 64
26
+
27
+
28
+ class VibrationAnalysis(_CONFIGS):
29
+ """
30
+ The class of normal mode frequencies calculation by finite difference algo.
31
+ Due to the huge computation cost, it would run sequentially instead of batched.
32
+ Users need to set the dataset and dataloader manually.
33
+
34
+ Args:
35
+ config_file: the path of input file.
36
+ data_type: graph data type. 'pyg' for torch-geometric BatchData, 'dgl' for dgl DGLGraph.
37
+
38
+ Input file parameters:
39
+ BLOCK_SIZE: int, the batch size of points (i.e., one structure image of finite difference) for parallel computing at one time. Default: 1.
40
+ DELTA: float, the step length of finite difference. Default: 1e-2.
41
+ SAVE_HESSIAN: bool, whether to save calculated Hessian matrix. Default: False.
42
+
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ config_file: str,
48
+ data_type: Literal['pyg', 'dgl'] = 'pyg',
49
+ ) -> None:
50
+ super().__init__(config_file)
51
+
52
+ self.config_file = config_file
53
+ assert data_type in {'pyg', 'dgl'}, f'Invalid data type {data_type}. It must be "pyg" or "dgl".'
54
+ self.data_type = data_type
55
+ self.reload_config(config_file)
56
+ if self.VERBOSE: self.logger.info('Config File Was Successfully Read.')
57
+ self.param = None
58
+ self._has_load_data = False
59
+ self._data_loader = None
60
+
61
+ self.Vib_config = {
62
+ 'method': self.VIBRATION.get('METHOD', 'Coord'),
63
+ 'block_size': int(self.VIBRATION.get('BLOCK_SIZE', 1)),
64
+ 'delta': float(self.VIBRATION.get('DELTA', 1e-2)),
65
+ }
66
+ self.is_save_Hessian = bool(self.VIBRATION.get('SAVE_HESSIAN', False))
67
+
68
+ def run(self, model):
69
+ """
70
+ Parameters:
71
+ model: the input model which is `uninstantiated` nn.Module class.
72
+ """
73
+ # check logger
74
+ if not self.logger.hasHandlers(): self.logger.addHandler(self.log_handler)
75
+ # check vars
76
+ _model: nn.Module = model(**self.MODEL_CONFIG)
77
+ if self.START == 'resume' or self.START == 1:
78
+ chk_data = th.load(self.LOAD_CHK_FILE_PATH, weights_only=True)
79
+ if self.param is None:
80
+ _model.load_state_dict(chk_data['model_state_dict'], strict=False)
81
+ else:
82
+ _model.load_state_dict(self.param, self.is_strict, self.is_assign)
83
+ epoch_now = chk_data['epoch']
84
+ elif self.START == 'from_scratch' or self.START == 0:
85
+ self.logger.warning(
86
+ 'WARNING: The model was not read the trained parameters from checkpoint file. I HOPE YOU KNOW WHAT YOU ARE DOING!'
87
+ )
88
+ epoch_now = 0
89
+ if self.param is not None:
90
+ _model.load_state_dict(self.param, self.is_strict, self.is_assign)
91
+ else:
92
+ raise ValueError('Invalid START value. It should be "from_scratch" / 0 or "resume" / 1 ')
93
+ # model vars
94
+ _model = _model.to(self.DEVICE)
95
+
96
+ # preprocessing data # TODO
97
+ if self._data_loader is None: raise RuntimeError('Please Set the DataLoader.')
98
+ if not self._has_load_data: raise RuntimeError('Please Set the Data to Predict.')
99
+
100
+ # initialize
101
+ self.n_samp = len(self.TRAIN_DATA['data']) # sample number
102
+ self.n_batch = math.ceil(self.n_samp // self.BATCH_SIZE) # total batch number per epoch
103
+
104
+ # I/O
105
+ try:
106
+ if self.VERBOSE > 0:
107
+ self.logout_task_information(_model, 'VIB', self.Vib_config, self.n_samp)
108
+
109
+ time_tol = time.perf_counter()
110
+ _model.eval()
111
+ # MAIN LOOP
112
+ # define the model wrapper & batch size getter & cell vector getter for different data type
113
+ if self.data_type == 'pyg':
114
+ model_wrap = _Model_Wrapper_regularBatch_pyg(_model) # note: Use regularBatch version of _model wrapper
115
+
116
+ def get_batch_size(data):
117
+ return len(data)
118
+
119
+ def get_cell_vec(data):
120
+ return data.cell.numpy(force=True)
121
+
122
+ def get_atomic_number(data):
123
+ return data.atomic_numbers.unsqueeze(0).tolist()
124
+
125
+ def get_indx(data):
126
+ _indx: Dict = getattr(data, 'idx', None)
127
+ return _indx
128
+
129
+ def get_fixed_mask(data):
130
+ return data.fixed.unsqueeze(0)
131
+
132
+ def rebatched_graph(single_graph, batch_size: int):
133
+ return model_wrap.pygBatch.from_data_list([single_graph, ]*batch_size)
134
+
135
+ else:
136
+ model_wrap = _Model_Wrapper_dgl(_model)
137
+ raise NotImplementedError # TODO <<<< complete dgl format
138
+
139
+ def get_batch_size(data):
140
+ return data.num_nodes('atom')
141
+
142
+ def get_cell_vec(data):
143
+ return data.nodes['cell'].data['cell'].numpy(force=True)
144
+
145
+ def get_atomic_number(data):
146
+ return data.nodes['atom'].data['Z'].unsqueeze(0).tolist()
147
+
148
+ def get_indx(data):
149
+ _indx: Dict = data.nodes['atom'].data
150
+ return _indx.get('idx', None)
151
+
152
+ def get_fixed_mask(data):
153
+ return data.nodes['atom'].data.get('fix', None)
154
+
155
+ vib_calculator = Frequency(**self.Vib_config)
156
+ val_set: Any = self._data_loader(self.TRAIN_DATA, self.BATCH_SIZE, self.DEVICE, is_train=False, **self._data_loader_configs)
157
+ n_c = 1 # number of cycles. each for-loop += 1.
158
+ n_s = 0 # number of calculated samples. each sample in batches in each for-loop += 1.
159
+ # To record the minimized X, Force, and Energies.
160
+ freq_dict = dict()
161
+ normal_mode_dict = dict()
162
+ hessian_dict = dict()
163
+ if (self.VERBOSE < 1) and (not self.SAVE_PREDICTIONS):
164
+ warnings.warn('WARNING: Neither`verbose` nor `self.SAVE_PREDICTIONS` was turn on.'
165
+ ' Hence NOTHING WILL BE OUTPUT. I HOPE YOU KNOW WHAT YOU ARE DOING!')
166
+ if self.VERBOSE: self.logger.info(f'An empty batch occurred. Skipped.')
167
+ for val_data, val_label in val_set:
168
+ try: # Catch error in each loop & continue, instead of directly exit.
169
+ t_bt = time.perf_counter()
170
+ # to avoid get an empty batch
171
+ n_batch = get_batch_size(val_data)
172
+ if n_batch <= 0:
173
+ if self.VERBOSE: self.logger.info(f'An empty batch occurred. Skipped.')
174
+ continue
175
+ elif n_batch > 1:
176
+ if self.VERBOSE: self.logger.error(f'Vibration do not support batched calculation yet. You should set BATCH_SIZE to 1.')
177
+ raise RuntimeError(f'Vibration do not support batched calculation yet. You should set BATCH_SIZE to 1.')
178
+ # batch indices
179
+ batch_indx1 = th.sum(
180
+ th.eq(val_data.batch, th.arange(0, val_data.batch_size, dtype=th.int64, device=self.DEVICE).unsqueeze(-1)), dim=-1
181
+ )
182
+ if self.data_type == 'pyg':
183
+ batch_indx = [len(dat.pos) for dat in val_data.to_data_list()]
184
+ else:
185
+ batch_indx = val_data.batch_num_nodes('atom')
186
+ # initial atom coordinates
187
+ if self.data_type == 'pyg':
188
+ X_init = val_data.pos.unsqueeze(0)
189
+ else:
190
+ X_init = val_data.nodes['atom'].data['pos'].unsqueeze(0)
191
+ CELL = get_cell_vec(val_data)
192
+ fixed_mask = get_fixed_mask(val_data)
193
+ element_list = get_atomic_number(val_data)
194
+ # get masses
195
+ masses = list()
196
+ for _Elem in element_list:
197
+ masses.append([MASS[__elem] if isinstance(__elem, str) else N_MASS[__elem] for __elem in _Elem])
198
+ masses = th.tensor(masses, dtype=th.float32, device=self.DEVICE)
199
+ masses = masses.unsqueeze(-1).expand_as(X_init) # (n_batch, n_atom, n_dim)
200
+ # get id
201
+ idx = get_indx(val_data)
202
+ idx = idx if idx is not None else [f'Untitled{_}' for _ in range(n_s, n_s + len(batch_indx))]
203
+ n_s += len(batch_indx)
204
+ if self.VERBOSE > 0:
205
+ self.logger.info('*' * 89)
206
+ self.logger.info(f'Vibration Calculation Batch {n_c}.')
207
+ cell_str = np.array2string(
208
+ CELL, **FLOAT_ARRAY_FORMAT
209
+ ).replace("[", " ").replace("]", " ")
210
+ self.logger.info(f'Structure names: {idx}\n')
211
+ self.logger.info(f'Cell Vectors:\n{cell_str}\n')
212
+ # print Atoms Information
213
+ self.logout_element_information(element_list, batch_indx)
214
+
215
+ # run
216
+ with th.no_grad():
217
+ eig_freq, normal_mode = vib_calculator.normal_mode(
218
+ model_wrap.Energy,
219
+ X_init.squeeze(0), # input format: (n_atom, 3) instead of (1, n_atom, 3) like structure opt. or MD.
220
+ masses.squeeze(0),
221
+ func_args=(val_data,),
222
+ grad_func=model_wrap.Grad,
223
+ grad_func_args=(val_data,),
224
+ fixed_atom_tensor=fixed_mask.squeeze(0),
225
+ save_hessian=self.is_save_Hessian
226
+ )
227
+ if self.is_save_Hessian:
228
+ vib_hessian = vib_calculator.hessian
229
+ # Print info
230
+ eig_freq_THz = eig_freq * 15.63330456 # THz
231
+ eig_freq_cm1 = eig_freq * 521.47091 # cm^-1
232
+ if self.VERBOSE > 0:
233
+ self.logger.info(f'Eigen Vibration Frequency (cm^-1):\n{
234
+ np.array2string(eig_freq_cm1.numpy(force=True), **REVISED_FLOAT_ARRAY_FORMAT).replace("[", " ").replace("]", " ")
235
+ }')
236
+ self.logger.info(f'\nNormal Mode:\n{
237
+ np.array2string(normal_mode.numpy(force=True), **FLOAT_ARRAY_FORMAT).replace("[", " ").replace("]", " ")
238
+ }\n')
239
+ if self.is_save_Hessian:
240
+ self.logger.info(f'Hessian Matrix:\n{
241
+ np.array2string(vib_hessian.numpy(force=True), **FLOAT_ARRAY_FORMAT).replace("[", " ").replace("]", " ")
242
+ }\n')
243
+ self.logger.info('-' * 100)
244
+ if self.SAVE_PREDICTIONS:
245
+ freq_dict[idx] = eig_freq
246
+ normal_mode_dict[idx] = normal_mode
247
+ if self.is_save_Hessian:
248
+ hessian_dict[idx] = vib_hessian
249
+ n_c += 1
250
+
251
+ except Exception as e:
252
+ self.logger.warning(f'WARNING: An error occurred in {n_c}th batch. Error: {e}.')
253
+ if self.VERBOSE > 1:
254
+ excp = traceback.format_exc()
255
+ self.logger.warning(f"Traceback:\n{excp}")
256
+ n_c += 1
257
+
258
+ if self.VERBOSE: self.logger.info(f'VIBRATION CALCULATION DONE. Total Time: {time.perf_counter() - time_tol:<.4f}')
259
+ if self.SAVE_PREDICTIONS:
260
+ t_save = time.perf_counter()
261
+ with _LoggingEnd(self.log_handler):
262
+ if self.VERBOSE: self.logger.info(f'SAVING RESULTS...')
263
+ info_dict = {'Frequencies': freq_dict, 'Forces': normal_mode_dict,}
264
+ if self.is_save_Hessian:
265
+ info_dict['Hessian'] = vib_hessian
266
+ th.save(info_dict, self.PREDICTIONS_SAVE_FILE)
267
+ if self.VERBOSE: self.logger.info(f'Done. Saving Time: {time.perf_counter() - t_save:<.4f}')
268
+
269
+ except Exception as e:
270
+ th.cuda.synchronize()
271
+ excp = traceback.format_exc()
272
+ self.logger.exception(f'An ERROR occurred:\n\t{e}\nTraceback:\n{excp}')
273
+
274
+ finally:
275
+ th.cuda.synchronize()
276
+ self.logger.removeHandler(self.log_handler)
277
+ if isinstance(self.log_handler, logging.FileHandler):
278
+ self.log_handler.close()
279
+ pass
@@ -0,0 +1,38 @@
1
+ # Copyright (c) 2024-2025.7.4, BUCToolkit.
2
+ # Authors: Pu Pengxin, Song Xin
3
+ # Version: 0.9a
4
+ # File: __init__.py
5
+ # Environment: Python 3.12
6
+
7
+ from .Trainer import Trainer
8
+ from .Predictor import Predictor
9
+ from .MolecularDynamics import MolecularDynamics
10
+ from .ConstrainedMolecularDynamics import ConstrainedMolecularDynamics
11
+ from .StructureOptimization import StructureOptimization
12
+ from .VibrationAnalysis import VibrationAnalysis
13
+ from .NEB import ClimbingImageNudgedElasticBand
14
+ from .MonteCarlo import MonteCarlo
15
+ from . import Losses
16
+ from . import DataLoaders
17
+ from . import Metrics
18
+ from . import ModelOptims
19
+
20
+ CINEB = ClimbingImageNudgedElasticBand
21
+ CONSTR_MD = ConstrainedMolecularDynamics
22
+
23
+ __all__ = [
24
+ 'Trainer',
25
+ 'Predictor',
26
+ 'StructureOptimization',
27
+ 'ClimbingImageNudgedElasticBand',
28
+ 'CINEB',
29
+ 'MolecularDynamics',
30
+ 'ConstrainedMolecularDynamics',
31
+ 'CONSTR_MD',
32
+ 'MonteCarlo',
33
+ 'VibrationAnalysis',
34
+ 'Losses',
35
+ 'DataLoaders',
36
+ 'Metrics',
37
+ 'ModelOptims',
38
+ ]