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,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())
|