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,1680 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
Methods of reading and transform various files
|
|
3
|
+
"""
|
|
4
|
+
# Copyright (c) 2024-2025.7.4, BUCToolkit.
|
|
5
|
+
# Authors: Pu Pengxin, Song Xin
|
|
6
|
+
# Version: 0.9a
|
|
7
|
+
# File: load_files.py
|
|
8
|
+
# Environment: Python 3.12
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
import traceback
|
|
13
|
+
from typing import Any, Dict, List, Sequence, Set, Tuple, Optional, Literal
|
|
14
|
+
import time
|
|
15
|
+
import os
|
|
16
|
+
import copy
|
|
17
|
+
import warnings
|
|
18
|
+
|
|
19
|
+
import joblib as jb
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
from BUCToolkit.BatchStructures.BatchStructuresBase import BatchStructures
|
|
23
|
+
from BUCToolkit.utils._CheckModules import check_module
|
|
24
|
+
from BUCToolkit.utils.ElemListReduce import elem_list_reduce
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
'POSCARs2Feat',
|
|
28
|
+
'OUTCAR2Feat',
|
|
29
|
+
'ASETraj2Feat',
|
|
30
|
+
'ExtXyz2Feat',
|
|
31
|
+
'load_from_csv',
|
|
32
|
+
'Cif2Feat',
|
|
33
|
+
'load_from_structures',
|
|
34
|
+
'Output2Feat'
|
|
35
|
+
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
''' CONSTANCE '''
|
|
39
|
+
_ALL_ELEMENT = ['H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar',
|
|
40
|
+
'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr',
|
|
41
|
+
'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe',
|
|
42
|
+
'Cs', 'Ba', 'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu',
|
|
43
|
+
'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn',
|
|
44
|
+
'Fr', 'Ra', 'Ac', 'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk', 'Cf', 'Es', 'Fm', 'Md', 'No', 'Lr',
|
|
45
|
+
'Rf', 'Db', 'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', ] # element List
|
|
46
|
+
|
|
47
|
+
''' Load labels from local file '''
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def load_from_csv(
|
|
51
|
+
file_name: str,
|
|
52
|
+
label_type: type = float,
|
|
53
|
+
ignore_None_label_samp: bool = True,
|
|
54
|
+
read_column: Tuple[int, int] = (0, 1),
|
|
55
|
+
has_title_line: bool = False
|
|
56
|
+
) -> Dict[str, Any]:
|
|
57
|
+
r"""
|
|
58
|
+
load information of a csv file into a dict of {samp_1:label_1, ...}.
|
|
59
|
+
|
|
60
|
+
Parameters:
|
|
61
|
+
file_name: str, csv file of 2 columns (sample name, sample label)
|
|
62
|
+
label_type: type, type of sample label
|
|
63
|
+
ignore_None_label_samp: bool, whether ignore None label samples i.e., not put them into output dict
|
|
64
|
+
read_column: Tuple[int,int], the 2 columns to read, where the 1st column as keys and the 2nd as values.
|
|
65
|
+
has_title_line: bool, whether ignore the 1st line which is the title or descriptions.
|
|
66
|
+
|
|
67
|
+
Return:
|
|
68
|
+
dict of {samp_1:label_1, ...} | dict, id_list of [samp_names]
|
|
69
|
+
"""
|
|
70
|
+
f = np.loadtxt(file_name, dtype=str, delimiter=',')
|
|
71
|
+
if has_title_line:
|
|
72
|
+
f = f[1:]
|
|
73
|
+
col1 = read_column[0]
|
|
74
|
+
col2 = read_column[1]
|
|
75
|
+
|
|
76
|
+
if ignore_None_label_samp:
|
|
77
|
+
#print('load_from_file : The sample which has None label was ignored...')
|
|
78
|
+
energy_dict = {cont[col1]: label_type(cont[col2])
|
|
79
|
+
for cont in f if cont[col2] not in {'None', '', 'nan'}} # a dict of {sample_id: energy}
|
|
80
|
+
else:
|
|
81
|
+
energy_dict = {
|
|
82
|
+
cont[col1]: label_type(cont[col2]) if not (cont[col2] in {'None', 'nan', ''})
|
|
83
|
+
else None
|
|
84
|
+
for cont in f
|
|
85
|
+
} # a dict of {sample_id: energy}
|
|
86
|
+
|
|
87
|
+
return energy_dict
|
|
88
|
+
|
|
89
|
+
def load_from_structures(path: str):
|
|
90
|
+
"""
|
|
91
|
+
Load BatchStructure memory-mapping files from `path` and return this BatchStructures.
|
|
92
|
+
Args:
|
|
93
|
+
path: the path of BatchStructure memory-mapping files.
|
|
94
|
+
|
|
95
|
+
Returns: BatchStructures with read data.
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
f = BatchStructures()
|
|
99
|
+
f.load(path)
|
|
100
|
+
return f
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class POSCARs2Feat(BatchStructures):
|
|
104
|
+
"""
|
|
105
|
+
Read and convert a folder of POSCAR files from given path into arrays of atoms, coordinates, cell vectors, etc.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def __init__(self, path: str = './', verbose: int = 0, *args, **kwargs) -> None:
|
|
109
|
+
"""
|
|
110
|
+
Read and convert a folder of POSCAR files from given path into arrays of atoms, coordinates, cell vectors, etc.
|
|
111
|
+
|
|
112
|
+
Parameters:
|
|
113
|
+
path: str, the path of POSCAR files
|
|
114
|
+
verbose: int, the verbose of print information
|
|
115
|
+
|
|
116
|
+
Method:
|
|
117
|
+
read: read files
|
|
118
|
+
para_read: read files in parallel
|
|
119
|
+
|
|
120
|
+
Attributes:
|
|
121
|
+
file_list: list, name list of structure files
|
|
122
|
+
Coord: list, coordinates of atoms. Shape: List[NDArray[(n_atoms, 3), dtype=float32]]
|
|
123
|
+
Cells: list, cell vector of crystals. Shape: List[NDArray[(3,3), dtype=float32]]
|
|
124
|
+
Elements: list, elements of crystals. Shape: List[List[str(element symbols)]]
|
|
125
|
+
Numbers: list, element numbers of crystals. Shape: List[List[int(atom number of each element)]]. The same order of Elements.
|
|
126
|
+
|
|
127
|
+
Returns: None
|
|
128
|
+
"""
|
|
129
|
+
super().__init__()
|
|
130
|
+
self.path = path
|
|
131
|
+
self.verbose = verbose
|
|
132
|
+
self.files_list = list()
|
|
133
|
+
|
|
134
|
+
def para_read(
|
|
135
|
+
self,
|
|
136
|
+
file_list: Optional[List] = None,
|
|
137
|
+
output_coord_type: str = 'cartesian',
|
|
138
|
+
n_core: int = -1,
|
|
139
|
+
backend='loky'
|
|
140
|
+
):
|
|
141
|
+
r"""
|
|
142
|
+
Reading file in parallel.
|
|
143
|
+
Parameters:
|
|
144
|
+
file_list: list (or other Sequences), the list of selected files to be read. 'None' means read all files in the input path.
|
|
145
|
+
output_coord_type: str, 'cartesian' or 'direct'. The coordination type of output atom coordination.
|
|
146
|
+
n_core: int, the number of CPU cores used in reading files.
|
|
147
|
+
backend: backend for parallelization in joblib. Options: 'loky', 'threading', 'multiprocessing'.
|
|
148
|
+
"""
|
|
149
|
+
time_st = time.perf_counter()
|
|
150
|
+
|
|
151
|
+
# loading files
|
|
152
|
+
if self.verbose > 0: print('*' * 60 + '\nReading files...')
|
|
153
|
+
if file_list is None:
|
|
154
|
+
self.files_list = os.listdir(self.path)
|
|
155
|
+
else:
|
|
156
|
+
self.files_list = file_list
|
|
157
|
+
if self.files_list is None: raise RuntimeError('occurred a None file list.')
|
|
158
|
+
self.n_samp = len(self.files_list)
|
|
159
|
+
|
|
160
|
+
# check vars
|
|
161
|
+
_type_converter = {'cartesian': 'C', 'direct': 'D'}
|
|
162
|
+
if output_coord_type != 'cartesian' and output_coord_type != 'direct':
|
|
163
|
+
raise ValueError('Unknown output coordination type. Please input "cartesian" or "direct".')
|
|
164
|
+
self.Coords_type = [_type_converter[output_coord_type], ] * self.n_samp
|
|
165
|
+
|
|
166
|
+
# check parallel and initialize
|
|
167
|
+
if n_core == -1:
|
|
168
|
+
n_core = jb.cpu_count()
|
|
169
|
+
elif n_core <= 0:
|
|
170
|
+
raise ValueError('Invalid CPU numbers.')
|
|
171
|
+
elif n_core > jb.cpu_count():
|
|
172
|
+
warnings.warn('Input n_core is greater than total available CPU number, so it would be set to -1.')
|
|
173
|
+
n_core = jb.cpu_count()
|
|
174
|
+
|
|
175
|
+
# Parallel read
|
|
176
|
+
Z_dict = {key: i for i, key in enumerate(_ALL_ELEMENT, 1)} # a dictionary which map element symbols into their atomic numbers.
|
|
177
|
+
self._Z_dict = Z_dict
|
|
178
|
+
_para = jb.Parallel(n_jobs=n_core, verbose=self.verbose, backend=backend)
|
|
179
|
+
_temp = _para(jb.delayed(self._load)(filename) for filename in self.files_list)
|
|
180
|
+
if _temp is None: raise RuntimeError('Occurred None data.')
|
|
181
|
+
for temp in _temp:
|
|
182
|
+
self._Sample_ids.append(temp[0]) # mp_ids
|
|
183
|
+
self.Cells.append(temp[1]) # Lattice parameters
|
|
184
|
+
self.Elements.append(temp[2]) # Element symbols
|
|
185
|
+
self.Numbers.append(temp[3]) # Element numbers
|
|
186
|
+
self.Coords.append(temp[4]) # Atom coordination (cartesian)
|
|
187
|
+
self.Fixed.append(temp[5])
|
|
188
|
+
self._update_indices()
|
|
189
|
+
self._check_id()
|
|
190
|
+
self._check_len()
|
|
191
|
+
if self.verbose > 0: print(f'Done. Total Time: {time.perf_counter() - time_st:<5.4f}')
|
|
192
|
+
|
|
193
|
+
def read(self, file_list: Optional[List] = None, output_coord_type: str = 'cartesian', ) -> None:
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
file_list: list (or other Sequences), the list of selected files to be read. 'None' means read all files in the input path.
|
|
198
|
+
output_coord_type: str, 'cartesian' or 'direct'. The coordination type of output atom coordination.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
|
|
202
|
+
"""
|
|
203
|
+
time_st = time.perf_counter()
|
|
204
|
+
|
|
205
|
+
Z_dict = {key: i for i, key in enumerate(_ALL_ELEMENT, 1)} # a dictionary which map element symbols into their atomic numbers.
|
|
206
|
+
self._Z_dict = Z_dict
|
|
207
|
+
|
|
208
|
+
# loading files
|
|
209
|
+
if self.verbose > 0: print('*' * 60 + '\nReading files...\n')
|
|
210
|
+
if file_list is None:
|
|
211
|
+
self.files_list = [__f.name for __f in os.scandir(self.path) if __f.is_file()]
|
|
212
|
+
elif isinstance(file_list, (List, Tuple)):
|
|
213
|
+
for __f in file_list:
|
|
214
|
+
if os.path.isfile(os.path.join(self.path, __f)):
|
|
215
|
+
self.files_list.append(__f)
|
|
216
|
+
else:
|
|
217
|
+
warnings.warn(f'No such file: {os.path.join(self.path, __f)}, skipped.', RuntimeWarning)
|
|
218
|
+
else:
|
|
219
|
+
raise TypeError(f'Invalid type of `file_list`: {type(file_list)}')
|
|
220
|
+
self.n_samp = len(self.files_list)
|
|
221
|
+
if self.n_samp == 0: raise RuntimeError('Occurred empty file_list. No file could be read.')
|
|
222
|
+
|
|
223
|
+
# check vars
|
|
224
|
+
_type_converter = {'cartesian': 'C', 'direct': 'D'}
|
|
225
|
+
if output_coord_type != 'cartesian' and output_coord_type != 'direct':
|
|
226
|
+
raise ValueError('Unknown output coordination type. Please input "cartesian" or "direct".')
|
|
227
|
+
|
|
228
|
+
# loading atoms coordinates
|
|
229
|
+
self.Coords_type = [_type_converter[output_coord_type], ] * self.n_samp
|
|
230
|
+
self._Sample_ids = list()
|
|
231
|
+
self.Atom_list = None
|
|
232
|
+
self.Atomic_number_list = None
|
|
233
|
+
self.Cells = list()
|
|
234
|
+
self.Coords = list()
|
|
235
|
+
self.Elements = list()
|
|
236
|
+
self.Numbers = list()
|
|
237
|
+
time_old = copy.deepcopy(time_st)
|
|
238
|
+
err_num = 0
|
|
239
|
+
if self.verbose > 0: print('Progress: ', end='\r')
|
|
240
|
+
for i, fname in enumerate(self.files_list):
|
|
241
|
+
time_mid = time.perf_counter()
|
|
242
|
+
try:
|
|
243
|
+
temp = self._load(fname)
|
|
244
|
+
# update attr
|
|
245
|
+
self._Sample_ids.append(temp[0]) # mp_ids
|
|
246
|
+
self.Cells.append(temp[1]) # Lattice parameters
|
|
247
|
+
self.Elements.append(temp[2]) # Element symbols
|
|
248
|
+
self.Numbers.append(temp[3]) # Element numbers
|
|
249
|
+
self.Coords.append(temp[4]) # Atom coordinates (cartesian)
|
|
250
|
+
self.Fixed.append(temp[5]) # fixed masks
|
|
251
|
+
except Exception as e:
|
|
252
|
+
err_num += 1
|
|
253
|
+
print(f'WARNING : Failed to read file {fname}, because an error "{e}" occurred.'
|
|
254
|
+
f' ERROR NUMBER: {err_num}')
|
|
255
|
+
|
|
256
|
+
# progress bar
|
|
257
|
+
if (self.verbose > 0) and (time_mid - time_old > 1):
|
|
258
|
+
time_old = copy.deepcopy(time_mid)
|
|
259
|
+
prog = ((i + 1) / self.n_samp)
|
|
260
|
+
print('Progress: ' + '>' * int(20 * prog) + f'|{(100 * prog):.1f}%', end='\r')
|
|
261
|
+
i_old = copy.deepcopy(i)
|
|
262
|
+
|
|
263
|
+
if self.verbose > 0: print('Progress: ' + '>' * 20 + '|100% ', end='\r')
|
|
264
|
+
self._indecies = {_id: ii for ii, _id in enumerate(self._Sample_ids)}
|
|
265
|
+
self._update_indices()
|
|
266
|
+
self._check_id()
|
|
267
|
+
self._check_len()
|
|
268
|
+
if self.verbose > 0: print('\nAll files were read successfully!\n' + '*' * 60)
|
|
269
|
+
if self.verbose > 0: print(f'Total time: {(time.perf_counter() - time_st):<5.4f}')
|
|
270
|
+
#print(self.coord_data)
|
|
271
|
+
|
|
272
|
+
def _load(self, fileName: str) -> Tuple[str, np.ndarray, List[str], List[int], np.ndarray, np.ndarray]:
|
|
273
|
+
"""
|
|
274
|
+
Loading data from files(POSCAR-like files) in given path.
|
|
275
|
+
"""
|
|
276
|
+
with open(os.path.join(self.path, fileName), 'r') as f:
|
|
277
|
+
data = f.readlines() # read raw data
|
|
278
|
+
|
|
279
|
+
# atom type
|
|
280
|
+
atom_type = data[5].split()
|
|
281
|
+
|
|
282
|
+
# atom number
|
|
283
|
+
_atom_num = data[6].split()
|
|
284
|
+
atom_num = [int(num) for num in _atom_num]
|
|
285
|
+
n_atom = sum(atom_num)
|
|
286
|
+
# whether Selective Dynamics
|
|
287
|
+
if data[7].split()[0][0] == 'S' or data[7].split()[0][0] == 's':
|
|
288
|
+
is_selective_dynamics = 1
|
|
289
|
+
else:
|
|
290
|
+
is_selective_dynamics = 0
|
|
291
|
+
# coordination system(Direct or Cartesian)
|
|
292
|
+
coord_sys = data[7 + is_selective_dynamics].split()
|
|
293
|
+
if coord_sys[0][0] == 'D' or coord_sys[0][0] == 'd':
|
|
294
|
+
in_coord_type = 'D'
|
|
295
|
+
elif coord_sys[0][0] == 'C' or coord_sys[0][0] == 'c':
|
|
296
|
+
in_coord_type = 'C'
|
|
297
|
+
else:
|
|
298
|
+
raise RuntimeError(f'Unknown coordination type "{coord_sys[0][0]}" in file {fileName}')
|
|
299
|
+
# cell vectors
|
|
300
|
+
cell = np.empty((3, 3), dtype=np.float32)
|
|
301
|
+
for i in [2, 3, 4]:
|
|
302
|
+
cell[i - 2] = np.array(data[i].split(), dtype=np.float32)
|
|
303
|
+
# atoms cartesian coordinates # TODO: fill list first, and convert to array then.
|
|
304
|
+
atom_coord = [['-'*10, '-'*10, '-'*10]] * n_atom
|
|
305
|
+
atom_fixed = [[1, 1, 1]] * n_atom
|
|
306
|
+
if is_selective_dynamics == 0:
|
|
307
|
+
for i in range(n_atom):
|
|
308
|
+
atom_coord[i] = (data[8 + i].split())[:3]
|
|
309
|
+
else:
|
|
310
|
+
_slec_dynam_convertor = {'T': 1, 'F': 0}
|
|
311
|
+
for i in range(n_atom): # if selective dynamics, coordinates shift down 1 line
|
|
312
|
+
line_info = data[8 + 1 + i].split()
|
|
313
|
+
atom_coord[i] = line_info[:3]
|
|
314
|
+
atom_fixed[i] = [_slec_dynam_convertor[_] for _ in line_info[3:6]]
|
|
315
|
+
atom_coord = np.array(atom_coord, dtype=np.float32)
|
|
316
|
+
atom_fixed = np.array(atom_fixed, dtype=np.int8)
|
|
317
|
+
|
|
318
|
+
if self.Coords_type[0] == in_coord_type:
|
|
319
|
+
pass
|
|
320
|
+
elif in_coord_type == 'D':
|
|
321
|
+
atom_coord = atom_coord @ cell
|
|
322
|
+
elif in_coord_type == 'C':
|
|
323
|
+
atom_coord = atom_coord @ np.linalg.inv(cell)
|
|
324
|
+
|
|
325
|
+
return fileName, cell, atom_type, atom_num, atom_coord, atom_fixed
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class ConcatPOSCAR2Feat(BatchStructures):
|
|
329
|
+
r"""
|
|
330
|
+
Read and convert the single file of a batch POSCARs contents from given path into arrays of atoms, coordinates, cell vectors etc.
|
|
331
|
+
All numerical data were stored as float32 in memory.
|
|
332
|
+
The format of the read file:
|
|
333
|
+
|
|
334
|
+
# id1
|
|
335
|
+
TITLE
|
|
336
|
+
SCALE FACTOR
|
|
337
|
+
LATTICE VECTOR AXIS X (x1, x2, x3)
|
|
338
|
+
LATTICE VECTOR AXIS Y (y1, y2, y3)
|
|
339
|
+
LATTICE VECTOR AXIS Z (z1, z2, z3)
|
|
340
|
+
CHEMICAL ELEMENTS LIST (Element1 Element2 Element3 ...)
|
|
341
|
+
ATOM NUMBERS OF EACH ELEMENT (N1 N2 N3 ...)
|
|
342
|
+
COORDINATE TYPE ("Direct" or "Cartesian")
|
|
343
|
+
ATOM COORDINATES IN ORDER OF CHEMICAL ELEMENTS LIST (x1 y1 z1)
|
|
344
|
+
(x2 y2 z2)
|
|
345
|
+
(x3 y3 z3)
|
|
346
|
+
...
|
|
347
|
+
# id2
|
|
348
|
+
...
|
|
349
|
+
# id3
|
|
350
|
+
...
|
|
351
|
+
...
|
|
352
|
+
|
|
353
|
+
EOF
|
|
354
|
+
|
|
355
|
+
Parameters:
|
|
356
|
+
path: str, the path of POSCAR files
|
|
357
|
+
select_ids: list (or other Sequences), the list of selected id to be read. 'None' means read all files in the input path.
|
|
358
|
+
output_coord_type: str, 'cartesian' or 'direct'. The coordination type of output atom coordination.
|
|
359
|
+
|
|
360
|
+
Attributes:
|
|
361
|
+
Sample_ids: list, name list of structrues
|
|
362
|
+
Coords: list, coordinations of atoms. Shape: List[NDArray[(n_atoms, 3), dtype=float32]]
|
|
363
|
+
Cells: list, cell vector of crystals. Shape: List[NDArray[(3,3), dtype=float32]]
|
|
364
|
+
Elements: list, elements of crystals. Shape: List[List[str(element symbols)]]
|
|
365
|
+
Numbers: list, element numbers of crystals. Shape: List[List[int(atom number of each element)]]. the same order of Elements.
|
|
366
|
+
|
|
367
|
+
Returns: None
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
def __init__(self, path: str = './', select_ids: List[str] | Set[str] | None = None, output_coord_type: str = 'cartesian', verbose: int = 0) -> None:
|
|
371
|
+
warnings.warn('Deprecated.', DeprecationWarning)
|
|
372
|
+
raise Exception('Deprecated.')
|
|
373
|
+
super().__init__()
|
|
374
|
+
time_st = time.perf_counter()
|
|
375
|
+
Z_dict = {key: i for i, key in enumerate(_ALL_ELEMENT, 1)} # a dictionary which map element symbols into their atomic numbers.
|
|
376
|
+
self._Z_dict = Z_dict
|
|
377
|
+
|
|
378
|
+
# loading files
|
|
379
|
+
if verbose: print('*' * 60 + '\nReading files...')
|
|
380
|
+
self.output_coord_type = output_coord_type
|
|
381
|
+
self._Sample_ids = list()
|
|
382
|
+
self.Atom_list = None
|
|
383
|
+
self.Atomic_number_list = None
|
|
384
|
+
self.Cells = list()
|
|
385
|
+
self.Coords = list()
|
|
386
|
+
self.Elements = list()
|
|
387
|
+
self.Numbers = list()
|
|
388
|
+
time_old = copy.deepcopy(time_st)
|
|
389
|
+
|
|
390
|
+
with open(path, 'r') as f:
|
|
391
|
+
if select_ids is None: # read all files
|
|
392
|
+
while True:
|
|
393
|
+
_text = f.readline()
|
|
394
|
+
if _text == '':
|
|
395
|
+
break
|
|
396
|
+
if _text[0] == '#': # whether the id line. Find the headline.
|
|
397
|
+
_id = _text.replace(' ', '') # str
|
|
398
|
+
_id = _id[1:-1]
|
|
399
|
+
self._Sample_ids.append(_id)
|
|
400
|
+
|
|
401
|
+
next(f) # skip title line
|
|
402
|
+
scale = float(f.readline()) # scale factor line
|
|
403
|
+
# Cell Vector
|
|
404
|
+
_cell_vec = list()
|
|
405
|
+
_cell_vec.append((f.readline()).split()) # cell axis a
|
|
406
|
+
_cell_vec.append((f.readline()).split()) # cell axis b
|
|
407
|
+
_cell_vec.append((f.readline()).split()) # cell axis c
|
|
408
|
+
_cell_vec = np.asarray(_cell_vec, dtype=np.float32) * scale # np.NDArray
|
|
409
|
+
self.Cells.append(_cell_vec)
|
|
410
|
+
# Atoms
|
|
411
|
+
_atoms = (f.readline()).split()
|
|
412
|
+
self.Elements.append(_atoms) # List[str]
|
|
413
|
+
# Atom Numbers
|
|
414
|
+
_atom_num = [int(_) for _ in (f.readline()).split()]
|
|
415
|
+
self.Numbers.append(_atom_num) # List[int]
|
|
416
|
+
_atom_tol_num = sum(_atom_num)
|
|
417
|
+
# Coord Type
|
|
418
|
+
_type = (f.readline()).strip()
|
|
419
|
+
# Coords
|
|
420
|
+
if _type[0] == 's' or _type[0] == 'S': # whether 'Selective dynamics'
|
|
421
|
+
_type = (f.readline()).strip()
|
|
422
|
+
_coords = np.empty((_atom_tol_num, 3), dtype=np.float32)
|
|
423
|
+
for ii in range(_atom_tol_num):
|
|
424
|
+
_coords[ii] = np.asarray((f.readline().split())[:3], dtype=np.float32)
|
|
425
|
+
if _type[0] == 'd' or _type[0] == 'D':
|
|
426
|
+
_coords = _coords @ _cell_vec
|
|
427
|
+
elif _type[0] == 'c' or _type[0] == 'C':
|
|
428
|
+
pass
|
|
429
|
+
else:
|
|
430
|
+
warnings.warn(f'Unknown Coordinate type. Coordinate type of sample *** {_id} *** would be considered as "Direct"')
|
|
431
|
+
_coords = _coords @ _cell_vec
|
|
432
|
+
self.Coords.append(_coords)
|
|
433
|
+
else:
|
|
434
|
+
select_ids = set(select_ids)
|
|
435
|
+
_have_read_ids = set()
|
|
436
|
+
while True:
|
|
437
|
+
_text = f.readline()
|
|
438
|
+
if _text == '': # judge EOF
|
|
439
|
+
break
|
|
440
|
+
if _text[0] == '#': # whether the id line. Find the head line.
|
|
441
|
+
_id = _text.replace(' ', '') # str
|
|
442
|
+
_id = _id[1:-1]
|
|
443
|
+
if _id not in select_ids:
|
|
444
|
+
continue
|
|
445
|
+
_have_read_ids.add(_id)
|
|
446
|
+
self._Sample_ids.append(_id)
|
|
447
|
+
|
|
448
|
+
next(f) # skip title line
|
|
449
|
+
scale = float(f.readline()) # scale factor line
|
|
450
|
+
# Cell Vector
|
|
451
|
+
_cell_vec = list()
|
|
452
|
+
_cell_vec.append((f.readline()).split()) # cell axis a
|
|
453
|
+
_cell_vec.append((f.readline()).split()) # cell axis b
|
|
454
|
+
_cell_vec.append((f.readline()).split()) # cell axis c
|
|
455
|
+
_cell_vec = np.asarray(_cell_vec, dtype=np.float32) * scale # np.NDArray
|
|
456
|
+
self.Cells.append(_cell_vec)
|
|
457
|
+
# Atoms
|
|
458
|
+
_atoms = (f.readline()).split()
|
|
459
|
+
self.Elements.append(_atoms) # List[str]
|
|
460
|
+
# Atom Numbers
|
|
461
|
+
_atom_num = [int(_) for _ in (f.readline()).split()]
|
|
462
|
+
self.Numbers.append(_atom_num) # List[int]
|
|
463
|
+
_atom_tol_num = sum(_atom_num)
|
|
464
|
+
# Coord Type
|
|
465
|
+
_type = (f.readline()).strip()
|
|
466
|
+
# Coords
|
|
467
|
+
if _type[0] == 's' or _type[0] == 'S': # whether 'Selective dynamics'
|
|
468
|
+
_type = (f.readline()).strip()
|
|
469
|
+
_coords = np.empty((_atom_tol_num, 3), dtype=np.float32)
|
|
470
|
+
for ii in range(_atom_tol_num):
|
|
471
|
+
_coords[ii] = np.asarray((f.readline().split())[:3], dtype=np.float32)
|
|
472
|
+
if _type[0] == 'd' or _type[0] == 'D':
|
|
473
|
+
_coords = _coords @ _cell_vec
|
|
474
|
+
elif _type[0] == 'c' or _type[0] == 'C':
|
|
475
|
+
pass
|
|
476
|
+
else:
|
|
477
|
+
warnings.warn(f'Unknown Coordinate type. Coordinate type of sample *** {_id} *** would be considered as "Direct"')
|
|
478
|
+
_coords = _coords @ _cell_vec
|
|
479
|
+
self.Coords.append(_coords)
|
|
480
|
+
|
|
481
|
+
if verbose: print('All files were read successfully!\n' + '*' * 60)
|
|
482
|
+
|
|
483
|
+
time_ed = time.perf_counter()
|
|
484
|
+
if verbose: print('Total time: %s' % (time_ed - time_st))
|
|
485
|
+
pass
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
class OUTCAR2Feat(BatchStructures):
|
|
489
|
+
r"""
|
|
490
|
+
Read atoms, coordinates, atom numbers, energies and force in OUTCARs from given path.
|
|
491
|
+
Notes: Because OUTCAR does not store any information of fixed atom (selective dynamics), all atoms would be set to FREE!!
|
|
492
|
+
|
|
493
|
+
Parameters:
|
|
494
|
+
path: str, the path of batched OUTCAR-like files. The Sample ids would be labeled as its file names.
|
|
495
|
+
"""
|
|
496
|
+
|
|
497
|
+
def __init__(self, path: str, verbose: int = 1):
|
|
498
|
+
super().__init__()
|
|
499
|
+
self.verbose = verbose
|
|
500
|
+
self.path = path
|
|
501
|
+
self.Energies = list()
|
|
502
|
+
self.Forces = list()
|
|
503
|
+
|
|
504
|
+
self.__n_atom_partt = re.compile(r"(?<=NIONS =)\s+[0-9]+\n")
|
|
505
|
+
self.__pos_force_partt = re.compile(
|
|
506
|
+
r"(?<=TOTAL-FORCE.\(eV/Angst\)\n.-----------------------------------------------------------------------------------\n)\s+[-|0-9\s\n.]+")
|
|
507
|
+
self.__energy_partt = re.compile(r'FREE ENERGIE OF THE ION-ELECTRON SYSTEM.*\n.*\n.*\n.*\n.*energy\(sigma->0\) =\s*([0-9\-.]+)\n')
|
|
508
|
+
self.__cell_partt = re.compile(r'VOLUME and BASIS-vectors are now.*\n.*\n.*\n.*\n.*direct lattice vectors.*\n(.*\n.*\n.*)')
|
|
509
|
+
self.__is_converge_partt = re.compile(r'aborting loop .')
|
|
510
|
+
self.__voltage_partt = re.compile(r"E-fermi\s+:\s*([-|0-9.]+)\s+XC")
|
|
511
|
+
|
|
512
|
+
@staticmethod
|
|
513
|
+
def _get_atom_info(data: str) -> Tuple[List, List]:
|
|
514
|
+
r""" get atom list and atom number from OUTCAR """
|
|
515
|
+
numbers = re.search(r"(?<=ions per type =)\s+[0-9|\s]+\n", data)
|
|
516
|
+
if numbers is not None:
|
|
517
|
+
numbers = numbers.group().split()
|
|
518
|
+
else:
|
|
519
|
+
raise RuntimeError('atom numbers could not find.')
|
|
520
|
+
n_type = len(numbers)
|
|
521
|
+
|
|
522
|
+
atoms = re.findall(r"POTCAR:\s*PAW_PBE\s*([A-Za-z]{1,2})", data)
|
|
523
|
+
atoms = atoms[:n_type]
|
|
524
|
+
|
|
525
|
+
return atoms, numbers
|
|
526
|
+
|
|
527
|
+
def _read_single_file(self, file_name: str, parallel: bool = False) -> Any:
|
|
528
|
+
r"""
|
|
529
|
+
Read single file.
|
|
530
|
+
|
|
531
|
+
Parameters:
|
|
532
|
+
file_name: str, the OUTCAR-like file name.
|
|
533
|
+
parallel: bool,
|
|
534
|
+
"""
|
|
535
|
+
full_path = os.path.join(self.path, file_name)
|
|
536
|
+
if not os.path.isfile(full_path):
|
|
537
|
+
warnings.warn(f'No OUTCAR file in given directory {os.path.join(self.path, file_name)}')
|
|
538
|
+
return None
|
|
539
|
+
try:
|
|
540
|
+
with open(full_path, "r") as file: #打开文件
|
|
541
|
+
data = file.read() #读取文件
|
|
542
|
+
n_atom = re.search(self.__n_atom_partt, data) #从OUTCAR文件搜索原子数
|
|
543
|
+
if n_atom is None:
|
|
544
|
+
warnings.warn(f'No atoms matched in {file_name}, skipped.', RuntimeWarning)
|
|
545
|
+
if parallel:
|
|
546
|
+
return [], [], [], [], [], [], [], [], []
|
|
547
|
+
else:
|
|
548
|
+
return None
|
|
549
|
+
n_atom = int(n_atom.group()) #将输出的原子个数保存为整数形式
|
|
550
|
+
position_iter = re.finditer(self.__pos_force_partt, data) #通过re匹配的迭代器函数,找到原子坐标
|
|
551
|
+
_energies = re.findall(self.__energy_partt, data)
|
|
552
|
+
is_converged = re.findall(self.__is_converge_partt, data)
|
|
553
|
+
|
|
554
|
+
# ATOM & Number
|
|
555
|
+
atoms, numbers = self._get_atom_info(data)
|
|
556
|
+
atoms = np.array(atoms, dtype='<U4')
|
|
557
|
+
numbers = np.array(numbers, dtype=np.int32)
|
|
558
|
+
# Cell Vectors
|
|
559
|
+
cells_str = re.findall(self.__cell_partt, data, )
|
|
560
|
+
|
|
561
|
+
cells = list()
|
|
562
|
+
_data = list()
|
|
563
|
+
energies = list()
|
|
564
|
+
for i, match_for in enumerate(position_iter): #循环的取每次迭代找到的一个结构的原子坐标与受力信息
|
|
565
|
+
# if SCF converged, content in OUTCAR would be 'aborting loop because ...' else 'aborting loop EDIFF was not reached ...'
|
|
566
|
+
if is_converged[i][-1] == 'b':
|
|
567
|
+
_dat = re.split(r"\n+", match_for.group()) #通过换行符进行划分
|
|
568
|
+
_dat = [re.split(r'[0-9][\n\s\t]+', dat_) for dat_ in _dat[:-2]] #去除空字符或都是横线的行,然后循环的对每一行进行划分 <<< # TODO
|
|
569
|
+
_data.append(_dat) #将原子坐标的列表添加进列表中
|
|
570
|
+
energies.append(float(_energies[i]))
|
|
571
|
+
# sometimes the cell vector would not split by \s e.g., "0.000000000-10.955671660 20.223087260"
|
|
572
|
+
_cell_line: List[str] = cells_str[i].split()
|
|
573
|
+
if len(_cell_line) != 3:
|
|
574
|
+
__cell_line = list()
|
|
575
|
+
[__cell_line.extend(_.replace('-', ' -').split()) for _ in _cell_line]
|
|
576
|
+
_cell_line = __cell_line
|
|
577
|
+
del __cell_line
|
|
578
|
+
cells.append(_cell_line)
|
|
579
|
+
|
|
580
|
+
cells = np.array(cells, dtype=np.float32).reshape(-1, 3, 6)
|
|
581
|
+
cells = cells[:, :, :3]
|
|
582
|
+
_data = np.array(_data, dtype=np.float32) # (n_step, n_atom, 3)
|
|
583
|
+
# check if match
|
|
584
|
+
if _data.shape[1] != np.sum(numbers):
|
|
585
|
+
warnings.warn(
|
|
586
|
+
f'Number of the read atoms and coordinates are not match in the file {file_name}, skipped. '
|
|
587
|
+
f'Maybe your elements in this structure are too many (e.g., > 10).',
|
|
588
|
+
RuntimeWarning
|
|
589
|
+
)
|
|
590
|
+
if parallel:
|
|
591
|
+
return [], [], [], [], [], [], [], [], []
|
|
592
|
+
else:
|
|
593
|
+
return None
|
|
594
|
+
if len(_data) == 0:
|
|
595
|
+
warnings.warn(f'Occurred empty data in file {file_name}, skipped.', RuntimeWarning)
|
|
596
|
+
if parallel:
|
|
597
|
+
return [], [], [], [], [], [], [], [], []
|
|
598
|
+
else:
|
|
599
|
+
return None
|
|
600
|
+
|
|
601
|
+
# formatted
|
|
602
|
+
n_step, n_atom, _ = _data.shape
|
|
603
|
+
coords = _data[:, :, :3]
|
|
604
|
+
coords = [coo for coo in coords]
|
|
605
|
+
forces = _data[:, :, 3:]
|
|
606
|
+
forces = [forc for forc in forces]
|
|
607
|
+
atoms = atoms[None, :].repeat(n_step, axis=0)
|
|
608
|
+
numbers = numbers[None, :].repeat(n_step, axis=0)
|
|
609
|
+
fixed = np.ones_like(coords, dtype=np.int8)
|
|
610
|
+
|
|
611
|
+
_id = [file_name + f'_{i}' for i in range(n_step)]
|
|
612
|
+
# output
|
|
613
|
+
if parallel:
|
|
614
|
+
return _id, atoms, numbers, cells, coords, energies, forces, fixed, ['C',] * n_step
|
|
615
|
+
else:
|
|
616
|
+
self._Sample_ids.extend(_id)
|
|
617
|
+
self.Elements.extend(atoms.tolist())
|
|
618
|
+
self.Numbers.extend(numbers.tolist())
|
|
619
|
+
self.Cells.extend(cells)
|
|
620
|
+
self.Coords.extend(coords)
|
|
621
|
+
self.Energies.extend(energies)
|
|
622
|
+
self.Forces.extend(forces)
|
|
623
|
+
self.Fixed.extend(fixed)
|
|
624
|
+
self.Coords_type.extend(["C"] * n_step)
|
|
625
|
+
except Exception as err:
|
|
626
|
+
warnings.warn(f'An Error occurred when reading file {file_name}, skipped.\nError: {err}.')
|
|
627
|
+
if parallel:
|
|
628
|
+
return [], [], [], [], [], [], [], [], []
|
|
629
|
+
else:
|
|
630
|
+
return None
|
|
631
|
+
|
|
632
|
+
def read(self, file_list: Optional[List[str]] = None, n_core: int = -1, backend: str = 'loky'):
|
|
633
|
+
r"""
|
|
634
|
+
Parameters:
|
|
635
|
+
file_list: List[str], the list of files to read. Default for all files under given path.
|
|
636
|
+
n_core: int, the number of CPU cores used in reading files.
|
|
637
|
+
backend: backend for parallelization in joblib. Options: 'loky', 'threading', 'multiprocessing'.
|
|
638
|
+
|
|
639
|
+
Return: None
|
|
640
|
+
Update the attribute self.data.
|
|
641
|
+
"""
|
|
642
|
+
t_st = time.perf_counter()
|
|
643
|
+
if file_list is None:
|
|
644
|
+
file_list = os.listdir(self.path)
|
|
645
|
+
file_list = [f_ for f_ in file_list if os.path.isfile(os.path.join(self.path, f_))]
|
|
646
|
+
elif not isinstance(file_list, Sequence):
|
|
647
|
+
raise TypeError(f'Invalid type of files_list: {type(file_list)}')
|
|
648
|
+
|
|
649
|
+
if n_core > len(file_list):
|
|
650
|
+
warnings.warn(f'`ncore` is greater than file numbers, so `ncore` was reset to {len(file_list)}', RuntimeWarning)
|
|
651
|
+
n_core = len(file_list)
|
|
652
|
+
elif n_core == -1:
|
|
653
|
+
n_core = jb.cpu_count()
|
|
654
|
+
elif not(isinstance(n_core, int)) or n_core < -1:
|
|
655
|
+
raise ValueError(f'Invalid `n_core` number: {n_core}.')
|
|
656
|
+
|
|
657
|
+
if n_core == 1:
|
|
658
|
+
if self.verbose: print('Sequential Reading...');print('Progress: 0%', end='\r')
|
|
659
|
+
for i, fi_name in enumerate(file_list):
|
|
660
|
+
self._read_single_file(fi_name, parallel=False)
|
|
661
|
+
if self.verbose > 0:
|
|
662
|
+
if (i + 1) % 50 == 0:
|
|
663
|
+
prog_ = (i + 1) / len(file_list)
|
|
664
|
+
prog = round(prog_ / 0.05)
|
|
665
|
+
print('Progress: ' + '>' * prog + f'{prog_ * 100:>3.2f}%', end='\r')
|
|
666
|
+
if self.verbose: print('Progress: ' + '>' * 20 + f'{100:>3d}%')
|
|
667
|
+
else:
|
|
668
|
+
_para = jb.Parallel(n_jobs=n_core, backend=backend, verbose=self.verbose)
|
|
669
|
+
_dat = _para(jb.delayed(self._read_single_file)(fi_name, parallel=True) for fi_name in file_list)
|
|
670
|
+
for temp in _dat:
|
|
671
|
+
self._Sample_ids.extend(temp[0])
|
|
672
|
+
self.Elements.extend(temp[1])
|
|
673
|
+
self.Numbers.extend(temp[2])
|
|
674
|
+
self.Cells.extend(temp[3])
|
|
675
|
+
self.Coords.extend(temp[4])
|
|
676
|
+
self.Energies.extend(temp[5])
|
|
677
|
+
self.Forces.extend(temp[6])
|
|
678
|
+
self.Fixed.extend(temp[7])
|
|
679
|
+
self.Coords_type.extend(temp[8])
|
|
680
|
+
if not self._Sample_ids: raise RuntimeError('All data are empty.')
|
|
681
|
+
self._update_indices()
|
|
682
|
+
self._check_id()
|
|
683
|
+
self._check_len()
|
|
684
|
+
if self.verbose > 0: print(f'Done. Total Time: {time.perf_counter() - t_st:>5.4f}')
|
|
685
|
+
|
|
686
|
+
def _read_single_file_add_voltage(self, file_name: str, parallel: bool = False) -> Any:
|
|
687
|
+
r"""
|
|
688
|
+
Read single file and output result has voltage.
|
|
689
|
+
|
|
690
|
+
Parameters:
|
|
691
|
+
file_name: str, the OUTCAR-like file name.
|
|
692
|
+
parallel: bool,
|
|
693
|
+
"""
|
|
694
|
+
full_path = os.path.join(self.path, file_name)
|
|
695
|
+
if not os.path.isfile(full_path):
|
|
696
|
+
warnings.warn(f'No OUTCAR file in given directory {os.path.join(self.path, file_name)}')
|
|
697
|
+
return None
|
|
698
|
+
try:
|
|
699
|
+
with open(full_path, "r") as file: #打开文件
|
|
700
|
+
data = file.read() #读取文件
|
|
701
|
+
n_atom = re.search(self.__n_atom_partt, data) #从OUTCAR文件搜索原子数
|
|
702
|
+
if n_atom is None:
|
|
703
|
+
warnings.warn(f'No atoms matched in {file_name}, skipped.', RuntimeWarning)
|
|
704
|
+
if parallel:
|
|
705
|
+
return [], [], [], [], [], [], [], [], [], []
|
|
706
|
+
else:
|
|
707
|
+
return None
|
|
708
|
+
n_atom = int(n_atom.group()) #将输出的原子个数保存为整数形式
|
|
709
|
+
position_iter = re.finditer(self.__pos_force_partt, data) #通过re匹配的迭代器函数,找到原子坐标
|
|
710
|
+
_energies = re.findall(self.__energy_partt, data)
|
|
711
|
+
is_converged = re.findall(self.__is_converge_partt, data)
|
|
712
|
+
_voltage = re.findall(self.__voltage_partt,data)
|
|
713
|
+
# ATOM & Number
|
|
714
|
+
atoms, numbers = self._get_atom_info(data)
|
|
715
|
+
atoms = np.array(atoms, dtype='<U4')
|
|
716
|
+
numbers = np.array(numbers, dtype=np.int32)
|
|
717
|
+
# Cell Vectors
|
|
718
|
+
cells_str = re.findall(self.__cell_partt, data, )
|
|
719
|
+
|
|
720
|
+
cells = list()
|
|
721
|
+
_data = list()
|
|
722
|
+
energies = list()
|
|
723
|
+
voltage = list()
|
|
724
|
+
for i, match_for in enumerate(position_iter): #循环的取每次迭代找到的一个结构的原子坐标与受力信息
|
|
725
|
+
# if SCF converged, content in OUTCAR would be 'aborting loop because ...' else 'aborting loop EDIFF was not reached ...'
|
|
726
|
+
if is_converged[i][-1] == 'b':
|
|
727
|
+
_dat = re.split(r"\n+", match_for.group()) #通过换行符进行划分
|
|
728
|
+
_dat = [re.split(r'[0-9][\n\s\t]+', dat_) for dat_ in _dat[:-2]] #去除空字符或都是横线的行,然后循环的对每一行进行划分 <<< # TODO
|
|
729
|
+
_data.append(_dat) #将原子坐标的列表添加进列表中
|
|
730
|
+
energies.append(float(_energies[i]))
|
|
731
|
+
voltage.append([float(_voltage[i])]*len(_dat))
|
|
732
|
+
# sometimes the cell vector would not split by \s e.g., "0.000000000-10.955671660 20.223087260"
|
|
733
|
+
_cell_line: List[str] = cells_str[i].split()
|
|
734
|
+
if len(_cell_line) != 3:
|
|
735
|
+
__cell_line = list()
|
|
736
|
+
[__cell_line.extend(_.replace('-', ' -').split()) for _ in _cell_line]
|
|
737
|
+
_cell_line = __cell_line
|
|
738
|
+
del __cell_line
|
|
739
|
+
cells.append(_cell_line)
|
|
740
|
+
|
|
741
|
+
cells = np.array(cells, dtype=np.float32).reshape(-1, 3, 6)
|
|
742
|
+
cells = cells[:, :, :3]
|
|
743
|
+
_data = np.array(_data, dtype=np.float32) # (n_step, n_atom, 3)
|
|
744
|
+
if len(_data) == 0:
|
|
745
|
+
warnings.warn(f'Occurred empty data in file {file_name}, skipped.', RuntimeWarning)
|
|
746
|
+
if parallel:
|
|
747
|
+
return [], [], [], [], [], [], [], [], [], []
|
|
748
|
+
else:
|
|
749
|
+
return None
|
|
750
|
+
|
|
751
|
+
# formatted
|
|
752
|
+
n_step, n_atom, _ = _data.shape
|
|
753
|
+
coords = _data[:, :, :3]
|
|
754
|
+
coords = [coo for coo in coords]
|
|
755
|
+
forces = _data[:, :, 3:]
|
|
756
|
+
forces = [forc for forc in forces]
|
|
757
|
+
atoms = atoms[None, :].repeat(n_step, axis=0)
|
|
758
|
+
numbers = numbers[None, :].repeat(n_step, axis=0)
|
|
759
|
+
fixed = np.ones_like(coords, dtype=np.int8)
|
|
760
|
+
|
|
761
|
+
_id = [file_name + f'_{i}' for i in range(n_step)]
|
|
762
|
+
# output
|
|
763
|
+
if parallel:
|
|
764
|
+
return _id, atoms, numbers, cells, coords, energies, forces, fixed, ['C',] * n_step, voltage
|
|
765
|
+
else:
|
|
766
|
+
self._Sample_ids.extend(_id)
|
|
767
|
+
self.Elements.extend(atoms.tolist())
|
|
768
|
+
self.Numbers.extend(numbers.tolist())
|
|
769
|
+
self.Cells.extend(cells)
|
|
770
|
+
self.Coords.extend(coords)
|
|
771
|
+
self.Energies.extend(energies)
|
|
772
|
+
self.Forces.extend(forces)
|
|
773
|
+
self.Fixed.extend(fixed)
|
|
774
|
+
self.Coords_type.extend(["C"] * n_step)
|
|
775
|
+
self.Labels.extend(voltage)
|
|
776
|
+
except Exception as err:
|
|
777
|
+
warnings.warn(f'An Error occurred when reading file {file_name}, skipped.\nError: {err}.')
|
|
778
|
+
if parallel:
|
|
779
|
+
return [], [], [], [], [], [], [], [], [], []
|
|
780
|
+
else:
|
|
781
|
+
return None
|
|
782
|
+
|
|
783
|
+
def read_contain_voltage(self, file_list: Optional[List[str]] = None, n_core: int = -1, backend: str = 'loky'):
|
|
784
|
+
r"""
|
|
785
|
+
read the list of OUTCAR files and output every set of id, element, atoms, coordinate, forces, energy, voltage
|
|
786
|
+
Parameters:
|
|
787
|
+
file_list: List[str], the list of files to read. Default for all files under given path.
|
|
788
|
+
n_core: int, the number of CPU cores used in reading files.
|
|
789
|
+
backend: backend for parallelization in joblib. Options: 'loky', 'threading', 'multiprocessing'.
|
|
790
|
+
|
|
791
|
+
Return: None
|
|
792
|
+
Update the attribute self.data.
|
|
793
|
+
"""
|
|
794
|
+
t_st = time.perf_counter()
|
|
795
|
+
self.Labels = list()
|
|
796
|
+
if file_list is None:
|
|
797
|
+
file_list = os.listdir(self.path)
|
|
798
|
+
file_list = [f_ for f_ in file_list if os.path.isfile(os.path.join(self.path, f_))]
|
|
799
|
+
elif not isinstance(file_list, Sequence):
|
|
800
|
+
raise TypeError(f'Invalid type of files_list: {type(file_list)}')
|
|
801
|
+
|
|
802
|
+
if n_core > len(file_list):
|
|
803
|
+
warnings.warn(f'`ncore` is greater than file numbers, so `ncore` was reset to {len(file_list)}', RuntimeWarning)
|
|
804
|
+
n_core = len(file_list)
|
|
805
|
+
elif n_core == -1:
|
|
806
|
+
n_core = jb.cpu_count()
|
|
807
|
+
elif not(isinstance(n_core, int)) or n_core < -1:
|
|
808
|
+
raise ValueError(f'Invalid `n_core` number: {n_core}.')
|
|
809
|
+
|
|
810
|
+
if n_core == 1:
|
|
811
|
+
if self.verbose: print('Sequential Reading...');print('Progress: 0%', end='\r')
|
|
812
|
+
for i, fi_name in enumerate(file_list):
|
|
813
|
+
self._read_single_file(fi_name, parallel=False)
|
|
814
|
+
if self.verbose > 0:
|
|
815
|
+
if (i + 1) % 50 == 0:
|
|
816
|
+
prog_ = (i + 1) / len(file_list)
|
|
817
|
+
prog = round(prog_ / 0.05)
|
|
818
|
+
print('Progress: ' + '>' * prog + f'{prog_ * 100:>3.2f}%', end='\r')
|
|
819
|
+
if self.verbose: print('Progress: ' + '>' * 20 + f'{100:>3d}%')
|
|
820
|
+
else:
|
|
821
|
+
_para = jb.Parallel(n_jobs=n_core, backend=backend, verbose=self.verbose)
|
|
822
|
+
_dat = _para(jb.delayed(self._read_single_file_add_voltage)(fi_name, parallel=True) for fi_name in file_list)
|
|
823
|
+
for temp in _dat:
|
|
824
|
+
self._Sample_ids.extend(temp[0])
|
|
825
|
+
self.Elements.extend(temp[1])
|
|
826
|
+
self.Numbers.extend(temp[2])
|
|
827
|
+
self.Cells.extend(temp[3])
|
|
828
|
+
self.Coords.extend(temp[4])
|
|
829
|
+
self.Energies.extend(temp[5])
|
|
830
|
+
self.Forces.extend(temp[6])
|
|
831
|
+
self.Fixed.extend(temp[7])
|
|
832
|
+
self.Coords_type.extend(temp[8])
|
|
833
|
+
self.Labels.extend(temp[9])
|
|
834
|
+
if not self._Sample_ids: raise RuntimeError('All data are empty.')
|
|
835
|
+
self._update_indices()
|
|
836
|
+
self._check_id()
|
|
837
|
+
self._check_len()
|
|
838
|
+
if self.verbose > 0: print(f'Done. Total Time: {time.perf_counter() - t_st:>5.4f}')
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
class Xyz2Feat(BatchStructures):
|
|
842
|
+
"""
|
|
843
|
+
read xyz files to BatchStructures.
|
|
844
|
+
"""
|
|
845
|
+
def __init__(self, path: str, verbose: int = 1, has_cell: bool = False, has_forces: bool = False):
|
|
846
|
+
super().__init__()
|
|
847
|
+
#warnings.warn()
|
|
848
|
+
self.verbose = verbose
|
|
849
|
+
self.path = path
|
|
850
|
+
self.has_cell = has_cell
|
|
851
|
+
self.has_forces = has_forces
|
|
852
|
+
if has_forces: self.Forces = list()
|
|
853
|
+
|
|
854
|
+
def _read_single(self, file_name: str, ) -> Tuple[List[str], List, List, List,
|
|
855
|
+
List[List], List[List], List[np.ndarray], List]:
|
|
856
|
+
with open(os.path.join(self.path, file_name), 'r') as _f:
|
|
857
|
+
data = _f.readlines()
|
|
858
|
+
# match atoms number, energy of every structure and generate atom_number_all_list, Energy_list
|
|
859
|
+
atom_list = list()
|
|
860
|
+
labels_list = list()
|
|
861
|
+
elem_coo_list = list()
|
|
862
|
+
is_head = False
|
|
863
|
+
end_ptr = len(data)
|
|
864
|
+
ptr = 0
|
|
865
|
+
while True:
|
|
866
|
+
if ptr >= end_ptr:
|
|
867
|
+
break
|
|
868
|
+
dd = data[ptr]
|
|
869
|
+
if is_head: # next title info line
|
|
870
|
+
labels_list.append(dd) # label info
|
|
871
|
+
ptr += 1
|
|
872
|
+
coo_part = data[ptr: ptr + atom_list[-1]]
|
|
873
|
+
elem_coo_list.append(coo_part)
|
|
874
|
+
is_head = False
|
|
875
|
+
ptr += atom_list[-1]
|
|
876
|
+
continue
|
|
877
|
+
# if head
|
|
878
|
+
try_head = re.match(r"^\s*[0-9]+\n", dd) # pure int line
|
|
879
|
+
if try_head is not None:
|
|
880
|
+
atom_list.append(int(try_head.group()))
|
|
881
|
+
is_head = True
|
|
882
|
+
ptr += 1
|
|
883
|
+
continue
|
|
884
|
+
ptr += 1
|
|
885
|
+
|
|
886
|
+
# Match coordinate, Element_type ,Element_number and generate Coordinate_list, Element_type_Without_repetition_list, Element_Number_Without_repetition_list
|
|
887
|
+
coordinate_list = list()
|
|
888
|
+
forces_list = list()
|
|
889
|
+
element_type_without_repetition_list = list()
|
|
890
|
+
element_number_without_repetition_list = list()
|
|
891
|
+
idx = list()
|
|
892
|
+
for i, P in enumerate(elem_coo_list):
|
|
893
|
+
try:
|
|
894
|
+
position_list_ = [l.split()[1:] for l in P]
|
|
895
|
+
element_list_ = [l.split()[0] for l in P]
|
|
896
|
+
elem, _, numb = elem_list_reduce(element_list_)
|
|
897
|
+
element_type_without_repetition_list.append(elem)
|
|
898
|
+
element_number_without_repetition_list.append(numb)
|
|
899
|
+
pos_forc_array = np.array(position_list_, dtype=np.float32)
|
|
900
|
+
coordinate_list.append(pos_forc_array[:, :3])
|
|
901
|
+
if self.has_forces:
|
|
902
|
+
forces_list.append(pos_forc_array[:, 3:])
|
|
903
|
+
# idx
|
|
904
|
+
idx.append(file_name + '_' + str(i))
|
|
905
|
+
except Exception as e:
|
|
906
|
+
self.logger.warning(f"* Error occurred while loading {i}-th structure: {e}")
|
|
907
|
+
cells = [np.zeros((3, 3))] * len(elem_coo_list)
|
|
908
|
+
coo_type = ['C'] * len(elem_coo_list)
|
|
909
|
+
|
|
910
|
+
return (idx, cells, labels_list, forces_list, element_type_without_repetition_list,
|
|
911
|
+
element_number_without_repetition_list, coordinate_list, coo_type)
|
|
912
|
+
|
|
913
|
+
def read(self, file_list = None):
|
|
914
|
+
t_st = time.perf_counter()
|
|
915
|
+
if file_list is None:
|
|
916
|
+
file_list = os.listdir(self.path)
|
|
917
|
+
file_list = [f_ for f_ in file_list if os.path.isfile(os.path.join(self.path, f_))]
|
|
918
|
+
elif not isinstance(file_list, Sequence):
|
|
919
|
+
raise TypeError(f'Invalid type of files_list: {type(file_list)}')
|
|
920
|
+
|
|
921
|
+
err = 0
|
|
922
|
+
self.Labels = list()
|
|
923
|
+
for i, fil in enumerate(file_list):
|
|
924
|
+
try:
|
|
925
|
+
temp = self._read_single(fil)
|
|
926
|
+
self._Sample_ids.extend(temp[0])
|
|
927
|
+
self.Cells.extend(temp[1])
|
|
928
|
+
self.Labels.extend(temp[2])
|
|
929
|
+
if self.has_forces:
|
|
930
|
+
self.Forces.extend(temp[3])
|
|
931
|
+
self.Elements.extend(temp[4])
|
|
932
|
+
self.Numbers.extend(temp[5])
|
|
933
|
+
self.Coords.extend(temp[6])
|
|
934
|
+
self.Coords_type.extend(temp[7])
|
|
935
|
+
self.Fixed.extend([np.ones_like(_, dtype='|i1') for _ in temp[6]])
|
|
936
|
+
|
|
937
|
+
except Exception as e:
|
|
938
|
+
err += 1
|
|
939
|
+
warnings.warn(
|
|
940
|
+
f"An error occurred in {i}th file: {fil}, "
|
|
941
|
+
f"ERROR: {e}"
|
|
942
|
+
f" skipped. Total errors: {err}, {traceback.format_exc()}",
|
|
943
|
+
RuntimeWarning
|
|
944
|
+
)
|
|
945
|
+
if self.verbose > 0:
|
|
946
|
+
warnings.warn(f"Error: {e}")
|
|
947
|
+
self._update_indices()
|
|
948
|
+
self._check_id()
|
|
949
|
+
self._check_len()
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
class ExtXyz2Feat(BatchStructures):
|
|
953
|
+
"""
|
|
954
|
+
Read extxyz file with multiple structures.
|
|
955
|
+
"""
|
|
956
|
+
def __init__(self, path: str, verbose: int = 1):
|
|
957
|
+
super().__init__()
|
|
958
|
+
self.verbose = verbose
|
|
959
|
+
self.path = path
|
|
960
|
+
self.Energies = list()
|
|
961
|
+
self.Forces = list()
|
|
962
|
+
self.fix_mask_trans_func = np.vectorize(self._fixed_info_transformer)
|
|
963
|
+
self.FIXED_DICT = {'T': 1, 'F': 0}
|
|
964
|
+
|
|
965
|
+
@staticmethod
|
|
966
|
+
def _fixed_info_transformer(a: np.ndarray, fixed_dict: dict):
|
|
967
|
+
y = fixed_dict[a]
|
|
968
|
+
return y
|
|
969
|
+
|
|
970
|
+
def _read_single(
|
|
971
|
+
self,
|
|
972
|
+
file_name: str,
|
|
973
|
+
lattice_tag: str = 'lattice',
|
|
974
|
+
energy_tag: str = 'energy',
|
|
975
|
+
column_info_tag: str = 'properties',
|
|
976
|
+
element_tag: str = 'species',
|
|
977
|
+
coordinates_tag: str = 'pos',
|
|
978
|
+
forces_tag: str|None = None,
|
|
979
|
+
fixed_atom_tag: str|None = None
|
|
980
|
+
) -> Tuple[List[str], List, List, List, List[List], List[List], List[np.ndarray], List, List]:
|
|
981
|
+
"""
|
|
982
|
+
read single extxyz file.
|
|
983
|
+
Args:
|
|
984
|
+
file_name:
|
|
985
|
+
lattice_tag:
|
|
986
|
+
energy_tag:
|
|
987
|
+
column_info_tag:
|
|
988
|
+
element_tag:
|
|
989
|
+
coordinates_tag:
|
|
990
|
+
forces_tag:
|
|
991
|
+
fixed_atom_tag:
|
|
992
|
+
|
|
993
|
+
Returns:
|
|
994
|
+
|
|
995
|
+
"""
|
|
996
|
+
# save infos
|
|
997
|
+
idx = list()
|
|
998
|
+
Cell_list = list()
|
|
999
|
+
Energy_list = list()
|
|
1000
|
+
Elem_list = list()
|
|
1001
|
+
Number_list = list()
|
|
1002
|
+
Coords_list = list()
|
|
1003
|
+
Forces_list = list()
|
|
1004
|
+
Fixed_list = list()
|
|
1005
|
+
if not os.path.isfile(os.path.join(self.path, file_name)):
|
|
1006
|
+
warnings.warn(f'No OUTCAR file in given directory {os.path.join(self.path, file_name)}')
|
|
1007
|
+
return [], [], [], [], [], [], [], [], []
|
|
1008
|
+
|
|
1009
|
+
try:
|
|
1010
|
+
with open(os.path.join(self.path, file_name), 'r') as _f:
|
|
1011
|
+
data = _f.readlines()
|
|
1012
|
+
|
|
1013
|
+
line_now = 0 # flag of line now, start from 0
|
|
1014
|
+
i_structure = 0 # a counter
|
|
1015
|
+
while line_now + 1 < len(data):
|
|
1016
|
+
n_atom = int(data[line_now])
|
|
1017
|
+
info = data[line_now + 1]
|
|
1018
|
+
# PREFERENCES
|
|
1019
|
+
# cell
|
|
1020
|
+
cell = re.search(self.CELL_PARTTEN, info)
|
|
1021
|
+
assert cell is not None, 'Lattice was not found.'
|
|
1022
|
+
cell = cell.groups()[0].split()
|
|
1023
|
+
cell = np.asarray(cell, dtype=np.float32).reshape(3, 3)
|
|
1024
|
+
# properties & column split
|
|
1025
|
+
col_content = dict() # dict recorded column content that {name: (type, column_number_start, end)}
|
|
1026
|
+
col_split = re.search(self.PROP_PARTTEN, info)
|
|
1027
|
+
if col_split is not None:
|
|
1028
|
+
col_split = col_split.groups()[0].split(':')
|
|
1029
|
+
n_col = len(col_split)
|
|
1030
|
+
assert len(col_split)%3 == 0, 'Invalid properties information format.'
|
|
1031
|
+
_i_now = 0
|
|
1032
|
+
for i_col in range(0, n_col, 3):
|
|
1033
|
+
col_content[col_split[i_col]] = (col_split[i_col + 1], _i_now, _i_now + int(col_split[i_col + 2]))
|
|
1034
|
+
_i_now += int(col_split[i_col + 2])
|
|
1035
|
+
else:
|
|
1036
|
+
col_content = {
|
|
1037
|
+
f'{element_tag}': ('S', 0, 1),
|
|
1038
|
+
f'{coordinates_tag}': ('R', 1, 3)
|
|
1039
|
+
}
|
|
1040
|
+
# energy
|
|
1041
|
+
ener = re.search(self.ENER_PARTTEN, info)
|
|
1042
|
+
if ener is None:
|
|
1043
|
+
has_energy = False # a flag shown whether file contain energy.
|
|
1044
|
+
else:
|
|
1045
|
+
has_energy = True
|
|
1046
|
+
ener = float(ener.groups()[0])
|
|
1047
|
+
|
|
1048
|
+
# MAIN INFO
|
|
1049
|
+
main_info = np.asarray([_.split() for _ in data[line_now + 2 : line_now + 2 + n_atom]])
|
|
1050
|
+
# element & its number
|
|
1051
|
+
element_list_ = main_info[:, col_content[f'{element_tag}'][1]:col_content[f'{element_tag}'][2]].flatten()
|
|
1052
|
+
elements = [element_list_[0], ]
|
|
1053
|
+
numbers = list()
|
|
1054
|
+
count_ = 0
|
|
1055
|
+
for j, el in enumerate(element_list_):
|
|
1056
|
+
if el != elements[-1]:
|
|
1057
|
+
elements.append(el)
|
|
1058
|
+
numbers.append(count_)
|
|
1059
|
+
count_ = 1
|
|
1060
|
+
else:
|
|
1061
|
+
count_ += 1
|
|
1062
|
+
numbers.append(count_)
|
|
1063
|
+
# coords
|
|
1064
|
+
coords = main_info[:, col_content[f'{coordinates_tag}'][1]:col_content[f'{coordinates_tag}'][2]].astype(np.float32)
|
|
1065
|
+
# forces
|
|
1066
|
+
forces_info = col_content.get(f'{forces_tag}', None) if forces_tag is not None else None
|
|
1067
|
+
if forces_info is not None:
|
|
1068
|
+
forces = main_info[:, forces_info[1]:forces_info[2]].astype(np.float32)
|
|
1069
|
+
else:
|
|
1070
|
+
forces = None
|
|
1071
|
+
if forces_tag is not None:
|
|
1072
|
+
warnings.warn('`forces_tag` was set, while it cannot be found in the column information.', RuntimeWarning)
|
|
1073
|
+
# fixed masks
|
|
1074
|
+
mask_info = col_content.get(f'{fixed_atom_tag}', None) if fixed_atom_tag is not None else None
|
|
1075
|
+
if mask_info is not None:
|
|
1076
|
+
fixed = main_info[:, mask_info[1]:mask_info[2]]
|
|
1077
|
+
fixed: np.ndarray = self.fix_mask_trans_func(fixed, self.FIXED_DICT)
|
|
1078
|
+
if fixed.ndim != 2: raise RuntimeError('Unsupported format of atom fixing.')
|
|
1079
|
+
if fixed.shape[1:2] != (3, ):
|
|
1080
|
+
fixed = fixed.repeat(3, 1)
|
|
1081
|
+
fixed.astype(np.int8)
|
|
1082
|
+
else:
|
|
1083
|
+
fixed = np.ones_like(coords, dtype=np.int8)
|
|
1084
|
+
if fixed_atom_tag is not None:
|
|
1085
|
+
warnings.warn(
|
|
1086
|
+
'`mask_info` was set, while it cannot be found in the column information.'
|
|
1087
|
+
' All atoms will be viewed as free',
|
|
1088
|
+
RuntimeWarning
|
|
1089
|
+
)
|
|
1090
|
+
|
|
1091
|
+
# write data
|
|
1092
|
+
idx.append(f'{os.path.splitext(file_name)[0]}_{i_structure}')
|
|
1093
|
+
Energy_list.append(ener)
|
|
1094
|
+
Cell_list.append(cell)
|
|
1095
|
+
Coords_list.append(coords)
|
|
1096
|
+
Elem_list.append(elements)
|
|
1097
|
+
Number_list.append(numbers)
|
|
1098
|
+
Fixed_list.append(fixed)
|
|
1099
|
+
Forces_list.append(forces)
|
|
1100
|
+
|
|
1101
|
+
line_now += 2 + n_atom
|
|
1102
|
+
i_structure += 1
|
|
1103
|
+
|
|
1104
|
+
return idx, Cell_list, Energy_list, Forces_list, Elem_list, Number_list, Coords_list, Fixed_list, ['C'] * i_structure
|
|
1105
|
+
|
|
1106
|
+
except Exception as err:
|
|
1107
|
+
warnings.warn(f'An Error occurred when reading file {file_name}, skipped.\nError: {err}.')
|
|
1108
|
+
return [], [], [], [], [], [], [], [], []
|
|
1109
|
+
|
|
1110
|
+
def read(
|
|
1111
|
+
self,
|
|
1112
|
+
file_list = None,
|
|
1113
|
+
n_core: int = -1,
|
|
1114
|
+
backend: Literal['loky', 'threading', 'multiprocessing']|str = 'loky',
|
|
1115
|
+
lattice_tag: str = 'lattice',
|
|
1116
|
+
energy_tag: str = 'energy',
|
|
1117
|
+
column_info_tag: str = 'properties',
|
|
1118
|
+
element_tag: str = 'species',
|
|
1119
|
+
coordinates_tag: str = 'pos',
|
|
1120
|
+
forces_tag: str | None = None,
|
|
1121
|
+
fixed_atom_tag: str | None = None
|
|
1122
|
+
):
|
|
1123
|
+
"""
|
|
1124
|
+
Read *.extxyz files to BatchStructures.
|
|
1125
|
+
Args:
|
|
1126
|
+
file_list: List[str], the list of files to read. Default for all files under given path.
|
|
1127
|
+
n_core: int, the number of CPU cores used in reading files. -1 for all cores.
|
|
1128
|
+
backend: backend of parallel reading in `joblib`.
|
|
1129
|
+
lattice_tag: key-words of lattice information in *.extxyz file, at each structure's 2nd line.
|
|
1130
|
+
energy_tag: key-words of structure's energy in *.extxyz file.
|
|
1131
|
+
column_info_tag: key-words of column content information in *.extxyz file. The column content has such format:
|
|
1132
|
+
`content 1`:`data type 1`:`column occupied number 1`:`content 2`:`data type 2`:`column occupied number 2`: ...
|
|
1133
|
+
detailed see https://wiki.fysik.dtu.dk/ase/dev/_modules/ase/io/extxyz.html.
|
|
1134
|
+
element_tag: As a content in `column_info_tag`, name of element column.
|
|
1135
|
+
coordinates_tag: As a content in `column_info_tag`, name of atom coordinates column.
|
|
1136
|
+
forces_tag: As a content in `column_info_tag`, name of forces column. If the file does not contain forces, set it to None.
|
|
1137
|
+
fixed_atom_tag: As a content in `column_info_tag`, name of the mask column specified which atom was fixed.
|
|
1138
|
+
|
|
1139
|
+
Returns: None
|
|
1140
|
+
All data would update as class attributes.
|
|
1141
|
+
"""
|
|
1142
|
+
t_st = time.perf_counter()
|
|
1143
|
+
# file check
|
|
1144
|
+
if file_list is None:
|
|
1145
|
+
file_list = os.listdir(self.path)
|
|
1146
|
+
file_list = [f_ for f_ in file_list if os.path.isfile(os.path.join(self.path, f_))]
|
|
1147
|
+
elif not isinstance(file_list, Sequence):
|
|
1148
|
+
raise TypeError(f'Invalid type of files_list: {type(file_list)}')
|
|
1149
|
+
# para. set
|
|
1150
|
+
if n_core > len(file_list):
|
|
1151
|
+
warnings.warn(f'`n_core` is greater than file numbers, so `n_core` was reset to {len(file_list)}', RuntimeWarning)
|
|
1152
|
+
n_core = len(file_list)
|
|
1153
|
+
elif n_core == -1:
|
|
1154
|
+
n_core = jb.cpu_count(only_physical_cores=True)
|
|
1155
|
+
elif (not isinstance(n_core, int)) or n_core < -1:
|
|
1156
|
+
raise ValueError(f'Invalid `n_core` number: {n_core}.')
|
|
1157
|
+
# search pattern
|
|
1158
|
+
self.CELL_PARTTEN = re.compile(rf'{lattice_tag}\s?=\s?\"([-+E0-9.\s]+)\"', re.IGNORECASE)
|
|
1159
|
+
self.PROP_PARTTEN = re.compile(rf'{column_info_tag}\s?=\s?([a-zA-Z:0-9_]+)', re.IGNORECASE)
|
|
1160
|
+
self.ENER_PARTTEN = re.compile(rf'{energy_tag}\s?=\s?([-+.0-9e]+)')
|
|
1161
|
+
|
|
1162
|
+
_para = jb.Parallel(n_jobs=n_core, backend=backend, verbose=self.verbose)
|
|
1163
|
+
_dat = _para(jb.delayed(self._read_single)(
|
|
1164
|
+
fi_name,
|
|
1165
|
+
lattice_tag,
|
|
1166
|
+
energy_tag,
|
|
1167
|
+
column_info_tag,
|
|
1168
|
+
element_tag,
|
|
1169
|
+
coordinates_tag,
|
|
1170
|
+
forces_tag,
|
|
1171
|
+
fixed_atom_tag
|
|
1172
|
+
) for fi_name in file_list)
|
|
1173
|
+
# idx, Cell_list, Energy_list, Forces_list, Elem_list, Number_list, Coords_list, Coord_type
|
|
1174
|
+
for temp in _dat:
|
|
1175
|
+
self._Sample_ids.extend(temp[0])
|
|
1176
|
+
self.Cells.extend(temp[1])
|
|
1177
|
+
self.Energies.extend(temp[2])
|
|
1178
|
+
self.Forces.extend(temp[3])
|
|
1179
|
+
self.Elements.extend(temp[4])
|
|
1180
|
+
self.Numbers.extend(temp[5])
|
|
1181
|
+
self.Coords.extend(temp[6])
|
|
1182
|
+
self.Fixed.extend(temp[7])
|
|
1183
|
+
self.Coords_type.extend(temp[8])
|
|
1184
|
+
if not self._Sample_ids: raise RuntimeError('All data are empty.')
|
|
1185
|
+
self._update_indices()
|
|
1186
|
+
self._check_id()
|
|
1187
|
+
self._check_len()
|
|
1188
|
+
if self.verbose > 0: print(f'Done. Total Time: {time.perf_counter() - t_st:>5.4f}')
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
class Cif2Feat(BatchStructures):
|
|
1192
|
+
"""
|
|
1193
|
+
Read CIF file with multiple structures.
|
|
1194
|
+
Notes: CIF file could not contain the fixation information, so all atoms would be free.
|
|
1195
|
+
|
|
1196
|
+
"""
|
|
1197
|
+
|
|
1198
|
+
def __init__(self, path: str, verbose: int = 1):
|
|
1199
|
+
super().__init__()
|
|
1200
|
+
self.verbose = verbose
|
|
1201
|
+
self.path = path
|
|
1202
|
+
self.Energies = None
|
|
1203
|
+
self.Forces = None
|
|
1204
|
+
|
|
1205
|
+
def read(
|
|
1206
|
+
self,
|
|
1207
|
+
file_list = None,
|
|
1208
|
+
n_core: int = -1,
|
|
1209
|
+
backend: Literal['loky', 'threading', 'multiprocessing'] | str = 'loky',
|
|
1210
|
+
):
|
|
1211
|
+
t_st = time.perf_counter()
|
|
1212
|
+
# file check
|
|
1213
|
+
if file_list is None:
|
|
1214
|
+
file_list = os.listdir(self.path)
|
|
1215
|
+
file_list = [f_ for f_ in file_list if os.path.isfile(os.path.join(self.path, f_))]
|
|
1216
|
+
elif not isinstance(file_list, Sequence):
|
|
1217
|
+
raise TypeError(f'Invalid type of files_list: {type(file_list)}')
|
|
1218
|
+
# para. set
|
|
1219
|
+
if n_core > len(file_list):
|
|
1220
|
+
warnings.warn(f'`n_core` is greater than file numbers, so `n_core` was reset to {len(file_list)}', RuntimeWarning)
|
|
1221
|
+
n_core = len(file_list)
|
|
1222
|
+
elif n_core == -1:
|
|
1223
|
+
n_core = jb.cpu_count(only_physical_cores=True)
|
|
1224
|
+
elif (not isinstance(n_core, int)) or n_core < -1:
|
|
1225
|
+
raise ValueError(f'Invalid `n_core` number: {n_core}.')
|
|
1226
|
+
|
|
1227
|
+
_para = jb.Parallel(n_jobs=n_core, backend=backend, verbose=self.verbose)
|
|
1228
|
+
_dat = _para(jb.delayed(self._read_single)(fi_name) for fi_name in file_list)
|
|
1229
|
+
# file_name, cell, elements, elem_numbers, coo,
|
|
1230
|
+
for temp in _dat:
|
|
1231
|
+
if temp is not None:
|
|
1232
|
+
self._Sample_ids.append(temp[0])
|
|
1233
|
+
self.Cells.append(temp[1])
|
|
1234
|
+
self.Elements.append(temp[2])
|
|
1235
|
+
self.Numbers.append(temp[3])
|
|
1236
|
+
self.Coords.append(temp[4])
|
|
1237
|
+
self.Fixed.append(temp[5])
|
|
1238
|
+
if not self._Sample_ids: raise RuntimeError('All data are empty.')
|
|
1239
|
+
self.Coords_type = ['D'] * len(self._Sample_ids)
|
|
1240
|
+
self._update_indices()
|
|
1241
|
+
self._check_id()
|
|
1242
|
+
self._check_len()
|
|
1243
|
+
|
|
1244
|
+
if self.verbose > 0: print(f'Done. Total Time: {time.perf_counter() - t_st:>5.4f}')
|
|
1245
|
+
|
|
1246
|
+
def _read_single(
|
|
1247
|
+
self,
|
|
1248
|
+
file_name: str,
|
|
1249
|
+
):
|
|
1250
|
+
if not os.path.isfile(os.path.join(self.path, file_name)):
|
|
1251
|
+
warnings.warn(f'No OUTCAR file in given directory {os.path.join(self.path, file_name)}')
|
|
1252
|
+
return None
|
|
1253
|
+
|
|
1254
|
+
try:
|
|
1255
|
+
with open(os.path.join(self.path, file_name), 'r') as _f:
|
|
1256
|
+
data = _f.readlines()
|
|
1257
|
+
|
|
1258
|
+
# manage information
|
|
1259
|
+
cell_info_dict = dict()
|
|
1260
|
+
atom_info_seq_dict = dict()
|
|
1261
|
+
coords_info_list = list()
|
|
1262
|
+
i_seq = 0 # serial numbers of atom information
|
|
1263
|
+
is_read_atom = False # detect if reading atom coo. info.
|
|
1264
|
+
for dat in data[1:]:
|
|
1265
|
+
_data_list = dat.split()
|
|
1266
|
+
if _data_list[0] == 'loop_': is_read_atom = False
|
|
1267
|
+
if is_read_atom and _data_list[0][0] != '_':
|
|
1268
|
+
coords_info_list.append(_data_list)
|
|
1269
|
+
elif _data_list[0][:5] == '_cell':
|
|
1270
|
+
cell_info_dict[_data_list[0]] = _data_list[1]
|
|
1271
|
+
elif _data_list[0][:5] == '_atom':
|
|
1272
|
+
atom_info_seq_dict[_data_list[0]] = i_seq
|
|
1273
|
+
i_seq = i_seq + 1
|
|
1274
|
+
is_read_atom = True
|
|
1275
|
+
|
|
1276
|
+
# manage cell vectors
|
|
1277
|
+
a = float(cell_info_dict['_cell_length_a'])
|
|
1278
|
+
b = float(cell_info_dict['_cell_length_b'])
|
|
1279
|
+
c = float(cell_info_dict['_cell_length_c'])
|
|
1280
|
+
alpha = np.deg2rad(float(cell_info_dict['_cell_angle_alpha']))
|
|
1281
|
+
beta = np.deg2rad(float(cell_info_dict['_cell_angle_beta']))
|
|
1282
|
+
gamma = np.deg2rad(float(cell_info_dict['_cell_angle_gamma']))
|
|
1283
|
+
b1 = b * np.cos(gamma)
|
|
1284
|
+
b2 = np.sqrt(b**2 - b1**2)
|
|
1285
|
+
c1 = c * np.cos(beta)
|
|
1286
|
+
c2 = (b * c * np.cos(alpha) - b1 * c1)/b2
|
|
1287
|
+
c3 = np.sqrt(c**2 - c1**2 - c2**2)
|
|
1288
|
+
cell = np.array(
|
|
1289
|
+
[[a, 0., 0.],
|
|
1290
|
+
[b1, b2, 0.],
|
|
1291
|
+
[c1, c2, c3]],
|
|
1292
|
+
dtype=np.float32
|
|
1293
|
+
)
|
|
1294
|
+
|
|
1295
|
+
# manage coordinates
|
|
1296
|
+
coords_info_list = np.asarray(coords_info_list)
|
|
1297
|
+
ix, iy, iz = atom_info_seq_dict['_atom_site_fract_x'], atom_info_seq_dict['_atom_site_fract_y'], atom_info_seq_dict['_atom_site_fract_z']
|
|
1298
|
+
coo = coords_info_list[:, [ix, iy, iz]].astype(np.float32)
|
|
1299
|
+
fix = np.ones_like(coo, dtype=np.int8)
|
|
1300
|
+
atom_list = coords_info_list[:, atom_info_seq_dict['_atom_site_type_symbol']].tolist()
|
|
1301
|
+
# elements
|
|
1302
|
+
_elem_old = atom_list[0]
|
|
1303
|
+
elements = [atom_list[0], ]
|
|
1304
|
+
elem_numbers = list()
|
|
1305
|
+
count_i = 0
|
|
1306
|
+
for elem in atom_list:
|
|
1307
|
+
if elem == _elem_old:
|
|
1308
|
+
count_i += 1
|
|
1309
|
+
else:
|
|
1310
|
+
_elem_old = elem
|
|
1311
|
+
elem_numbers.append(count_i)
|
|
1312
|
+
elements.append(elem)
|
|
1313
|
+
count_i = 1
|
|
1314
|
+
elem_numbers.append(count_i)
|
|
1315
|
+
|
|
1316
|
+
return file_name, cell, elements, elem_numbers, coo, fix
|
|
1317
|
+
|
|
1318
|
+
except Exception as err:
|
|
1319
|
+
warnings.warn(f'An Error occurred when reading file {file_name}, skipped.\nError: {err}.\n{traceback.format_exc()}')
|
|
1320
|
+
return None
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
class ASETraj2Feat(BatchStructures):
|
|
1324
|
+
"""
|
|
1325
|
+
read ASE files to BatchStructures.
|
|
1326
|
+
"""
|
|
1327
|
+
|
|
1328
|
+
def __init__(self, path: str, verbose: int = 1) -> None:
|
|
1329
|
+
super().__init__()
|
|
1330
|
+
ase_io = check_module('ase.io')
|
|
1331
|
+
if ase_io is None:
|
|
1332
|
+
raise ImportError(f'`ASETraj2Feat` requires the ase package which is not found.')
|
|
1333
|
+
self.ase_io = ase_io
|
|
1334
|
+
self.path = path
|
|
1335
|
+
self.verbose = verbose
|
|
1336
|
+
self.Energies = list()
|
|
1337
|
+
self.Forces = list()
|
|
1338
|
+
|
|
1339
|
+
def _read_single(self, ase_file):
|
|
1340
|
+
try:
|
|
1341
|
+
idx = list()
|
|
1342
|
+
Cell_list = list()
|
|
1343
|
+
Energy_list = list()
|
|
1344
|
+
Elem_list = list()
|
|
1345
|
+
Number_list = list()
|
|
1346
|
+
Coords_list = list()
|
|
1347
|
+
Coords_type = list()
|
|
1348
|
+
Forces_list = list()
|
|
1349
|
+
Fixed_list = list()
|
|
1350
|
+
atom_list = self.ase_io.read(ase_file, ':')
|
|
1351
|
+
base_name = os.path.basename(ase_file)
|
|
1352
|
+
base_name, _ = os.path.splitext(base_name) # remove postfix
|
|
1353
|
+
for i, atm in enumerate(atom_list):
|
|
1354
|
+
if getattr(atm, 'calc', None) is None: # skip the energy-empty sample
|
|
1355
|
+
continue
|
|
1356
|
+
Cell_list.append(np.asarray(atm.cell, dtype=np.float32))
|
|
1357
|
+
Coords_list.append(atm.positions.astype(np.float32))
|
|
1358
|
+
coord_type = 'D' if np.all(np.abs(atm.positions) <= 1.) else 'C'
|
|
1359
|
+
Coords_type.append(coord_type)
|
|
1360
|
+
Energy_list.append(atm.get_potential_energy())
|
|
1361
|
+
Forces_list.append(np.asarray(atm.get_forces(), dtype=np.float32))
|
|
1362
|
+
fix = np.ones_like(atm.positions, dtype=np.int8)
|
|
1363
|
+
fix[atm.constraints[0].index] = 0
|
|
1364
|
+
Fixed_list.append(fix)
|
|
1365
|
+
elem_sym, atomic_num, atom_count = elem_list_reduce(atm.numbers)
|
|
1366
|
+
Elem_list.append(elem_sym)
|
|
1367
|
+
Number_list.append(atom_count)
|
|
1368
|
+
idx.append(f'{base_name}_{i}')
|
|
1369
|
+
|
|
1370
|
+
return idx, Cell_list, Energy_list, Forces_list, Elem_list, Number_list, Coords_list, Coords_type, Fixed_list
|
|
1371
|
+
|
|
1372
|
+
except Exception as err:
|
|
1373
|
+
warnings.warn(f'An Error occurred when reading file {ase_file}, skipped.\nError: {err}.')
|
|
1374
|
+
return [], [], [], [], [], [], [], [], []
|
|
1375
|
+
|
|
1376
|
+
def read(
|
|
1377
|
+
self,
|
|
1378
|
+
file_list = None,
|
|
1379
|
+
n_core: int = -1,
|
|
1380
|
+
backend: Literal['loky', 'threading', 'multiprocessing'] | str = 'loky',
|
|
1381
|
+
):
|
|
1382
|
+
t_st = time.perf_counter()
|
|
1383
|
+
# file check
|
|
1384
|
+
if file_list is None:
|
|
1385
|
+
file_list = os.listdir(self.path)
|
|
1386
|
+
file_list = [f_ for f_ in file_list if os.path.isfile(os.path.join(self.path, f_))]
|
|
1387
|
+
elif not isinstance(file_list, Sequence):
|
|
1388
|
+
raise TypeError(f'Invalid type of files_list: {type(file_list)}')
|
|
1389
|
+
# para. set
|
|
1390
|
+
if n_core > len(file_list):
|
|
1391
|
+
warnings.warn(f'`n_core` is greater than file numbers, so `n_core` was reset to {len(file_list)}', RuntimeWarning)
|
|
1392
|
+
n_core = len(file_list)
|
|
1393
|
+
elif n_core == -1:
|
|
1394
|
+
n_core = jb.cpu_count(only_physical_cores=True)
|
|
1395
|
+
elif (not isinstance(n_core, int)) or n_core < -1:
|
|
1396
|
+
raise ValueError(f'Invalid `n_core` number: {n_core}.')
|
|
1397
|
+
|
|
1398
|
+
_para = jb.Parallel(n_jobs=n_core, backend=backend, verbose=self.verbose)
|
|
1399
|
+
_dat = _para(jb.delayed(self._read_single)(os.path.join(self.path, fi_name)) for fi_name in file_list)
|
|
1400
|
+
# file_name, cell, elements, elem_numbers, coo,
|
|
1401
|
+
# idx, Cell_list, Energy_list, Forces_list, Elem_list, Number_list, Coords_list, Coord_type, fixed
|
|
1402
|
+
for temp in _dat:
|
|
1403
|
+
self._Sample_ids.extend(temp[0])
|
|
1404
|
+
self.Cells.extend(temp[1])
|
|
1405
|
+
self.Energies.extend(temp[2])
|
|
1406
|
+
self.Forces.extend(temp[3])
|
|
1407
|
+
self.Elements.extend(temp[4])
|
|
1408
|
+
self.Numbers.extend(temp[5])
|
|
1409
|
+
self.Coords.extend(temp[6])
|
|
1410
|
+
self.Coords_type.extend(temp[7])
|
|
1411
|
+
self.Fixed.extend(temp[8])
|
|
1412
|
+
if not self._Sample_ids: raise RuntimeError('All data are empty.')
|
|
1413
|
+
self._update_indices()
|
|
1414
|
+
self._check_id()
|
|
1415
|
+
self._check_len()
|
|
1416
|
+
|
|
1417
|
+
if self.verbose > 0: print(f'Done. Total Time: {time.perf_counter() - t_st:>5.4f}')
|
|
1418
|
+
|
|
1419
|
+
class Output2Feat(BatchStructures):
|
|
1420
|
+
"""
|
|
1421
|
+
A class of post-processing to reading output files of BatchOptimization and BatchMD
|
|
1422
|
+
"""
|
|
1423
|
+
def __init__(self, path: str, verbose: int = 1) -> None:
|
|
1424
|
+
super().__init__()
|
|
1425
|
+
self.path = path
|
|
1426
|
+
self.verbose = verbose
|
|
1427
|
+
self.Energies = list()
|
|
1428
|
+
self.Forces = list()
|
|
1429
|
+
|
|
1430
|
+
def read(self, file_list: List[str], n_core: int = -1, backend='loky'):
|
|
1431
|
+
t_st = time.perf_counter()
|
|
1432
|
+
# file check
|
|
1433
|
+
if file_list is None:
|
|
1434
|
+
file_list = os.listdir(self.path)
|
|
1435
|
+
file_list = [f_ for f_ in file_list if os.path.isfile(os.path.join(self.path, f_))]
|
|
1436
|
+
elif not isinstance(file_list, Sequence):
|
|
1437
|
+
raise TypeError(f'Invalid type of files_list: {type(file_list)}')
|
|
1438
|
+
# para. set
|
|
1439
|
+
if n_core > len(file_list):
|
|
1440
|
+
warnings.warn(f'`n_core` is greater than file numbers, so `n_core` was reset to {len(file_list)}', RuntimeWarning)
|
|
1441
|
+
n_core = len(file_list)
|
|
1442
|
+
elif n_core == -1:
|
|
1443
|
+
n_core = min(jb.cpu_count(only_physical_cores=True), len(file_list))
|
|
1444
|
+
elif (not isinstance(n_core, int)) or n_core < -1:
|
|
1445
|
+
raise ValueError(f'Invalid `n_core` number: {n_core}.')
|
|
1446
|
+
|
|
1447
|
+
_para = jb.Parallel(n_jobs=n_core, backend=backend, verbose=self.verbose)
|
|
1448
|
+
_dat = _para(jb.delayed(self._read_single)(fi_name) for fi_name in file_list)
|
|
1449
|
+
# file_name, cell, elements, elem_numbers, coo,
|
|
1450
|
+
# samp_id, cell, elements, elem_numbers, energies, forces, coo, fix
|
|
1451
|
+
is_no_force = False
|
|
1452
|
+
for temp in _dat:
|
|
1453
|
+
self._Sample_ids.extend(temp[0])
|
|
1454
|
+
self.Cells.extend(temp[1])
|
|
1455
|
+
self.Elements.extend(temp[2])
|
|
1456
|
+
self.Numbers.extend(temp[3])
|
|
1457
|
+
self.Energies.extend(temp[4])
|
|
1458
|
+
if len(temp[5]) != 0:
|
|
1459
|
+
self.Forces.extend(temp[5])
|
|
1460
|
+
else:
|
|
1461
|
+
is_no_force = True
|
|
1462
|
+
warnings.warn(f'Forces are not output in some files, so that all forces are set to None.')
|
|
1463
|
+
self.Coords.extend(temp[6])
|
|
1464
|
+
self.Fixed.extend(temp[7])
|
|
1465
|
+
if is_no_force:
|
|
1466
|
+
self.Forces = None
|
|
1467
|
+
self.Coords_type = ['C'] * len(self)
|
|
1468
|
+
if not self._Sample_ids: raise RuntimeError('All data are empty.')
|
|
1469
|
+
self._update_indices()
|
|
1470
|
+
self._check_id()
|
|
1471
|
+
self._check_len()
|
|
1472
|
+
|
|
1473
|
+
if self.verbose > 0: print(f'Done. Total Time: {time.perf_counter() - t_st:>5.4f}')
|
|
1474
|
+
pass
|
|
1475
|
+
|
|
1476
|
+
def _read_single(self, file_name):
|
|
1477
|
+
if not os.path.isfile(os.path.join(self.path, file_name)):
|
|
1478
|
+
warnings.warn(f'No output file in given directory {os.path.join(self.path, file_name)}')
|
|
1479
|
+
return None
|
|
1480
|
+
samp_id = list()
|
|
1481
|
+
cell = list()
|
|
1482
|
+
elements = list()
|
|
1483
|
+
elem_numbers = list()
|
|
1484
|
+
energies = list()
|
|
1485
|
+
forces = list()
|
|
1486
|
+
coo = list()
|
|
1487
|
+
fix = list()
|
|
1488
|
+
number_pattern = re.compile(r'[\s\-.0-9]+')
|
|
1489
|
+
try:
|
|
1490
|
+
with open(os.path.join(self.path, file_name), 'r') as _f:
|
|
1491
|
+
data = _f.readlines()
|
|
1492
|
+
# identify tasks
|
|
1493
|
+
assert len(data) > 0, f'Empty data occurred.'
|
|
1494
|
+
task_tag = None
|
|
1495
|
+
is_task_opt = None
|
|
1496
|
+
is_task_md = None
|
|
1497
|
+
for line in data:
|
|
1498
|
+
tmp = re.search(r'VERBOSITY LEVEL: (.*)$', line)
|
|
1499
|
+
if tmp is not None:
|
|
1500
|
+
data_verbosity = int(tmp.group(1))
|
|
1501
|
+
continue
|
|
1502
|
+
tmp = re.search(r'TASK: Structure Optimization <<', line)
|
|
1503
|
+
if tmp is not None:
|
|
1504
|
+
is_task_opt = tmp
|
|
1505
|
+
continue
|
|
1506
|
+
tmp = re.search(r'TASK: Molecular Dynamics <<', line)
|
|
1507
|
+
if tmp is not None:
|
|
1508
|
+
is_task_md = tmp
|
|
1509
|
+
continue
|
|
1510
|
+
EOF = re.match(r'^ENTERING MAIN', line) # match the end of head information
|
|
1511
|
+
if EOF is not None:
|
|
1512
|
+
break
|
|
1513
|
+
if is_task_opt is not None:
|
|
1514
|
+
task_tag = 'opt'
|
|
1515
|
+
Id_pattern = re.compile(r'Structure names:\s\[(.+)]')
|
|
1516
|
+
Elem_tag = re.compile(r'Structure\s*[0-9]+:\s*(.*)$')
|
|
1517
|
+
Iter_pattern = re.compile(r'ITERATION\s*([0-9]+)')
|
|
1518
|
+
E_pattern = re.compile(r'\s*Energies: +\[([+\s0-9e.-]+)]')
|
|
1519
|
+
F_tag = re.compile(r'\s*Forces:')
|
|
1520
|
+
Coo_tag = re.compile(r'\s*Coordinates:')
|
|
1521
|
+
Cell_tag = re.compile(r'Cell Vectors:')
|
|
1522
|
+
Final_Coo_tag = re.compile(r'^ Final Coordinates:')
|
|
1523
|
+
Final_F_tag = re.compile(r'^ Final Forces:')
|
|
1524
|
+
elif is_task_md is not None:
|
|
1525
|
+
task_tag = 'md'
|
|
1526
|
+
raise NotImplementedError
|
|
1527
|
+
if task_tag is None: raise RuntimeError(f'Unknown task type.')
|
|
1528
|
+
|
|
1529
|
+
i_batch = -1
|
|
1530
|
+
# MAIN LOOP
|
|
1531
|
+
for i, line in enumerate(data):
|
|
1532
|
+
is_name = re.match(Id_pattern, line)
|
|
1533
|
+
if is_name is not None: # it is the name line
|
|
1534
|
+
name = re.split(', *', is_name.group(1).replace('\'', ''))
|
|
1535
|
+
i_batch += 1
|
|
1536
|
+
n_batch = len(name)
|
|
1537
|
+
cell_temp = list()
|
|
1538
|
+
elements_temp = list()
|
|
1539
|
+
elem_numbers_temp = list()
|
|
1540
|
+
continue
|
|
1541
|
+
# cell
|
|
1542
|
+
is_cell = re.match(Cell_tag, line)
|
|
1543
|
+
if is_cell is not None:
|
|
1544
|
+
_cell = list()
|
|
1545
|
+
for _sub_line in data[i+1:]:
|
|
1546
|
+
is_empty = re.match(r'^\s*$', _sub_line)
|
|
1547
|
+
if is_empty is not None:
|
|
1548
|
+
cell_temp.append(np.asarray(_cell, dtype=np.float32))
|
|
1549
|
+
_cell = list()
|
|
1550
|
+
continue
|
|
1551
|
+
is_numbers = re.match(number_pattern, _sub_line)
|
|
1552
|
+
if is_numbers is not None:
|
|
1553
|
+
_numbers = is_numbers.group().split()
|
|
1554
|
+
_cell.append(_numbers)
|
|
1555
|
+
else:
|
|
1556
|
+
break # neither empty line nor numbers, thus skipping
|
|
1557
|
+
continue
|
|
1558
|
+
# element & numbers
|
|
1559
|
+
is_elem = re.match(Elem_tag, line)
|
|
1560
|
+
if is_elem is not None:
|
|
1561
|
+
_elem = list()
|
|
1562
|
+
_number = list()
|
|
1563
|
+
for ii, _ in enumerate(is_elem.group(1).replace(':', '').split()):
|
|
1564
|
+
if ii % 2 == 0:
|
|
1565
|
+
_elem.append(_)
|
|
1566
|
+
else:
|
|
1567
|
+
_number.append(int(_))
|
|
1568
|
+
elements_temp.append(_elem)
|
|
1569
|
+
elem_numbers_temp.append(_number)
|
|
1570
|
+
continue
|
|
1571
|
+
# Main iterations. Here each iteration would repeat the global information, e.g., cell, elements, numbers, etc.
|
|
1572
|
+
if data_verbosity > 1: # for verbosity == 1, only the last coords. will be output
|
|
1573
|
+
is_in_iter = re.match(Iter_pattern, line)
|
|
1574
|
+
if is_in_iter is not None:
|
|
1575
|
+
_samp_id = is_in_iter.group(1)
|
|
1576
|
+
for ii in range(n_batch):
|
|
1577
|
+
samp_id.append(f'{file_name}_{name[ii]}_iter{_samp_id}')
|
|
1578
|
+
cell.append(cell_temp[-n_batch + ii])
|
|
1579
|
+
elements.append(elements_temp[-n_batch + ii])
|
|
1580
|
+
elem_numbers.append(elem_numbers_temp[-n_batch + ii])
|
|
1581
|
+
continue
|
|
1582
|
+
# energy
|
|
1583
|
+
is_energy = re.match(E_pattern, line)
|
|
1584
|
+
if is_energy is not None:
|
|
1585
|
+
_ener = is_energy.group(1).split()
|
|
1586
|
+
for _ in _ener:
|
|
1587
|
+
energies.append(float(_))
|
|
1588
|
+
continue
|
|
1589
|
+
# coords
|
|
1590
|
+
is_coo = re.match(Coo_tag, line)
|
|
1591
|
+
if is_coo is not None:
|
|
1592
|
+
_coo = list()
|
|
1593
|
+
for _sub_line in data[i+2:]:
|
|
1594
|
+
is_empty = re.match(r'^\s*$', _sub_line)
|
|
1595
|
+
if is_empty is not None:
|
|
1596
|
+
if len(_coo[0]) == 0: continue # avoid multiple empty lines are read as coordinates
|
|
1597
|
+
_coo = np.asarray(_coo, dtype=np.float32)
|
|
1598
|
+
coo.append(_coo)
|
|
1599
|
+
fix.append(np.ones_like(_coo, dtype=np.int8))
|
|
1600
|
+
_coo = list()
|
|
1601
|
+
continue
|
|
1602
|
+
is_numbers = re.match(number_pattern, _sub_line)
|
|
1603
|
+
if is_numbers is not None:
|
|
1604
|
+
_numbers = is_numbers.group().split()
|
|
1605
|
+
_coo.append(_numbers)
|
|
1606
|
+
else:
|
|
1607
|
+
break # neither empty line nor numbers, thus skipping
|
|
1608
|
+
continue
|
|
1609
|
+
# forces
|
|
1610
|
+
is_forc = re.match(F_tag, line)
|
|
1611
|
+
if is_forc is not None:
|
|
1612
|
+
_forc = list()
|
|
1613
|
+
for _sub_line in data[i + 2:]:
|
|
1614
|
+
is_empty = re.match(r'^\s*$', _sub_line)
|
|
1615
|
+
if is_empty is not None:
|
|
1616
|
+
forces.append(np.asarray(_forc, dtype=np.float32))
|
|
1617
|
+
_forc = list()
|
|
1618
|
+
continue
|
|
1619
|
+
is_numbers = re.match(number_pattern, _sub_line)
|
|
1620
|
+
if is_numbers is not None:
|
|
1621
|
+
_numbers = is_numbers.group().split()
|
|
1622
|
+
_forc.append(_numbers)
|
|
1623
|
+
else:
|
|
1624
|
+
break # neither empty line nor numbers, thus skipping
|
|
1625
|
+
continue
|
|
1626
|
+
else: # only got the final coords & forces
|
|
1627
|
+
# energy
|
|
1628
|
+
is_energy = re.match(E_pattern, line)
|
|
1629
|
+
if is_energy is not None:
|
|
1630
|
+
_ener = is_energy.group(1).split()
|
|
1631
|
+
is_final = re.match(Final_Coo_tag, line)
|
|
1632
|
+
if is_final is not None:
|
|
1633
|
+
for ii in range(n_batch):
|
|
1634
|
+
samp_id.append(f'{file_name}_{name[ii]}')
|
|
1635
|
+
cell.append(cell_temp[-n_batch + ii])
|
|
1636
|
+
elements.append(elements_temp[-n_batch + ii])
|
|
1637
|
+
elem_numbers.append(elem_numbers_temp[-n_batch + ii])
|
|
1638
|
+
energies.append(float(_ener[ii]))
|
|
1639
|
+
_coo = list()
|
|
1640
|
+
for _sub_line in data[i + 2:]:
|
|
1641
|
+
is_empty = re.match(r'^\s*$', _sub_line)
|
|
1642
|
+
if is_empty is not None:
|
|
1643
|
+
if len(_coo[0]) == 0: continue # avoid multiple empty lines are read as coordinates
|
|
1644
|
+
_coo = np.asarray(_coo, dtype=np.float32)
|
|
1645
|
+
coo.append(_coo)
|
|
1646
|
+
fix.append(np.ones_like(_coo, dtype=np.int8))
|
|
1647
|
+
_coo = list()
|
|
1648
|
+
continue
|
|
1649
|
+
is_numbers = re.match(number_pattern, _sub_line)
|
|
1650
|
+
if is_numbers is not None:
|
|
1651
|
+
_numbers = is_numbers.group().split()
|
|
1652
|
+
_coo.append(_numbers)
|
|
1653
|
+
else:
|
|
1654
|
+
break # neither empty line nor numbers, thus skipping
|
|
1655
|
+
continue
|
|
1656
|
+
|
|
1657
|
+
is_forc = re.match(Final_F_tag, line)
|
|
1658
|
+
if is_forc is not None:
|
|
1659
|
+
_forc = list()
|
|
1660
|
+
for _sub_line in data[i + 2:]:
|
|
1661
|
+
is_empty = re.match(r'^\s*$', _sub_line)
|
|
1662
|
+
if is_empty is not None:
|
|
1663
|
+
forces.append(np.asarray(_forc, dtype=np.float32))
|
|
1664
|
+
_forc = list()
|
|
1665
|
+
continue
|
|
1666
|
+
is_numbers = re.match(number_pattern, _sub_line)
|
|
1667
|
+
if is_numbers is not None:
|
|
1668
|
+
_numbers = is_numbers.group().split()
|
|
1669
|
+
_forc.append(_numbers)
|
|
1670
|
+
else:
|
|
1671
|
+
break # neither empty line nor numbers, thus skipping
|
|
1672
|
+
continue
|
|
1673
|
+
|
|
1674
|
+
return samp_id, cell, elements, elem_numbers, energies, forces, coo, fix
|
|
1675
|
+
|
|
1676
|
+
except Exception as err:
|
|
1677
|
+
warnings.warn(f'An Error occurred when reading file {file_name}, skipped.\nError: {err}.')
|
|
1678
|
+
excp = traceback.format_exc()
|
|
1679
|
+
warnings.warn(f"Traceback:\n{excp}")
|
|
1680
|
+
return None
|