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,140 @@
1
+ # Copyright (c) 2026.4.1, BUCToolkit.
2
+ # Authors: Pu Pengxin, Song Xin
3
+ # Version: 1.0b
4
+ # File: input_stub.py
5
+ # Environment: Python 3.12
6
+ from typing import Any
7
+
8
+ # The parameter dictionary in the format:
9
+ # key: (value, type, description)
10
+ CONFIG_STUB = {
11
+ "TASK": ("MD", str, "task name. Options: 'OPT', 'TS', 'VIB', 'NEB', 'MD', 'CMD', 'MC'"),
12
+ "START": (1, int, "0: from scratch; 1: load checkpoint from LOAD_CHK_FILE_PATH; 2: only load model parameters/weights"),
13
+ "VERBOSE": (1, int, "verbosity level for log output"),
14
+ "DEVICE": ("cuda:0", str, "the device on which the task would run"),
15
+ "BATCH_SIZE": (16, int, "the batch size of input data during calculation"),
16
+
17
+ # I/O configs
18
+ "LOAD_CHK_FILE_PATH": ("your/model/checkpoint/file/path", str, "path of model checkpoint to load"),
19
+ "OUTPUT_PATH": ("your/log/output/path", str, "path to save logs"),
20
+ "OUTPUT_POSTFIX": ("your_logfile_suffix", str, "suffix for log file name"),
21
+ "PREDICTIONS_SAVE_FILE": ("your/model/predictions/save/path", str, "path of saving predictions"),
22
+ "STRICT_LOAD": (True, bool, "whether to strictly load model parameter"),
23
+ "REDIRECT": (True, bool, "whether output training logs to OUTPUT_PATH or directly print on screen"),
24
+ "SAVE_PREDICTIONS": (True, bool, "only for predictions. Whether output predictions to a dump file"),
25
+ "DATA_TYPE": ("BS", str, "Data type. Options: 'POSCAR', 'OUTCAR', 'CIF', 'ASE_TRAJ', 'BS', 'OPT', 'MD'"),
26
+ "DATA_PATH": ("/your/data/path", str, "path of data used for calculation. If training, it is viewed as the training set"),
27
+ "DATA_NAME_SELECTOR": (".*$", str, "regular expression to select data names. Only matched names will be loaded"),
28
+ "FSDATA_PATH": ("your/final/state/data/path", str, "path for final state data, used for calculations requiring both initial and final states, e.g., CI-NEB"),
29
+ "DISPDATA_PATH": ("your/displacement/data/path", str, "path for displacement data, used for calculations requiring initial guess of a direction, e.g., Dimer"),
30
+ "VAL_SET_PATH": ("your/validation/set/path", str, "path for validation set, used for training that requires validation data"),
31
+ "VAL_SPLIT_RATIO": (0.1, float, "ratio of validation set in the total dataset. Ignored if VAL_SET_PATH is provided"),
32
+ "DATA_LOADER_KWARGS": ({}, dict, "other keyword arguments for data loader"),
33
+ "IS_SHUFFLE": (False, bool, "whether to randomly shuffle dataset before calculating"),
34
+
35
+ # Training
36
+ "TRAIN": {
37
+ "EPOCH": (10, int, "number of training epochs"),
38
+ "VAL_BATCH_SIZE": (20, int, "batch size for validation. default is the same as BATCH_SIZE"),
39
+ "VAL_PER_STEP": (100, int, "validate every VAL_PER_STEP steps. step = BATCH_SIZE * ACCUMULATE_STEP"),
40
+ "VAL_IF_TRN_LOSS_BELOW": (1e5, float, "only validating after training loss < VAL_IF_TRN_LOSS_BELOW"),
41
+ "ACCUMULATE_STEP": (12, int, "gradient accumulation steps"),
42
+ "LOSS": ("Energy_Loss", str, "loss function name. Options: 'MSE', 'MAE', 'Hubber', 'CrossEntropy', 'Energy_Force_Loss', 'Energy_Loss'"),
43
+ "LOSS_CONFIG": {
44
+ "loss_E": ("SmoothMAE", str, "loss function for energy")
45
+ },
46
+ "METRICS": (["E_MAE", "E_R2"], list, "list of metrics to compute. Options: E_MAE, F_MAE, F_MaxE, E_R2, MSE, MAE, R2, RMSE"),
47
+ "METRICS_CONFIG": ({}, dict, "other keyword arguments for metrics"),
48
+ "OPTIM": ("AdamW", str, "optimizer name. Options: 'Adam', 'SGD', 'AdamW', 'Adadelta', 'Adagrad', 'ASGD', 'Adamax', 'FIRE'"),
49
+ "OPTIM_CONFIG": {
50
+ "lr": (2e-4, float, "learning rate")
51
+ },
52
+ "LAYERWISE_OPTIM_CONFIG": {
53
+ "force_block.*": ({"lr": 5e-4}, dict, "learning rate for layers matching regex 'force_block.*'"),
54
+ "energy_block.*": ({"lr": 2e-4}, dict, "learning rate for layers matching regex 'energy_block.*'"),
55
+ ".*_bias_layer.*": ({"lr": 2e-4}, dict, "learning rate for layers matching regex '.*_bias_layer.*'")
56
+ },
57
+ "GRAD_CLIP": (True, bool, "whether to toggle on gradient clip"),
58
+ "GRAD_CLIP_MAX_NORM": (10.0, float, "maximum grad. norm to clip"),
59
+ "GRAD_CLIP_CONFIG": ({}, dict, "other kwargs for torch.nn.utils.clip_grad_norm_"),
60
+ "LR_SCHEDULER": (None, str, "learning rate scheduler name. Options: 'StepLR', 'ExponentialLR', 'ChainedScheduler', 'ConstantLR', 'LambdaLR', 'LinearLR', 'CosineAnnealingWarmRestarts', 'CyclicLR', 'MultiStepLR', 'CosineAnnealingLR', None"),
61
+ "LR_SCHEDULER_CONFIG": ({}, dict, "keyword arguments for the learning rate scheduler"),
62
+ "EMA": (False, bool, "whether to toggle on EMA (Exponential Moving Average)"),
63
+ "EMA_DECAY": (0.999, float, "EMA decay rate"),
64
+ },
65
+
66
+ # Relaxation
67
+ "RELAXATION": {
68
+ "ALGO": ("FIRE", str, "optimization algorithm. Options: CG, BFGS, FIRE"),
69
+ "ITER_SCHEME": ("PR+", str, "conjugate gradient iteration scheme. Only for ALGO=CG. Options: 'PR+', 'FR', 'PR', 'WYL'"),
70
+ "E_THRES": (1e4, float, "threshold of energy difference for convergence"),
71
+ "F_THRES": (0.05, float, "threshold of max force for convergence"),
72
+ "MAXITER": (300, int, "maximum number of iterations"),
73
+ "STEPLENGTH": (0.5, float, "initial step length"),
74
+ "USE_BB": (True, bool, "whether to use Barzilai-Borwein I steplength as initial steplength"),
75
+ "LINESEARCH": ("B", str, "line search method. 'Backtrack' with Armijo's condition, 'Wolfe' for weak Wolfe condition, 'Exact' for exact linear search"),
76
+ "LINESEARCH_MAXITER": (8, int, "maximum iterations of line search per outer iteration"),
77
+ "LINESEARCH_THRES": (0.02, float, "threshold for exact line search"),
78
+ "LINESEARCH_FACTOR": (0.5, float, "shrinkage factor for backtracking"),
79
+ "REQUIRE_GRAD": (False, bool, "whether to toggle on auto-gradient during calculation"),
80
+ },
81
+
82
+ # Transition state
83
+ "TRANSITION_STATE": {
84
+ "ALGO": ("DIMER", str, "algorithm for transition state search. Options: DIMER"),
85
+ "X_DIFF_ATTR": ("x_dimer", str, "attribute name of initial dimer direction"),
86
+ "E_THRES": (1e-4, float, "energy difference threshold for convergence"),
87
+ "TORQ_THRES": (1e-2, float, "torque threshold during rotation process, i.e., residuals of eigen vector"),
88
+ "F_THRES": (5e-2, float, "force threshold for convergence"),
89
+ "MAXITER_TRANS": (300, int, "maximum number of translation steps"),
90
+ "MAXITER_ROT": (5, int, "maximum number of rotation steps"),
91
+ "MAX_STEPLENGTH": (0.5, float, "maximum step length"),
92
+ "DX": (0.1, float, "finite difference length for Hessian-vector product"),
93
+ "REQUIRE_GRAD": (False, bool, "whether to toggle on auto-gradient during calculation"),
94
+ },
95
+
96
+ # Vibration analyses
97
+ "VIBRATION": {
98
+ "METHOD": ("Coord", str, "method for vibration analysis. 'Coord' for finite difference, 'Grad' for auto-grad"),
99
+ "BLOCK_SIZE": (1, int, "block size for tensor/vectorize parallelization"),
100
+ "DELTA": (0.01, float, "finite difference length for Hessian-vector product"),
101
+ },
102
+
103
+ # NEB transition state
104
+ "NEB": {
105
+ "ALGO": ("CI-NEB", str, "NEB algorithm. Options: CI-NEB"),
106
+ "N_IMAGES": (7, int, "number of images for CI-NEB calculation"),
107
+ "SPRING_CONST": (5.0, float, "spring constant for NEB"),
108
+ "OPTIMIZER": ("FIRE", str, "optimizer for CI-NEB. Currently only FIRE supported"),
109
+ "STEPLENGTH": (0.2, float, "initial step length for optimizer"),
110
+ "E_THRESHOLD": (1e-3, float, "energy difference threshold for convergence"),
111
+ "F_THRESHOLD": (0.05, float, "force threshold for convergence"),
112
+ "MAXITER": (20, int, "maximum number of iterations"),
113
+ "REQUIRE_GRAD": (False, bool, "whether to toggle on auto-gradient during calculation"),
114
+ },
115
+
116
+ # Molecular dynamics
117
+ "MD": {
118
+ "ENSEMBLE": ("NVT", str, "MD ensemble. Options: NVE, NVT"),
119
+ "THERMOSTAT": ("CSVR", str, "thermostat for NVT ensemble. Options: Langevin, VR, Nose-Hoover, CSVR"),
120
+ "THERMOSTAT_CONFIG": {
121
+ "DAMPING_COEFF": (0.01, float, "damping coefficient for Langevin thermostat. Unit: fs^-1"),
122
+ "TIME_CONST": (120, float, "time constant for CSVR thermostat. Unit: fs"),
123
+ },
124
+ "TIME_STEP": (1, float, "MD time step. Unit: fs"),
125
+ "MAX_STEP": (100, int, "total number of MD steps. Total time = TIME_STEP * MAX_STEP"),
126
+ "T_INIT": (298.15, float, "initial temperature. Unit: K. For NVE, used to generate random initial velocities"),
127
+ "OUTPUT_COORDS_PER_STEP": (1, int, "frequency of outputting atom coordinates. If verbose=3, velocities also output"),
128
+ "CONSTRAINTS_FILE": ("./constraints.py", str, "path to constraints function file"),
129
+ "CONSTRAINTS_FUNC": ("func", str, "name of constraints function in CONSTRAINTS_FILE"),
130
+ "REQUIRE_GRAD": (False, bool, "whether to toggle on auto-gradient during calculation"),
131
+ },
132
+
133
+ # Model configs
134
+ "MODEL_FILE": ("your/model/file/path/template_model.py", str, "path to model definition file"),
135
+ "MODEL_NAME": ("YourModel", str, "name of the model class in MODEL_FILE"),
136
+ "MODEL_CONFIG": {
137
+ "hyperparameter1": ("xxx", Any, "Model hyperparameter"),
138
+ "hyperparameter2": ("xxx", Any, "Model hyperparameter"),
139
+ },
140
+ }
BUCToolkit/cli/main.py ADDED
@@ -0,0 +1,368 @@
1
+ """
2
+ The Traditional Style Main Function/Program that runs tasks by a single command line with
3
+ input files and args.
4
+ """
5
+ # Copyright (c) 2026.3.27, BUCToolkit.
6
+ # Authors: Pu Pengxin, Song Xin
7
+ # Version: 0.9a
8
+ # File: main.py
9
+ # Environment: Python 3.12
10
+
11
+ import sys
12
+ import argparse
13
+ import time
14
+ import os
15
+ import warnings
16
+ from typing import Dict, Any, Callable
17
+ import importlib.util
18
+ import hashlib
19
+
20
+ import yaml
21
+ import torch as th
22
+
23
+ import BUCToolkit as bt
24
+ from BUCToolkit.BatchGenerate.coords_interp import direction_for_finite_diff
25
+ from BUCToolkit.cli.print_logo import generate_display_art
26
+ import BUCToolkit.api as api
27
+ from BUCToolkit.api.DataLoaders import PyGDataLoader, ISFSPyGDataLoader
28
+ import BUCToolkit.Preprocessing.load_files as load_files
29
+
30
+
31
+ def parse_center_input_file(path: str):
32
+ """
33
+ Parse the input file with the highest level that control the overall task.
34
+ Args:
35
+ path: path of the input file.
36
+
37
+ Returns:
38
+ runner: the api of the task. Use `runner.run(model)` to run the task.
39
+
40
+ """
41
+ if not os.path.isfile(path):
42
+ raise FileNotFoundError(path)
43
+
44
+ with open(path, 'r') as f:
45
+ config: Dict[str, Any] = yaml.safe_load(f)
46
+
47
+ TASKS_TYPE = {
48
+ 'TRAIN': api.Trainer,
49
+ 'PREDICT': api.Predictor,
50
+ 'OPT': api.StructureOptimization,
51
+ 'TS': api.StructureOptimization,
52
+ 'VIB': api.VibrationAnalysis,
53
+ 'NEB': api.ClimbingImageNudgedElasticBand,
54
+ 'MD': api.MolecularDynamics,
55
+ 'CMD': api.ConstrainedMolecularDynamics,
56
+ 'MC': api.MonteCarlo,
57
+ }
58
+ TASKS_TYPE_ALIAS = {
59
+ 'TRAIN': 'TRAIN',
60
+ 'PREDICT': 'PREDICT',
61
+ 'PREDICTION': 'PREDICT',
62
+ 'OPT': 'OPT',
63
+ 'STRUCTURE_OPTIMIZATION': 'OPT',
64
+ 'STRUC_OPT': 'OPT',
65
+ 'DIMER': 'TS',
66
+ 'TS': 'TS',
67
+ 'VIB': 'VIB',
68
+ 'VIBRATIONAL_ANALYSIS': 'VIB',
69
+ 'NEB': 'NEB',
70
+ 'CINEB': 'NEB',
71
+ 'CI_NEB': 'NEB',
72
+ 'MD': 'MD',
73
+ 'MOLECULAR_DYNAMICS': 'MD',
74
+ 'CMD': 'CMD',
75
+ 'CONSTRAINED_MOLECULAR_DYNAMICS': 'CMD',
76
+ 'CONSTR_MD': 'CMD',
77
+ 'MC': 'MC',
78
+ 'MONTE_CARLO': 'MC',
79
+ }
80
+
81
+ # Section: check task
82
+ task_type = config.get('TASK', None)
83
+ if task_type is None:
84
+ raise ValueError(
85
+ 'Task type is missing. '
86
+ f'You must specify a task_type by argument `TASK` in the {path} file.\n'
87
+ f'Available `TASK` values are:\n{", ".join(TASKS_TYPE_ALIAS.keys())} '
88
+ )
89
+ else:
90
+ task_type: str
91
+ task_type = task_type.upper()
92
+ if task_type not in TASKS_TYPE_ALIAS:
93
+ raise ValueError(
94
+ 'Task type is invalid. '
95
+ f'Available `TASK` values are:\n{", ".join(TASKS_TYPE_ALIAS.keys())}'
96
+ )
97
+ else:
98
+ task_type = TASKS_TYPE_ALIAS[task_type]
99
+
100
+ # Section: load model
101
+ model_file = config.get('MODEL_FILE', None)
102
+ model_name = config.get('MODEL_NAME', None)
103
+ if model_file is None:
104
+ raise ValueError(f"`MODEL_FILE` must be specified.")
105
+ if model_name is None:
106
+ raise ValueError(f"`MODEL_NAME` must be specified.")
107
+ udf_model = load_model(model_file, model_name)
108
+
109
+ # Section: load data
110
+ data_type = config.get('DATA_TYPE', 'POSCAR').upper()
111
+ data_path = config.get('DATA_PATH', '')
112
+ data_selector = config.get('DATA_NAME_SELECTOR', None)
113
+ if data_path == '': raise ValueError(f'`DATA_PATH` is not defined.')
114
+ data_loader_kwargs = config.get('DATA_LOADER_KWARGS', {})
115
+ data: bt.Structures = load_data(data_type, data_path, data_loader_kwargs)
116
+ if data_selector is not None:
117
+ data = data.select_by_sample_id(rf"{data_selector}")
118
+ if len(data) == 0:
119
+ raise RuntimeError(f"No data matches the pattern given by `DATA_NAME_SELECTOR`. BYE!")
120
+ is_shuffle = config.get('IS_SHUFFLE', False)
121
+
122
+ if task_type == 'TRAIN':
123
+ # handle the validation set data
124
+ val_set_path = config.get('VAL_SET_PATH', None)
125
+ validation_ratio = config.get('VAL_SPLIT_RATIO', None)
126
+ if val_set_path is not None:
127
+ val_data = load_data(data_type, val_set_path, data_loader_kwargs)
128
+ if data_selector is not None:
129
+ val_data = val_data.select_by_sample_id(rf"{data_selector}")
130
+ elif validation_ratio is not None:
131
+ # check validation if ratio correct
132
+ validation_ratio = float(validation_ratio)
133
+ if validation_ratio >= 1. or validation_ratio <= 0.:
134
+ raise ValueError(f"`VAL_SPLIT_RATIO` must be in interval (0, 1), but got {validation_ratio}.")
135
+ n_val = int(validation_ratio * len(data))
136
+ if n_val >= len(data) or n_val <= 0:
137
+ raise ValueError(
138
+ f"Unreasonable `VAL_SPLIT_RATIO` value `{validation_ratio}`, "
139
+ f"which will cause {n_val} validation samples and {len(data) - n_val} training samples."
140
+ )
141
+ val_data = data[:n_val]
142
+ data = data[n_val:]
143
+ else:
144
+ raise ValueError(f"THERE IS NO VALIDATION DATA SPECIFIED. TRAINING MAY BE MEANINGLESS.")
145
+
146
+ data_list = bt.preprocessing.CreatePygData(1).feat2data_list(data, n_core=1)
147
+ val_data_list = bt.preprocessing.CreatePygData(1).feat2data_list(val_data, n_core=1)
148
+
149
+ trn_ener = [data[atm.idx].Energies[0] for atm in data_list]
150
+ if data.Forces is None:
151
+ trn_forc = None
152
+ else:
153
+ trn_forc = [data[atm.idx].Forces[0] for atm in data_list]
154
+ train_data = {'data': data_list, 'labels': {'energy': trn_ener, 'forces': trn_forc}}
155
+ val_ener = [val_data[atm.idx].Energies[0] for atm in val_data_list]
156
+ if data.Forces is None:
157
+ val_forc = None
158
+ else:
159
+ val_forc = [val_data[atm.idx].Forces[0] for atm in val_data_list]
160
+ valid_data = {'data': val_data_list, 'labels': {'energy': val_ener, 'forces': val_forc}}
161
+ dataset_args = (train_data, valid_data)
162
+
163
+ elif task_type == 'NEB' or task_type == 'CMD': # They use ISFSDataLoader
164
+ # handle the final state configuration data
165
+ fs_data_path = config.get('FSDATA_PATH', None)
166
+ if fs_data_path is not None:
167
+ fs_data = load_data(data_type, fs_data_path, data_loader_kwargs)
168
+ else:
169
+ raise ValueError(f"`FSDATA_PATH` is not defined. For TASK `{task_type}`, "
170
+ f"you must specify the final-state-configuration data path by `FSDATA_PATH`.")
171
+ is_data_list = bt.preprocessing.CreatePygData(1).feat2data_list(data, n_core=1)
172
+ fs_data_list = bt.preprocessing.CreatePygData(1).feat2data_list(fs_data, n_core=1)
173
+ run_data = {'dataIS': is_data_list, 'dataFS': fs_data_list}
174
+ dataset_args = (run_data,)
175
+
176
+ elif task_type == 'TS': # Need a dimer initial guess
177
+ # handle the final state configuration data
178
+ disp_data_path = config.get('DISPDATA_PATH', None)
179
+ displace_flag = config.get('TRANSITION_STATE', None)
180
+ if displace_flag is None:
181
+ raise ValueError(
182
+ f"Transition state search is required but input arguments not provided. "
183
+ f"Please set `TRANSITION_STATE` section in the config file. "
184
+ )
185
+ else:
186
+ displace_flag = displace_flag.get('X_DIFF_ATTR', None)
187
+
188
+ is_data_list = bt.preprocessing.CreatePygData(1).feat2data_list(data, n_core=1)
189
+
190
+ if displace_flag is not None: # IF DISPDATA_PATH is given, canonically use data read from DISPDATA_PATH
191
+ if disp_data_path is not None:
192
+ disp_data = load_data(data_type, disp_data_path, data_loader_kwargs)
193
+ for i, dat in enumerate(is_data_list):
194
+ disp_tensor = th.as_tensor(disp_data.Coords[i])
195
+ setattr(dat, str(displace_flag), disp_tensor)
196
+ else:
197
+ fs_data_path = config.get('FSDATA_PATH', None) # ELIF FSDATA_PATH, use interpolated middle point of is/fs conf.
198
+ if fs_data_path is not None:
199
+ fs_data = load_data(data_type, fs_data_path, data_loader_kwargs)
200
+ # convert to displacement by interpolation
201
+ for i, dat in enumerate(is_data_list):
202
+ disp_coo, diff = direction_for_finite_diff(dat.pos, fs_data.Coords[i], dat.fixed)
203
+ setattr(dat, 'pos', disp_coo)
204
+ setattr(dat, str(displace_flag), diff)
205
+ else:
206
+ warnings.warn(f"`X_DIFF_ATTR` is set but no displacement is provided. `X_DIFF_ATTR` will be ignored.")
207
+ for i, dat in enumerate(is_data_list):
208
+ setattr(dat, str(displace_flag), None)
209
+ run_data = {'data': is_data_list, 'labels': None}
210
+ dataset_args = (run_data,)
211
+
212
+ else:
213
+ data_list = bt.preprocessing.CreatePygData(1).feat2data_list(data, n_core=1)
214
+ run_data = {'data': data_list, 'labels': None}
215
+ dataset_args = (run_data,)
216
+
217
+ # dataloader
218
+ if task_type == 'NEB' or task_type == 'CMD':
219
+ dataloader = ISFSPyGDataLoader
220
+ else:
221
+ dataloader = PyGDataLoader
222
+
223
+ # set runner
224
+ runner = TASKS_TYPE[task_type](path, 'pyg')
225
+ runner.set_dataset(*dataset_args, ) # type: ignore
226
+ runner.set_dataloader(dataloader, {'shuffle': is_shuffle})
227
+ # set constraints function
228
+ if task_type == 'CMD':
229
+ constr_file = config.get('CONSTRAINTS_FILE', None)
230
+ constr_name = config.get('CONSTRAINTS_FUNC', None)
231
+ if constr_file is None:
232
+ raise ValueError(f"`CONSTRAINTS_FILE` must be specified.")
233
+ if constr_name is None:
234
+ raise ValueError(f"`CONSTRAINTS_FUNC` must be specified.")
235
+ constr_func = load_model(constr_file, constr_name)
236
+ runner.set_constr_func(constr_func)
237
+
238
+ return task_type, runner, udf_model
239
+
240
+
241
+ def load_data(data_type, data_path, data_loader_kwargs):
242
+ """
243
+ A data loader
244
+ Returns: BatchStructure formated data
245
+
246
+ """
247
+ DATA_TYPE = {
248
+ 'external': {
249
+ 'POSCAR': load_files.POSCARs2Feat,
250
+ 'OUTCAR': load_files.OUTCAR2Feat,
251
+ 'CIF': load_files.Cif2Feat,
252
+ 'ASE_TRAJ': load_files.ASETraj2Feat,
253
+ },
254
+ 'buildin': {
255
+ 'BS': bt.load,
256
+ 'OPT': bt.read_opt_structures,
257
+ 'MD': bt.read_md_traj,
258
+ }
259
+ }
260
+
261
+ if data_type in DATA_TYPE['external']:
262
+ data_reader = DATA_TYPE['external'][data_type](data_path)
263
+ data = data_reader.read(**data_loader_kwargs)
264
+ elif data_type in DATA_TYPE['buildin']:
265
+ data_reader = DATA_TYPE['buildin'][data_type]
266
+ data = data_reader(data_path, **data_loader_kwargs)
267
+ else:
268
+ raise ValueError(
269
+ f'Data type "{data_type}" is invalid. '
270
+ f'Available `DATA_TYPE` values are:\n{', '.join(DATA_TYPE['external'].keys())}, {', '.join(DATA_TYPE['buildin'].keys())}'
271
+ )
272
+
273
+ return data
274
+
275
+
276
+ def load_model(func_file_path, func_name) -> Callable:
277
+ """
278
+ Load user defined model.
279
+ Args:
280
+ func_file_path: user-defined function file path
281
+ func_name: user-defined function name
282
+
283
+ Returns:
284
+
285
+ """
286
+ if not os.path.isfile(func_file_path):
287
+ raise FileNotFoundError(f"File {func_file_path} not found.")
288
+
289
+ realpath = os.path.realpath(func_file_path, strict=True)
290
+ enc_path = hashlib.md5(realpath.encode()).hexdigest() # hash the file name
291
+ module_name = f"udf_{enc_path}"
292
+ spec = importlib.util.spec_from_file_location(module_name, func_file_path)
293
+ if spec is None:
294
+ raise ImportError(f"Cannot load module from {func_file_path}.")
295
+
296
+ module = importlib.util.module_from_spec(spec)
297
+
298
+ # run
299
+ spec.loader.exec_module(module)
300
+
301
+ # get target function
302
+ udf = getattr(module, func_name, None)
303
+ if udf is None:
304
+ raise AttributeError(f"Function/Model {func_name} not found in {func_file_path}.")
305
+
306
+ return udf
307
+
308
+ def launch_task(inp):
309
+ """
310
+ launch a task.
311
+ Args:
312
+ inp: the input file path.
313
+
314
+ Returns: None
315
+
316
+ """
317
+ task_type, runner, udf_model = parse_center_input_file(inp)
318
+ if task_type == 'TRAIN':
319
+ runner.train(udf_model)
320
+ elif task_type == 'TS':
321
+ runner.ts(udf_model)
322
+ elif task_type == 'OPT':
323
+ runner.relax(udf_model)
324
+ else:
325
+ runner.run(udf_model)
326
+
327
+ def main():
328
+ """
329
+ Main program interface
330
+ Usage Convention:
331
+ buctoolkit -i xxx.inp -o xxx.oup
332
+ """
333
+ parser = argparse.ArgumentParser(
334
+ description=f'BUCToolkit MAIN PROGRAM INTERFACES\n{generate_display_art()}',
335
+ formatter_class=argparse.RawDescriptionHelpFormatter
336
+ )
337
+ parser.add_argument('-i', '--input', help='The path to input file.', required=True)
338
+ parser.add_argument(
339
+ '-o', '--output',
340
+ help='The path to output file. It will change the stdout. One can also cleanly redirect output by setting '
341
+ '`REDIRECT: true`, `OUTPUT_PATH: your/path/to/output/log/`, and `OUTPUT_POSTFIX: your_log_postfix` '
342
+ 'in the input file. If so, this argument will be ignored.',
343
+ default=None,
344
+ type=str,
345
+ )
346
+
347
+ opened_file = None
348
+ try:
349
+ if len(sys.argv) == 1: # Enter the interactive CLI
350
+ bt.cli.run_base_cli()
351
+ else: # otherwise directly run in one-line command
352
+ args = parser.parse_args()
353
+ if args.output is not None:
354
+ _rename = args.output
355
+ while os.path.exists(_rename):
356
+ _rename += '_1'
357
+ warnings.warn(f'Output file `{args.output}` already exists. Rename to {_rename}.')
358
+ opened_file = open(args.output, 'w')
359
+ sys.stdout = opened_file
360
+ launch_task(args.input)
361
+ finally:
362
+ if opened_file is not None:
363
+ if not opened_file.closed:
364
+ opened_file.close()
365
+ sys.stdout = sys.__stdout__
366
+
367
+ if __name__ == '__main__':
368
+ main()
@@ -0,0 +1,149 @@
1
+
2
+ from BUCToolkit._version import __version__
3
+ import time
4
+
5
+ def generate_display_art() -> str:
6
+ letters = {
7
+ 'B': [
8
+ "BBBBBB ",
9
+ "B B",
10
+ "B B",
11
+ "BBBBBB ",
12
+ "B B",
13
+ "B B",
14
+ "BBBBBB "
15
+ ],
16
+ 'U': [
17
+ "U U",
18
+ "U U",
19
+ "U U",
20
+ "U U",
21
+ "U U",
22
+ "U U",
23
+ " UUUUU "
24
+ ],
25
+ 'C': [
26
+ " CCCCC ",
27
+ "C C",
28
+ "C ",
29
+ "C ",
30
+ "C ",
31
+ "C C",
32
+ " CCCCC "
33
+ ],
34
+ 'T': [
35
+ "TTTTTTT",
36
+ " T ",
37
+ " T ",
38
+ " T ",
39
+ " T ",
40
+ " T ",
41
+ " T "
42
+ ],
43
+ 'O': [
44
+ " OOOOO ",
45
+ "O O",
46
+ "O O",
47
+ "O O",
48
+ "O O",
49
+ "O O",
50
+ " OOOOO "
51
+ ],
52
+ 'L': [
53
+ "LL ",
54
+ "LL ",
55
+ "LL ",
56
+ "LL ",
57
+ "LL ",
58
+ "LL ",
59
+ "LLLLLLL"
60
+ ],
61
+ 'K': [
62
+ "K K",
63
+ "K K ",
64
+ "K K ",
65
+ "KK ",
66
+ "K K ",
67
+ "K K ",
68
+ "K K"
69
+ ],
70
+ 'I': [
71
+ " II ",
72
+ " II ",
73
+ " II ",
74
+ " II ",
75
+ " II ",
76
+ " II ",
77
+ " II "
78
+ ]
79
+ }
80
+
81
+ # Target
82
+ word = "BUCTOOLKIT"
83
+
84
+ # 7 Lines to construct Letter Plot
85
+ art_lines = ["" for _ in range(7)]
86
+ for ch in word:
87
+ ch_art = letters.get(ch, letters['O'])
88
+ for i in range(7):
89
+ art_lines[i] += ch_art[i] + " " # one space as a delimiter
90
+
91
+ # drop rest spaces
92
+ art_lines = [line.rstrip() for line in art_lines]
93
+ art_width = max(len(line) for line in art_lines)
94
+ art_height = len(art_lines)
95
+
96
+ # Monitor Shape
97
+ left_margin = 4
98
+ top_margin = 2
99
+ internal_width = art_width + 2 * left_margin
100
+ internal_height = art_height + 2 * top_margin
101
+
102
+ # Inner Letters
103
+ screen_lines = []
104
+ for _ in range(internal_height):
105
+ screen_lines.append([" "] * internal_width)
106
+
107
+ # ASCII plot
108
+ start_row = top_margin
109
+ start_col = left_margin
110
+ for i, line in enumerate(art_lines):
111
+ for j, ch in enumerate(line):
112
+ if start_row + i < internal_height and start_col + j < internal_width:
113
+ screen_lines[start_row + i][start_col + j] = ch
114
+
115
+ screen_content = ["".join(row) for row in screen_lines]
116
+
117
+ copyright_lines = (f" BUCToolkit {__version__}. Copyright (c) 2024-{time.strftime("%Y")} "
118
+ f"Authors: Pu Pengxin, Song Xin, etc. " + " "*8)
119
+ screen_content.append(copyright_lines)
120
+
121
+ # Monitor Edges
122
+ top_border = "+" + "=" * internal_width + "+"
123
+ bottom_border = "+" + "=" * internal_width + "+"
124
+ bordered_lines = [top_border]
125
+ for line in screen_content:
126
+ bordered_lines.append("|" + line + "|")
127
+ bordered_lines.append(bottom_border)
128
+
129
+ # Monitor Steadier
130
+ base_width = internal_width - 4
131
+ if base_width < 6:
132
+ base_width = 6
133
+ base_pad = (internal_width - base_width) // 2 + 1
134
+ base_line = " " * base_pad + "|" + "_" * (base_width - 2) + "|" + " " * (internal_width - base_pad - base_width)
135
+ bordered_lines.append(base_line)
136
+ foot_width = base_width + 2
137
+ foot_pad = (internal_width - foot_width) // 2 + 1
138
+ foot_line = " " * foot_pad + "|" + "_" * (foot_width - 2) + "|" + " " * (internal_width - foot_pad - foot_width)
139
+ bordered_lines.append(foot_line)
140
+
141
+ # Copyright info
142
+ #copyright_lines = f" BUCToolkit {__version__}. Copyright (c) 2024-{time.strftime("%Y")} Authors: Pu Pengxin, Song Xin, etc."
143
+ #bordered_lines.append(copyright_lines)
144
+
145
+ return "\n".join(bordered_lines)
146
+
147
+
148
+ if __name__ == "__main__":
149
+ print(generate_display_art())
@@ -0,0 +1,6 @@
1
+ # Copyright (c) 2026.3.28, BUCToolkit.
2
+ # Authors: Pu Pengxin, Song Xin
3
+ # Version: 1.0b
4
+ # File: template_model.py
5
+ # Environment: Python 3.12
6
+ from EqV2.equiformer_v2.nets import EquiformerV2_OC20 as EqV2