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,282 @@
1
+ # Copyright (c) 2024-2025.7.4, BUCToolkit.
2
+ # Authors: Pu Pengxin, Song Xin
3
+ # Version: 0.9a
4
+ # File: MolecularDynamics.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 Dict, Any, Literal
12
+
13
+ import numpy as np
14
+ import torch as th
15
+ from torch import nn
16
+
17
+ from BUCToolkit.BatchMD import NVE, NVT
18
+ from ._io import _CONFIGS, _LoggingEnd, _Model_Wrapper_pyg, _Model_Wrapper_dgl
19
+ from BUCToolkit.utils._print_formatter import FLOAT_ARRAY_FORMAT
20
+ from BUCToolkit.BatchMD._BaseMD import _BaseMD
21
+
22
+
23
+ class MolecularDynamics(_CONFIGS):
24
+ """
25
+ Class of molecular dynamics simulation.
26
+ Users need to set the dataset and dataloader manually.
27
+
28
+ Args:
29
+ config_file: path to input file.
30
+ data_type: graph data type. 'pyg' for torch-geometric BatchData, 'dgl' for dgl DGLGraph.
31
+
32
+ Input file parameters: (Under the section `MD` in input files)
33
+ ENSEMBLE: Literal[NVE, NVT], the ensemble for MD.
34
+ THERMOSTAT: Literal[Langevin, VR, CSVR, Nose-Hoover], the thermostat type. only used for ENSEMBLE=NVT.
35
+ 'VR' is Velocity Rescaling and 'CSVR' is Canonical Sampling Velocity Rescaling by Bussi et al. [1].
36
+ THERMOSTAT_CONFIG: Dict, the configs of thermostat.
37
+ * For 'Langevin', option key 'damping_coeff' (fs^-1) is to control the damping coefficient. Large damping_coeff lead to a strong coupling. Default: 0.01
38
+ * For 'CSVR', option key 'time_const' (fs) is to control the characteristic timescale. Large time_const leads to a weak coupling. Default: 10*TIME_STEP
39
+ TIME_STEP: float, the time step (fs) for MD. Default: 1
40
+ MAX_STEP: int, total time (fs) = TIME_STEP * MAX_STEP
41
+ T_INIT: float, initial Temperature (K). Default: 298.15
42
+ * For ENSEMBLE=NVE, T_INIT is only used to generate ramdom initial velocities by Boltzmann dist if V_init was not given.
43
+ * For ENSEMBLE=NVT, T_INIT is the target temperature of thermostat.
44
+ OUTPUT_COORDS_PER_STEP: int, to control the frequency of outputting atom coordinates. If verbose = 3, atom velocities would also be outputted. Default: 1
45
+
46
+ References:
47
+ [1] J. Chem. Phys., 2007, 126, 014101.
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ config_file: str,
53
+ data_type: Literal['pyg', 'dgl'] = 'pyg',
54
+ ) -> None:
55
+ super().__init__(config_file)
56
+
57
+ self.config_file = config_file
58
+ assert data_type in {'pyg', 'dgl'}, f'Invalid data type {data_type}. It must be "pyg" or "dgl".'
59
+ self.data_type = data_type
60
+ self.reload_config(config_file)
61
+ if self.VERBOSE: self.logger.info('Config File Was Successfully Read.')
62
+ self.param = None
63
+ self._has_load_data = False
64
+ self._data_loader = None
65
+
66
+ __ensembles = {'NVE': NVE, 'NVT': NVT}
67
+ if self.MD is None:
68
+ raise RuntimeError('Molecular Dynamics Configs was NOT Set.')
69
+ self.MD: Dict
70
+
71
+ try:
72
+ self.MDType = __ensembles[self.MD['ENSEMBLE']] # The Main Function of MD. MD_config is its parameters.
73
+ except KeyError:
74
+ raise NotImplementedError(f'Unknown Ensemble {self.MD["ENSEMBLE"]}.')
75
+ self.require_grad = self.MD.get('REQUIRE_GRAD', False)
76
+ self.MD_config = {
77
+ 'time_step': self.MD.get('TIME_STEP', 1),
78
+ 'max_step': self.MD.get('MAX_STEP'),
79
+ 'T_init': self.MD.get('T_INIT', 298.15),
80
+ 'output_structures_per_step': self.MD.get('OUTPUT_COORDS_PER_STEP', 1),
81
+ 'device': self.DEVICE,
82
+ 'verbose': self.VERBOSE
83
+ }
84
+ if self.SAVE_PREDICTIONS:
85
+ self.MD_config['output_file'] = self.PREDICTIONS_SAVE_FILE
86
+ if self.MD['ENSEMBLE'] == 'NVT':
87
+ self.MD_config['thermostat'] = self.MD.get('THERMOSTAT', 'CSVR')
88
+ self.MD_config['thermostat_config'] = self.MD.get('THERMOSTAT_CONFIG', dict())
89
+
90
+ def run(self, model):
91
+ """
92
+ Parameters:
93
+ model: the input model which is non-instantiated nn.Module class.
94
+ """
95
+ # check logger
96
+ if not self.logger.hasHandlers(): self.logger.addHandler(self.log_handler)
97
+ # check vars
98
+ _model: nn.Module = model(**self.MODEL_CONFIG)
99
+ if (self.START == 'resume') or (self.START == 1) or (self.START == 2):
100
+ chk_data = th.load(self.LOAD_CHK_FILE_PATH, weights_only=True)
101
+ if self.param is None:
102
+ _model.load_state_dict(chk_data['model_state_dict'], strict=self.STRICT_LOAD)
103
+ else:
104
+ _model.load_state_dict(self.param, self.is_strict, self.is_assign)
105
+ elif self.START == 'from_scratch' or self.START == 0:
106
+ self.logger.warning(
107
+ 'WARNING: The model does not read the trained parameters from checkpoint file. I HOPE YOU KNOW WHAT YOU ARE DOING!'
108
+ )
109
+ if self.param is not None:
110
+ _model.load_state_dict(self.param, self.is_strict, self.is_assign)
111
+ else:
112
+ raise ValueError('Invalid START value. It should be "from_scratch" / 0 or "resume" / 1 ')
113
+ # model vars
114
+ _model = _model.to(self.DEVICE)
115
+
116
+ # preprocessing data # TODO
117
+ if self._data_loader is None: raise RuntimeError('Please Set the DataLoader.')
118
+ if not self._has_load_data: raise RuntimeError('Please Set the Data to Predict.')
119
+
120
+ # initialize
121
+ self.n_samp = len(self.TRAIN_DATA['data']) # sample number
122
+ self.n_batch = math.ceil(self.n_samp / self.BATCH_SIZE) # total batch number per epoch
123
+
124
+ mole_dynam = None
125
+ try:
126
+ # I/O
127
+ if self.VERBOSE > 0:
128
+ self.logout_task_information(_model, 'MD', self.MD_config, self.n_samp)
129
+
130
+ time_tol = time.perf_counter()
131
+ _model.eval()
132
+ # MAIN LOOP
133
+ # define the model wrapper & batch size getter & cell vector getter for different data type
134
+ if self.data_type == 'pyg':
135
+ model_wrap = _Model_Wrapper_pyg(_model)
136
+ def get_batch_size(data):
137
+ return len(data)
138
+
139
+ def get_cell_vec(data):
140
+ return data.cell.numpy(force=True)
141
+
142
+ def get_atomic_number(data):
143
+ return data.atomic_numbers.unsqueeze(0).tolist()
144
+
145
+ def get_fixed_mask(data):
146
+ mask = getattr(data, 'fixed', None)
147
+ if mask is not None:
148
+ mask = mask.unsqueeze(0)
149
+ return mask
150
+
151
+ def get_batch_indx(data):
152
+ return [len(dat.pos) for dat in data.to_data_list()]
153
+
154
+ def get_init_veloc(data):
155
+ veloc = getattr(data, 'velocity', None)
156
+ if veloc is not None:
157
+ veloc = veloc.unsqueeze(0)
158
+ return veloc
159
+
160
+ def get_indx(data):
161
+ _indx: Dict = getattr(data, 'idx', None)
162
+ return _indx
163
+
164
+ else:
165
+ model_wrap = _Model_Wrapper_dgl(_model)
166
+ def get_batch_size(data):
167
+ return data.num_nodes('atom')
168
+
169
+ def get_cell_vec(data):
170
+ return data.nodes['cell'].data['cell'].numpy(force=True)
171
+
172
+ def get_atomic_number(data):
173
+ return data.nodes['atom'].data['Z'].unsqueeze(0).tolist()
174
+
175
+ def get_fixed_mask(data):
176
+ mask = data.nodes['atom'].data.get('fix', None)
177
+ if mask is not None:
178
+ mask = mask.unsqueeze(0)
179
+ return mask
180
+
181
+ def get_batch_indx(data):
182
+ return data.batch_num_nodes('atom')
183
+
184
+ def get_init_veloc(data):
185
+ veloc = data.nodes['atom'].data.get('velocity', None)
186
+ if veloc is not None:
187
+ veloc = veloc.unsqueeze(0)
188
+ return veloc
189
+
190
+ def get_indx(data):
191
+ _indx: Dict = data.nodes['atom'].data
192
+ return _indx.get('idx', None)
193
+
194
+ mole_dynam = self.MDType(**self.MD_config)
195
+ #if self.REDIRECT:
196
+ # _file_handler = os.path.join(self.OUTPUT_PATH, f'{time.strftime("%Y%m%d_%H_%M_%S")}_{self.OUTPUT_POSTFIX}.out')
197
+ # mole_dynam.reset_logger_handler(_file_handler)
198
+ if self.SAVE_PREDICTIONS:
199
+ mole_dynam._HOLD_DUMPER = True
200
+ # change dumping mode to 'a' to contiguously dumper within the whole for-loop
201
+ mole_dynam.dumper.reset_args(self.PREDICTIONS_SAVE_FILE, mode='a', cache_size=4096, )
202
+ val_set: Any = self._data_loader(self.TRAIN_DATA, self.BATCH_SIZE, self.DEVICE, is_train=False, **self._data_loader_configs)
203
+ n_c = 1 # running batch now
204
+ n_s = 0 # number of calculated samples. each sample in batches in each for-loop += 1.
205
+ for val_data, val_label in val_set:
206
+ try:
207
+ # to avoid get an empty batch
208
+ if get_batch_size(val_data) <= 0:
209
+ if self.VERBOSE: self.logger.info(f'An empty batch occurred. Skipped.')
210
+ continue
211
+ # MD
212
+ # cells & fixations
213
+ _cell = get_cell_vec(val_data)
214
+ fixed_mask = get_fixed_mask(val_data)
215
+ # get batch
216
+ batch_indx = get_batch_indx(val_data)
217
+ # get id
218
+ idx = get_indx(val_data)
219
+ idx = idx if idx is not None else [f'Untitled{_}' for _ in range(n_s, n_s + len(batch_indx))]
220
+ n_s += len(batch_indx)
221
+ element_tensor = get_atomic_number(val_data)
222
+ if self.VERBOSE > 0:
223
+ self.logger.info('*' * 89)
224
+ self.logger.info(f'Running Batch {n_c}.')
225
+ cell_str = np.array2string(
226
+ _cell, **FLOAT_ARRAY_FORMAT
227
+ ).replace("[", " ").replace("]", " ")
228
+ self.logger.info(f'Structure names: {idx}\n')
229
+ self.logger.info(f'Cell Vectors:\n{cell_str}\n')
230
+ # print structures titles with elements
231
+ self.logout_element_information(element_tensor, batch_indx)
232
+
233
+ # initial atom coordinates
234
+ if self.data_type == 'pyg':
235
+ X_init = val_data.pos.unsqueeze(0)
236
+ else:
237
+ X_init = val_data.nodes['atom'].data['pos'].unsqueeze(0)
238
+
239
+ mv2cent_freq = int(self.MD.get('MOVE_TO_CENTER_FREQ', -1))
240
+ mole_dynam.run(
241
+ model_wrap.Energy,
242
+ X_init,
243
+ Element_list=get_atomic_number(val_data),
244
+ Cell_vector=_cell,
245
+ V_init=get_init_veloc(val_data), # user-defined initial velocities.
246
+ grad_func=model_wrap.Grad,
247
+ func_args=(val_data,),
248
+ grad_func_args=(val_data,),
249
+ is_grad_func_contain_y=False,
250
+ require_grad=self.require_grad,
251
+ batch_indices=batch_indx,
252
+ fixed_atom_tensor=fixed_mask, # The Selective Dynamics.
253
+ move_to_center_freq=mv2cent_freq
254
+ )
255
+
256
+ # Print info
257
+ if self.VERBOSE > 0:
258
+ self.logger.info(f'Batch {n_c} done.')
259
+ n_c += 1
260
+
261
+ except Exception as e:
262
+ self.logger.warning(f'WARNING: An error occurred in {n_c}th batch. Error: {e}.')
263
+ if self.VERBOSE > 0:
264
+ excp = traceback.format_exc()
265
+ self.logger.warning(f"Traceback:\n{excp}")
266
+ n_c += 1
267
+
268
+ if self.VERBOSE: self.logger.info(f'Molecular Dynamics Done. Total Time: {time.perf_counter() - time_tol:<.4f}')
269
+
270
+ except Exception as e:
271
+ th.cuda.synchronize()
272
+ excp = traceback.format_exc()
273
+ self.logger.exception(f'An ERROR occurred:\n\t{e}\nTraceback:\n{excp}')
274
+
275
+ finally:
276
+ th.cuda.synchronize()
277
+ if mole_dynam is not None:
278
+ mole_dynam.dumper.close()
279
+ self.logger.removeHandler(self.log_handler)
280
+ if isinstance(self.log_handler, logging.FileHandler):
281
+ self.log_handler.close()
282
+ pass
@@ -0,0 +1,280 @@
1
+ # Copyright (c) 2024-2025.7.4, BUCToolkit.
2
+ # Authors: Pu Pengxin, Song Xin
3
+ # Version: 0.9a
4
+ # File: MolecularDynamics.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 Dict, Any, Literal
12
+
13
+ import numpy as np
14
+ import torch as th
15
+ from torch import nn
16
+
17
+ from BUCToolkit.BatchMC import MMC
18
+ from ._io import _CONFIGS, _LoggingEnd, _Model_Wrapper_pyg, _Model_Wrapper_dgl
19
+ from BUCToolkit.utils._print_formatter import FLOAT_ARRAY_FORMAT
20
+
21
+
22
+ class MonteCarlo(_CONFIGS):
23
+ """
24
+ Class of Monte Carlo algorithms, including NVT g Monte Carlo & simulated annealing (temperature varied MC).
25
+ Users need to set the dataset and dataloader manually.
26
+
27
+ Args:
28
+ config_file: path to input file.
29
+ data_type: graph data type. 'pyg' for torch-geometric BatchData, 'dgl' for dgl DGLGraph.
30
+
31
+ Input file parameters: (Under the section `MC` in input files.)
32
+ ITER_SCHEME: Literal['Gaussian', 'Cauchy', 'Uniform'], the scheme of atoms moving. Default: Gaussian.
33
+ COORDINATE_UPDATE_PARAM: float, the scale parameter for coordinates update. variation for Gaussian/range for Uniform/scale for Cauchy.
34
+ MAXITER: int, the maximum iteration numbers. Default: 300.
35
+ TEMPERATURE_INIT: initial temperature.
36
+ TEMPERATURE_SCHEME: Literal['constant', 'linear', 'exponential', 'log', 'fast'], temperature update scheme.
37
+ * `constant` for a fixed temperature during the whole simulation.
38
+ * `linear` for a linearly changed temperature from `TEMPERATURE_INIT` to `TEMPERATURE_SCHEME_PARAM` during MAXITER steps.
39
+ * `exponential` for temperature (T) changing by T^(i + 1) = `TEMPERATURE_SCHEME_PARAM` * T^(i).
40
+ * `log` for temperature (T) changing by T^(i) = `TEMPERATURE_INIT`/(1. + log(1. + `TEMPERATURE_SCHEME_PARAM` * i)), `i` is the step number.
41
+ * `fast` for temperature (T) changing by T^(i) = `TEMPERATURE_INIT`/(1. + `TEMPERATURE_SCHEME_PARAM` * i), `i` is the step number.
42
+ TEMPERATURE_SCHEME_PARAM: float, to control the temperature scheme. See args `TEMPERATURE_SCHEME`.
43
+ TEMPERATURE_UPDATE_FREQ: update temperature per `TEMPERATURE_UPDATE_FREQ` step.
44
+ OUTPUT_COORDS_PER_STEP: int, to control the frequency of outputting atom coordinates. If verbose = 3, atom velocities would also be outputted. Default: 1
45
+
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ config_file: str,
51
+ data_type: Literal['pyg', 'dgl'] = 'pyg',
52
+ ) -> None:
53
+ super().__init__(config_file)
54
+
55
+ self.config_file = config_file
56
+ assert data_type in {'pyg', 'dgl'}, f'Invalid data type {data_type}. It must be "pyg" or "dgl".'
57
+ self.data_type = data_type
58
+ self.reload_config(config_file)
59
+ if self.VERBOSE: self.logger.info('Config File Was Successfully Read.')
60
+ self.param = None
61
+ self._has_load_data = False
62
+ self._data_loader = None
63
+
64
+ __ensembles = {'METROPOLIS': MMC, }
65
+ if self.MC is None:
66
+ raise RuntimeError('Monte Carlo Configs was NOT Set.')
67
+ self.MC: Dict
68
+
69
+ try:
70
+ self.MCType = __ensembles[self.MC['TYPE'].upper()]
71
+ except KeyError:
72
+ raise NotImplementedError(f'Type {self.MC['TYPE']} is not supported yet.')
73
+ self.MC_config = {
74
+ 'maxiter': int(self.MC.get('MAXITER', 10000)),
75
+ 'output_structures_per_step': int(self.MC.get('OUTPUT_COORDS_PER_STEP', 1)),
76
+ 'device': self.DEVICE,
77
+ 'verbose': self.VERBOSE
78
+ }
79
+ if self.REDIRECT:
80
+ self.MC_config['output_file'] = self.PREDICTIONS_SAVE_FILE
81
+ if self.MC['TYPE'].upper() == 'METROPOLIS':
82
+ self.MC_config.update(
83
+ {
84
+ 'iter_scheme': str(self.MC.get('ITER_SCHEME', 'Gaussian')),
85
+ 'temperature_init': float(self.MC.get('T_INIT', 298.15)),
86
+ 'temperature_scheme': str(self.MC.get('T_SCHEME', 'constant')),
87
+ 'temperature_update_freq': int(self.MC.get('T_UPDATE_FREQ', 1)),
88
+ 'temperature_scheme_param': float(self.MC.get('T_SCHEME_PARAM', None)),
89
+ 'coordinate_update_param': float(self.MC.get('COORDINATE_UPDATE_PARAM', 0.2)),
90
+ }
91
+ )
92
+
93
+ def run(self, model):
94
+ """
95
+ Parameters:
96
+ model: the input model which is non-instantiated nn.Module class.
97
+ """
98
+ # check logger
99
+ if not self.logger.hasHandlers(): self.logger.addHandler(self.log_handler)
100
+ # check vars
101
+ _model: nn.Module = model(**self.MODEL_CONFIG)
102
+ if (self.START == 'resume') or (self.START == 1) or (self.START == 2):
103
+ chk_data = th.load(self.LOAD_CHK_FILE_PATH, weights_only=True)
104
+ if self.param is None:
105
+ _model.load_state_dict(chk_data['model_state_dict'], strict=self.STRICT_LOAD)
106
+ else:
107
+ _model.load_state_dict(self.param, self.is_strict, self.is_assign)
108
+ elif self.START == 'from_scratch' or self.START == 0:
109
+ self.logger.warning(
110
+ 'WARNING: The model does not read the trained parameters from checkpoint file. I HOPE YOU KNOW WHAT YOU ARE DOING!'
111
+ )
112
+ if self.param is not None:
113
+ _model.load_state_dict(self.param, self.is_strict, self.is_assign)
114
+ else:
115
+ raise ValueError('Invalid START value. It should be "from_scratch" / 0 or "resume" / 1 ')
116
+ # model vars
117
+ _model = _model.to(self.DEVICE)
118
+
119
+ # preprocessing data # TODO
120
+ if self._data_loader is None: raise RuntimeError('Please Set the DataLoader.')
121
+ if not self._has_load_data: raise RuntimeError('Please Set the Data to Predict.')
122
+
123
+ # initialize
124
+ self.n_samp = len(self.TRAIN_DATA['data']) # sample number
125
+ self.n_batch = math.ceil(self.n_samp / self.BATCH_SIZE) # total batch number per epoch
126
+
127
+ mont_carlo = None
128
+ try:
129
+ # I/O
130
+ if self.VERBOSE > 0:
131
+ self.logout_task_information(_model, 'MC', self.MC_config, self.n_samp)
132
+
133
+ time_tol = time.perf_counter()
134
+ _model.eval()
135
+ # MAIN LOOP
136
+ # define the model wrapper & batch size getter & cell vector getter for different data type
137
+ if self.data_type == 'pyg':
138
+ model_wrap = _Model_Wrapper_pyg(_model)
139
+ def get_batch_size(data):
140
+ return len(data)
141
+
142
+ def get_cell_vec(data):
143
+ return data.cell.numpy(force=True)
144
+
145
+ def get_atomic_number(data):
146
+ return data.atomic_numbers.unsqueeze(0).tolist()
147
+
148
+ def get_fixed_mask(data):
149
+ mask = getattr(data, 'fixed', None)
150
+ if mask is not None:
151
+ mask = mask.unsqueeze(0)
152
+ return mask
153
+
154
+ def get_batch_indx(data):
155
+ return [len(dat.pos) for dat in data.to_data_list()]
156
+
157
+ def get_init_veloc(data):
158
+ veloc = getattr(data, 'velocity', None)
159
+ if veloc is not None:
160
+ veloc = veloc.unsqueeze(0)
161
+ return veloc
162
+
163
+ def get_indx(data):
164
+ _indx: Dict = getattr(data, 'idx', None)
165
+ return _indx
166
+
167
+ else:
168
+ model_wrap = _Model_Wrapper_dgl(_model)
169
+ def get_batch_size(data):
170
+ return data.num_nodes('atom')
171
+
172
+ def get_cell_vec(data):
173
+ return data.nodes['cell'].data['cell'].numpy(force=True)
174
+
175
+ def get_atomic_number(data):
176
+ return data.nodes['atom'].data['Z'].unsqueeze(0).tolist()
177
+
178
+ def get_fixed_mask(data):
179
+ mask = data.nodes['atom'].data.get('fix', None)
180
+ if mask is not None:
181
+ mask = mask.unsqueeze(0)
182
+ return mask
183
+
184
+ def get_batch_indx(data):
185
+ return data.batch_num_nodes('atom')
186
+
187
+ def get_init_veloc(data):
188
+ veloc = data.nodes['atom'].data.get('velocity', None)
189
+ if veloc is not None:
190
+ veloc = veloc.unsqueeze(0)
191
+ return veloc
192
+
193
+ def get_indx(data):
194
+ _indx: Dict = data.nodes['atom'].data
195
+ return _indx.get('idx', None)
196
+
197
+ mont_carlo = self.MCType(**self.MC_config)
198
+ #if self.REDIRECT:
199
+ # _file_handler = os.path.join(self.OUTPUT_PATH, f'{time.strftime("%Y%m%d_%H_%M_%S")}_{self.OUTPUT_POSTFIX}.out')
200
+ # mont_carlo.reset_logger_handler(_file_handler)
201
+ if self.SAVE_PREDICTIONS:
202
+ mont_carlo._HOLD_DUMPER = True
203
+ # change dumping mode to 'a' to contiguously dumper within the whole for-loop
204
+ mont_carlo.dumper.reset_args(self.PREDICTIONS_SAVE_FILE, mode='a', cache_size=4096, )
205
+ val_set: Any = self._data_loader(self.TRAIN_DATA, self.BATCH_SIZE, self.DEVICE, is_train=False, **self._data_loader_configs)
206
+ n_c = 1 # running batch now
207
+ n_s = 0 # number of calculated samples. each sample in batches in each for-loop += 1.
208
+ for val_data, val_label in val_set:
209
+ try:
210
+ # to avoid get an empty batch
211
+ if get_batch_size(val_data) <= 0:
212
+ if self.VERBOSE: self.logger.info(f'An empty batch occurred. Skipped.')
213
+ continue
214
+ # MCc
215
+ # cells & fixations
216
+ _cell = get_cell_vec(val_data)
217
+ fixed_mask = get_fixed_mask(val_data)
218
+ # get batch
219
+ batch_indx = get_batch_indx(val_data)
220
+ # get id
221
+ idx = get_indx(val_data)
222
+ idx = idx if idx is not None else [f'Untitled{_}' for _ in range(n_s, n_s + len(batch_indx))]
223
+ n_s += len(batch_indx)
224
+ element_tensor = get_atomic_number(val_data)
225
+ if self.VERBOSE > 0:
226
+ self.logger.info('*' * 89)
227
+ self.logger.info(f'Running Batch {n_c}.')
228
+ cell_str = np.array2string(
229
+ _cell, **FLOAT_ARRAY_FORMAT
230
+ ).replace("[", " ").replace("]", " ")
231
+ self.logger.info(f'Structure names: {idx}\n')
232
+ self.logger.info(f'Cell Vectors:\n{cell_str}\n')
233
+ # print structures titles with elements
234
+ self.logout_element_information(element_tensor, batch_indx)
235
+
236
+ # initial atom coordinates
237
+ if self.data_type == 'pyg':
238
+ X_init = val_data.pos.unsqueeze(0)
239
+ else:
240
+ X_init = val_data.nodes['atom'].data['pos'].unsqueeze(0)
241
+
242
+ mv2cent_freq = int(self.MC.get('MOVE_TO_CENTER_FREQ', -1))
243
+ mont_carlo.run(
244
+ model_wrap.Energy,
245
+ X_init,
246
+ Element_list=get_atomic_number(val_data),
247
+ Cell_vector=_cell,
248
+ func_args=(val_data,),
249
+ fixed_atom_tensor=fixed_mask,
250
+ batch_indices=batch_indx,
251
+ move_to_center_freq=mv2cent_freq
252
+ )
253
+
254
+ # Print info
255
+ if self.VERBOSE > 0:
256
+ self.logger.info(f'Batch {n_c} done.')
257
+ n_c += 1
258
+
259
+ except Exception as e:
260
+ self.logger.warning(f'WARNING: An error occurred in {n_c}th batch. Error: {e}.')
261
+ if self.VERBOSE > 0:
262
+ excp = traceback.format_exc()
263
+ self.logger.warning(f"Traceback:\n{excp}")
264
+ n_c += 1
265
+
266
+ if self.VERBOSE: self.logger.info(f'Monte Carlo Simulation Done. Total Time: {time.perf_counter() - time_tol:<.4f}')
267
+
268
+ except Exception as e:
269
+ th.cuda.synchronize()
270
+ excp = traceback.format_exc()
271
+ self.logger.exception(f'An ERROR occurred:\n\t{e}\nTraceback:\n{excp}')
272
+
273
+ finally:
274
+ th.cuda.synchronize()
275
+ if mont_carlo is not None:
276
+ mont_carlo.dumper.close()
277
+ self.logger.removeHandler(self.log_handler)
278
+ if isinstance(self.log_handler, logging.FileHandler):
279
+ self.log_handler.close()
280
+ pass