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,524 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025.7.4, BUCToolkit.
|
|
2
|
+
# Authors: Pu Pengxin, Song Xin
|
|
3
|
+
# Version: 0.9a
|
|
4
|
+
# File: write_files.py
|
|
5
|
+
# Environment: Python 3.12
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import warnings
|
|
9
|
+
from typing import Sequence, List, Literal, Tuple
|
|
10
|
+
import joblib as jb
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
# from BUCToolkit.BatchStructures.BatchStructuresBase import BatchStructures
|
|
14
|
+
from BUCToolkit.utils._print_formatter import STRING_ARRAY_FORMAT, AS_PRINT_COORDS, FLOAT_ARRAY_FORMAT
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class WritePOSCARs:
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
cells: Sequence[Sequence],
|
|
25
|
+
coords: Sequence[np.ndarray],
|
|
26
|
+
atom_labels: Sequence[Sequence[str]],
|
|
27
|
+
atom_numbers: Sequence[Sequence[int]],
|
|
28
|
+
fixed: Sequence[np.ndarray] | None = None,
|
|
29
|
+
output_path: str = './',
|
|
30
|
+
file_name_list: str | Sequence[str] = 'POSCAR',
|
|
31
|
+
system_list: str | Sequence[str] = 'untitled',
|
|
32
|
+
coord_type: List[Literal['C', 'D']] | Literal['C', 'D'] = 'C',
|
|
33
|
+
ncore: int = -1
|
|
34
|
+
) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Convert coordinates matrices to POSCAR format, and write files to the "output_path".
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
cells: List|ndarray, a batch of lattice vectors. shape: (n_batch, 3, 3)
|
|
40
|
+
atom_labels: 2D list|ndarray of str, a batch of list of element symbols. shape: (n_batch, n_atom)
|
|
41
|
+
atom_numbers:2D list|ndarray of int, list of atom number of each element, in the order of atom_label. shape: (n_batch, n_atom)
|
|
42
|
+
coords: list|ndarray, the batch of atoms coordinates, in the order of atom_label. shape: (n_batch, n_atom, 3)
|
|
43
|
+
fixed: list|ndarray|None, the batch of atoms fixed directions, in the order of atom_label. shape: (n_batch, n_atom, 3), dtype: int.
|
|
44
|
+
`0` for fixed, `1` for free. if None, `fixed` would fill with `1`.
|
|
45
|
+
output_path: str, the output path.
|
|
46
|
+
file_name_list: list(str), the list of file names.
|
|
47
|
+
system_list: str|list(str), the 1st line of output file i.e., the annotation or title of the file.
|
|
48
|
+
coord_type: List[Literal['C', 'D']]|Literal['C', 'D'], 'D' or 'C', which means whether the input coordinates are "Direct" or "Cartesian".
|
|
49
|
+
If only a string, `coord_type` would set to be '[`coord_type`] * n_batch'.
|
|
50
|
+
ncore: int, the number of CPU cores to write files in parallel.
|
|
51
|
+
|
|
52
|
+
Returns: None
|
|
53
|
+
"""
|
|
54
|
+
# check vars
|
|
55
|
+
n_batch = len(cells)
|
|
56
|
+
if isinstance(file_name_list, str):
|
|
57
|
+
file_name_list = [file_name_list + str(i) for i in range(n_batch)]
|
|
58
|
+
if isinstance(system_list, str):
|
|
59
|
+
system_list = [system_list] * n_batch
|
|
60
|
+
if not (n_batch == len(coords) == len(atom_labels) == len(atom_numbers)):
|
|
61
|
+
raise ValueError(f'number of cells in cell vector, atom coordinates, atom labels, and atom_numbers should be the same,\
|
|
62
|
+
but occurred {n_batch, len(coords), len(atom_labels), len(atom_numbers)}')
|
|
63
|
+
if isinstance(coord_type, str):
|
|
64
|
+
coord_type = [coord_type] * n_batch
|
|
65
|
+
elif (not isinstance(coord_type, (List, Tuple))) or (len(coord_type) != n_batch):
|
|
66
|
+
raise ValueError(f'Invalid value of `coord_type`: type: {type(coord_type)}, length: {len(coord_type)}.'
|
|
67
|
+
f' It should be type: List | Tuple, length: {n_batch}')
|
|
68
|
+
if fixed is None:
|
|
69
|
+
fixed = [np.full_like(_, 1, dtype=np.int8) for _ in coords]
|
|
70
|
+
# check len
|
|
71
|
+
if isinstance(file_name_list, Sequence):
|
|
72
|
+
if not (len(file_name_list) == len(cells) == len(atom_labels) == len(atom_numbers) == len(coords) == len(system_list) == len(fixed)):
|
|
73
|
+
raise ValueError(
|
|
74
|
+
f'Inconsistent length of inputs.'
|
|
75
|
+
f'length of file_name_list, cells, atom_labels, atom_numbers, coords, system_list, fixed: '
|
|
76
|
+
f'{
|
|
77
|
+
(len(file_name_list), len(cells), len(atom_labels), len(atom_numbers), len(coords), len(system_list), len(fixed))
|
|
78
|
+
}'
|
|
79
|
+
)
|
|
80
|
+
# check parallel
|
|
81
|
+
tot_cores = jb.cpu_count()
|
|
82
|
+
if not isinstance(ncore, int):
|
|
83
|
+
raise TypeError(f'`ncore` must be an integer, but occurred {type(ncore)}.')
|
|
84
|
+
elif ncore == -1:
|
|
85
|
+
ncore = tot_cores
|
|
86
|
+
elif ncore > tot_cores:
|
|
87
|
+
warnings.warn('Input `ncore` is greater than total CPU cores and was set to total CPU cores automatically.', RuntimeWarning)
|
|
88
|
+
ncore = tot_cores
|
|
89
|
+
|
|
90
|
+
if ncore != 1:
|
|
91
|
+
_para = jb.Parallel(ncore, backend="threading")
|
|
92
|
+
_para(
|
|
93
|
+
jb.delayed(self.__write)(
|
|
94
|
+
cell,
|
|
95
|
+
coords[i],
|
|
96
|
+
fixed[i],
|
|
97
|
+
atom_labels[i],
|
|
98
|
+
atom_numbers[i],
|
|
99
|
+
output_path=output_path,
|
|
100
|
+
file_name=file_name_list[i],
|
|
101
|
+
system=system_list[i],
|
|
102
|
+
coord_type=coord_type[i]
|
|
103
|
+
)
|
|
104
|
+
for i, cell in enumerate(cells)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
else:
|
|
108
|
+
for i in range(n_batch):
|
|
109
|
+
self.__write(
|
|
110
|
+
cells[i],
|
|
111
|
+
coords[i],
|
|
112
|
+
fixed[i],
|
|
113
|
+
atom_labels[i],
|
|
114
|
+
atom_numbers[i],
|
|
115
|
+
output_path=output_path,
|
|
116
|
+
file_name=file_name_list[i],
|
|
117
|
+
system=system_list[i],
|
|
118
|
+
coord_type=coord_type[i]
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def __write(cell: Sequence,
|
|
125
|
+
coord: np.ndarray,
|
|
126
|
+
fixed: np.ndarray,
|
|
127
|
+
atom_label: Sequence,
|
|
128
|
+
atom_number: Sequence,
|
|
129
|
+
output_path: str,
|
|
130
|
+
file_name: str,
|
|
131
|
+
system: str,
|
|
132
|
+
coord_type: str) -> None:
|
|
133
|
+
"""
|
|
134
|
+
Convert coordinates matrix to POSCAR format, and write a file to the output_path.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
cell: list|ndarray, the lattice vector of cell.
|
|
138
|
+
atom_label:1D list|ndarray of str, list of element symbols.
|
|
139
|
+
atom_number:1D list|ndarray of int, list of atom number of each element, in the order of atom_label.
|
|
140
|
+
coord: list|ndarray, the coordinates of atoms, in the order of atom_label.
|
|
141
|
+
fixed: list|ndarray, the fixed direction of atoms.
|
|
142
|
+
output_path: str, the output path.
|
|
143
|
+
file_name: str, the file name.
|
|
144
|
+
system: str, the 1st line of an output file i.e., the annotation or title of the file.
|
|
145
|
+
coord_type: str, 'D' or 'C', which means whether the coordinates are "Direct" or "Cartesian".
|
|
146
|
+
|
|
147
|
+
Return: None
|
|
148
|
+
"""
|
|
149
|
+
# check vars
|
|
150
|
+
if not isinstance(cell, (list, np.ndarray)):
|
|
151
|
+
raise TypeError(f'Unknown type of cell, type : {type(cell)}')
|
|
152
|
+
if isinstance(coord, List):
|
|
153
|
+
coord = np.asarray(coord, dtype=np.float32)
|
|
154
|
+
elif not isinstance(coord, np.ndarray):
|
|
155
|
+
raise TypeError(f'Unknown type of coord, type : {type(coord)}')
|
|
156
|
+
if not (isinstance(output_path, str) and isinstance(file_name, str) and isinstance(system, str)):
|
|
157
|
+
raise TypeError(f'output_path||file_name||system must be strings, '
|
|
158
|
+
f'but got type {type(output_path)}||{type(file_name)}||{type(system)}.')
|
|
159
|
+
elif coord_type != 'C' and coord_type != 'D':
|
|
160
|
+
raise ValueError(f'Unknown coord_type : "{coord_type}"')
|
|
161
|
+
|
|
162
|
+
# main
|
|
163
|
+
with open(os.path.join(output_path, file_name), 'w') as POSCAR:
|
|
164
|
+
POSCAR.write(system)
|
|
165
|
+
POSCAR.write('\n 1\n')
|
|
166
|
+
# cell
|
|
167
|
+
for vx in cell:
|
|
168
|
+
for xx in vx:
|
|
169
|
+
POSCAR.write(f' {xx:0< 14.8f}')
|
|
170
|
+
POSCAR.write('\n')
|
|
171
|
+
# atom element
|
|
172
|
+
for label in atom_label:
|
|
173
|
+
POSCAR.write(f' {label: <6s}')
|
|
174
|
+
POSCAR.write('\n')
|
|
175
|
+
for label in atom_number:
|
|
176
|
+
POSCAR.write(f' {label: <6d}')
|
|
177
|
+
POSCAR.write('\n')
|
|
178
|
+
# selective dynamics
|
|
179
|
+
POSCAR.write('Selective Dynamics\n')
|
|
180
|
+
if coord_type == 'C':
|
|
181
|
+
POSCAR.write('Cartesian\n')
|
|
182
|
+
elif coord_type == 'D':
|
|
183
|
+
POSCAR.write('Direct\n')
|
|
184
|
+
else: raise ValueError(f'Invalid `coord_type`: {coord_type}')
|
|
185
|
+
# atom coordinates
|
|
186
|
+
fixed = np.where(fixed == 1, 'T', 'F')
|
|
187
|
+
coord_str = AS_PRINT_COORDS(coord[:, :3])
|
|
188
|
+
print_arr = np.concatenate((coord_str, fixed[:, :3]), axis=1)
|
|
189
|
+
print_str = np.array2string(print_arr, **STRING_ARRAY_FORMAT).replace('[', ' ').replace(']', ' ')
|
|
190
|
+
POSCAR.write(print_str)
|
|
191
|
+
POSCAR.write('\n')
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class Write2JDFTX:
|
|
195
|
+
|
|
196
|
+
def __init__(self, ):
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
def write(self,
|
|
200
|
+
batch_structures,
|
|
201
|
+
output_path: str = './',
|
|
202
|
+
file_name_list: str | Sequence[str] = 'POSCAR',
|
|
203
|
+
system_list: str | Sequence[str] = 'untitled',
|
|
204
|
+
coord_type: str = 'C') -> None:
|
|
205
|
+
"""
|
|
206
|
+
Convert coordinates matrices to POSCAR format, and write files to the "output_path".
|
|
207
|
+
|
|
208
|
+
Parameters:
|
|
209
|
+
batch_structures: batch of structures.
|
|
210
|
+
output_path: str, the output path.
|
|
211
|
+
file_name_list: list(str), the list of file names.
|
|
212
|
+
system_list: str|list(str), the 1st line of output file i.e., the annotation or title of the file.
|
|
213
|
+
coord_type: str, 'D' or 'C', which means whether the coordinates is "Direct" or "Cartesian".
|
|
214
|
+
|
|
215
|
+
Returns: None
|
|
216
|
+
"""
|
|
217
|
+
batch_structures.generate_atom_list()
|
|
218
|
+
n_batch = len(batch_structures.Cells)
|
|
219
|
+
if isinstance(file_name_list, str):
|
|
220
|
+
file_name_list = [file_name_list + str(i) for i in range(n_batch)]
|
|
221
|
+
if isinstance(system_list, str):
|
|
222
|
+
system_list = [system_list] * n_batch
|
|
223
|
+
if not (n_batch == len(batch_structures.Coords) == len(batch_structures.Elements) == len(batch_structures.Numbers)):
|
|
224
|
+
raise ValueError(f'number of cells in cell vector, atom coordinates, atom labels, and atom_numbers should be the same, '
|
|
225
|
+
f'but occurred {n_batch, len(batch_structures.Coords), len(batch_structures.Elements), len(batch_structures.Numbers)}')
|
|
226
|
+
|
|
227
|
+
for i in range(n_batch):
|
|
228
|
+
self.__write(batch_structures.Cells[i],
|
|
229
|
+
batch_structures.Coords_type[i],
|
|
230
|
+
batch_structures.Coords[i],
|
|
231
|
+
batch_structures.Fixed[i],
|
|
232
|
+
batch_structures.Atom_list[i],
|
|
233
|
+
output_path=output_path,
|
|
234
|
+
file_name=file_name_list[i],
|
|
235
|
+
system=system_list[i],
|
|
236
|
+
out_coord_type=coord_type)
|
|
237
|
+
|
|
238
|
+
pass
|
|
239
|
+
|
|
240
|
+
@staticmethod
|
|
241
|
+
def __write(cell: Sequence,
|
|
242
|
+
in_coord_type: str,
|
|
243
|
+
coord: np.ndarray,
|
|
244
|
+
fixed: np.ndarray,
|
|
245
|
+
atom_list: List,
|
|
246
|
+
output_path: str,
|
|
247
|
+
file_name: str,
|
|
248
|
+
system: str,
|
|
249
|
+
out_coord_type: str,
|
|
250
|
+
) -> None:
|
|
251
|
+
"""
|
|
252
|
+
Convert coordinates matrix to POSCAR format, and write a file to the output_path.
|
|
253
|
+
|
|
254
|
+
Parameters:
|
|
255
|
+
cell: list|ndarray|torchTensor, the lattice vector of cell.
|
|
256
|
+
coord: list|ndarray|torchTensor, the coordinates of atoms, in the order of atom_label.
|
|
257
|
+
atom_list: list|
|
|
258
|
+
output_path: str, the output path.
|
|
259
|
+
file_name: str, the file name.
|
|
260
|
+
system: str, the 1st line of output file i.e., the annotation or title of the file.
|
|
261
|
+
out_coord_type: str, 'D' or 'C', which means whether the coordinate type is "Direct" or "Cartesian".
|
|
262
|
+
|
|
263
|
+
Return: None
|
|
264
|
+
"""
|
|
265
|
+
# check vars
|
|
266
|
+
if not isinstance(cell, (list, np.ndarray)):
|
|
267
|
+
raise TypeError(f'Unknown type of cell, type : {type(cell)}')
|
|
268
|
+
elif not isinstance(coord, (list, np.ndarray)):
|
|
269
|
+
raise TypeError(f'Unknown type of coord, type : {type(coord)}')
|
|
270
|
+
elif not (isinstance(output_path, str) and isinstance(file_name, str) and isinstance(system, str)):
|
|
271
|
+
raise TypeError(
|
|
272
|
+
f'output_path||file_name||system must be strings, but type {type(output_path)}||{type(file_name)}||{type(system)} occurred.')
|
|
273
|
+
elif out_coord_type != 'C' and out_coord_type != 'D':
|
|
274
|
+
raise ValueError(f'Unknown coord_type : "{out_coord_type}"')
|
|
275
|
+
|
|
276
|
+
# main
|
|
277
|
+
with open(os.path.join(output_path, file_name), 'w') as POSCAR:
|
|
278
|
+
POSCAR.write('#' + system)
|
|
279
|
+
POSCAR.write('\n')
|
|
280
|
+
# cell
|
|
281
|
+
POSCAR.write('lattice\\\n')
|
|
282
|
+
cell = np.asarray(cell, dtype=np.float32) * 1.89036 # 1 Angstrom = 1.89036 Bohr. Convert from A to Bohr.
|
|
283
|
+
for vx in cell[:-1]:
|
|
284
|
+
for xx in vx:
|
|
285
|
+
POSCAR.write(f'{xx:>14.8f}')
|
|
286
|
+
POSCAR.write('\\\n')
|
|
287
|
+
for xx in cell[-1]:
|
|
288
|
+
POSCAR.write(f'{xx:>14.8f}')
|
|
289
|
+
POSCAR.write('\n')
|
|
290
|
+
# others
|
|
291
|
+
POSCAR.write('coulomb-interaction Slab 001\n')
|
|
292
|
+
POSCAR.write('coulomb-truncation-embed 0 0 0\n')
|
|
293
|
+
# atom coordinates
|
|
294
|
+
coord = np.asarray(coord, dtype=np.float32)
|
|
295
|
+
if out_coord_type == 'C':
|
|
296
|
+
POSCAR.write('coords-type Cartesian\n')
|
|
297
|
+
if in_coord_type == 'D':
|
|
298
|
+
coord = coord @ cell
|
|
299
|
+
elif in_coord_type == 'C':
|
|
300
|
+
coord = coord * 1.89036
|
|
301
|
+
else:
|
|
302
|
+
raise ValueError(f'Unknown coordinate type {in_coord_type}. It must be "C" or "D".')
|
|
303
|
+
elif out_coord_type == 'D':
|
|
304
|
+
POSCAR.write('coords-type Lattice\n')
|
|
305
|
+
if in_coord_type == 'D':
|
|
306
|
+
pass
|
|
307
|
+
elif in_coord_type == 'C':
|
|
308
|
+
coord = coord @ np.linalg.inv(cell / 1.89036)
|
|
309
|
+
for ind, vx in enumerate(coord[:, :3]):
|
|
310
|
+
POSCAR.write(f'ion {atom_list[ind]:>3s}')
|
|
311
|
+
for xx in vx:
|
|
312
|
+
POSCAR.write(f'{xx: > 14.8f}')
|
|
313
|
+
POSCAR.write(f' {fixed[ind][0]}') # Selective Dynamics
|
|
314
|
+
POSCAR.write('\n')
|
|
315
|
+
|
|
316
|
+
def write_xyz(
|
|
317
|
+
elements,
|
|
318
|
+
coords,
|
|
319
|
+
cells,
|
|
320
|
+
energies,
|
|
321
|
+
numbers,
|
|
322
|
+
forces,
|
|
323
|
+
output_path: str,
|
|
324
|
+
filename_list: List[str]|None=None,
|
|
325
|
+
output_xyz_type: Literal['only_position_xyz','write_position_and_force']='only_position_xyz',
|
|
326
|
+
n_core:int=-1
|
|
327
|
+
) -> None:
|
|
328
|
+
"""
|
|
329
|
+
'only_position_xyz':output .xyz file with only position,without force
|
|
330
|
+
'write_position_and_force':output .xyz file with position and force
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
if output_xyz_type not in {'only_position_xyz','write_position_and_force'}:
|
|
334
|
+
raise ValueError(f"`output_xyz_type` must be 'only_position_xyz' or 'write_position_and_force',"
|
|
335
|
+
f" but occurred {output_xyz_type}.")
|
|
336
|
+
# loading data
|
|
337
|
+
n_batch = len(cells)
|
|
338
|
+
if filename_list is None:
|
|
339
|
+
filename_list = [f'{_}.xyz' for _ in range(n_batch)]
|
|
340
|
+
elif not isinstance(filename_list, (List, Tuple)):
|
|
341
|
+
raise TypeError(f'Invalid type of `filename_list`: {type(filename_list)}')
|
|
342
|
+
elif len(filename_list) != n_batch:
|
|
343
|
+
raise ValueError(f'Number of file names ({len(filename_list)}) and structures ({n_batch}) does not match')
|
|
344
|
+
# check n_core
|
|
345
|
+
if n_core == -1:
|
|
346
|
+
n_core = jb.cpu_count(True)
|
|
347
|
+
if n_core > n_batch:
|
|
348
|
+
n_core = n_batch
|
|
349
|
+
|
|
350
|
+
def _write_single(
|
|
351
|
+
file_name: str,
|
|
352
|
+
elements,
|
|
353
|
+
coords,
|
|
354
|
+
cells,
|
|
355
|
+
energies,
|
|
356
|
+
numbers,
|
|
357
|
+
forces,
|
|
358
|
+
):
|
|
359
|
+
with open(os.path.join(output_path, file_name), "w") as xyz: # 遍历生成不同所有文件,并进行编写
|
|
360
|
+
xyz.write(f"{len(coords)}\n")#total number of atoms
|
|
361
|
+
|
|
362
|
+
cells_str = np.array2string(cells, **FLOAT_ARRAY_FORMAT).replace("[", " ").replace("]"," ").replace("\n","").replace(" "," ")
|
|
363
|
+
xyz.write(f"Lattice ='{cells_str}' Properties=species:S:1:pos:R:3:forces:R:3 energy={energies:<.7e} pbc=T T T\n")
|
|
364
|
+
# generate elem_list which contain the element type of every structure. (data type:list(array()),)
|
|
365
|
+
elem_ = [[f"{elements[i]: <2s}"] * int(numbers[i]) for i in range(len(elements))]
|
|
366
|
+
elem = sum(elem_, [])
|
|
367
|
+
elem_list = np.array(elem).reshape((len(coords), 1))
|
|
368
|
+
|
|
369
|
+
if output_xyz_type == "write_position_and_force":
|
|
370
|
+
coo_force_str = AS_PRINT_COORDS(np.concatenate((coords, forces), axis=1))
|
|
371
|
+
elem_pos_force_array = np.concatenate((elem_list, coo_force_str), axis=1)
|
|
372
|
+
elem_pos_force = np.array2string(elem_pos_force_array, **STRING_ARRAY_FORMAT).replace("[", " ").replace("]"," ")#.replace("'", "")
|
|
373
|
+
xyz.write(f"{elem_pos_force}\n")
|
|
374
|
+
else:
|
|
375
|
+
elem_pos_array = np.concatenate((elem_list, AS_PRINT_COORDS(coords)), axis=1)
|
|
376
|
+
elem_pos_array = np.array2string(elem_pos_array, **STRING_ARRAY_FORMAT).replace("[", " ").replace(
|
|
377
|
+
"]", " ") # .replace("'", "")
|
|
378
|
+
xyz.write(f"{elem_pos_array}\n")
|
|
379
|
+
|
|
380
|
+
if n_core == 1:
|
|
381
|
+
for ii, fn in enumerate(filename_list):
|
|
382
|
+
_write_single(
|
|
383
|
+
fn,
|
|
384
|
+
elements[ii],
|
|
385
|
+
coords[ii],
|
|
386
|
+
cells[ii],
|
|
387
|
+
energies[ii],
|
|
388
|
+
numbers[ii],
|
|
389
|
+
forces[ii]
|
|
390
|
+
)
|
|
391
|
+
else:
|
|
392
|
+
_para = jb.Parallel(n_core, )
|
|
393
|
+
_para(
|
|
394
|
+
jb.delayed(_write_single)(
|
|
395
|
+
fn,
|
|
396
|
+
elements[ii],
|
|
397
|
+
coords[ii],
|
|
398
|
+
cells[ii],
|
|
399
|
+
energies[ii],
|
|
400
|
+
numbers[ii],
|
|
401
|
+
forces[ii]
|
|
402
|
+
)
|
|
403
|
+
for ii, fn in enumerate(filename_list)
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def write_cif(
|
|
408
|
+
cells: Sequence[Sequence],
|
|
409
|
+
coords: Sequence[np.ndarray],
|
|
410
|
+
atom_labels: Sequence[Sequence[str]],
|
|
411
|
+
atom_numbers: Sequence[Sequence[int]],
|
|
412
|
+
output_path: str = './',
|
|
413
|
+
file_name_list: str | Sequence[str] = 'POSCAR',
|
|
414
|
+
system_list: str | Sequence[str] = 'untitled',
|
|
415
|
+
coord_type: List[Literal['C', 'D']] | Literal['C', 'D'] = 'C',
|
|
416
|
+
n_core: int = -1
|
|
417
|
+
) -> None:
|
|
418
|
+
"""
|
|
419
|
+
Convert coordinates matrices to POSCAR format, and write files to the "output_path".
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
cells: List|ndarray, a batch of lattice vectors. shape: (n_batch, 3, 3)
|
|
423
|
+
atom_labels: 2D list|ndarray of str, a batch of list of element symbols. shape: (n_batch, n_atom)
|
|
424
|
+
atom_numbers:2D list|ndarray of int, list of atom number of each element, in the order of atom_label. shape: (n_batch, n_atom)
|
|
425
|
+
coords: list|ndarray, the batch of atoms coordinates, in the order of atom_label. shape: (n_batch, n_atom, 3)
|
|
426
|
+
output_path: str, the output path.
|
|
427
|
+
file_name_list: list(str), the list of file names.
|
|
428
|
+
system_list: str|list(str), the 1st line of output file i.e., the annotation or title of the file.
|
|
429
|
+
coord_type: List[Literal['C', 'D']]|Literal['C', 'D'], 'D' or 'C', which means whether the input coordinates are "Direct" or "Cartesian".
|
|
430
|
+
If only a string, `coord_type` would set to be '[`coord_type`] * n_batch'.
|
|
431
|
+
n_core: int, the number of CPU cores to write files in parallel.
|
|
432
|
+
|
|
433
|
+
Returns: None
|
|
434
|
+
|
|
435
|
+
"""
|
|
436
|
+
def _write_single(
|
|
437
|
+
file_name,
|
|
438
|
+
cell,
|
|
439
|
+
coord,
|
|
440
|
+
atom_label,
|
|
441
|
+
atom_number,
|
|
442
|
+
_coord_type,
|
|
443
|
+
system_name: str | Sequence[str] = 'untitled',
|
|
444
|
+
):
|
|
445
|
+
atom_label_str = ' '.join(atom_label)
|
|
446
|
+
formula = ''.join([f'{atom_label[_]}{atom_number[_]}' for _ in range(len(atom_number))])
|
|
447
|
+
|
|
448
|
+
atom_label_list = list()
|
|
449
|
+
atom_sequence_list = list()
|
|
450
|
+
for i, num in enumerate(atom_number):
|
|
451
|
+
atom_label_list.extend([atom_label[i]] * num)
|
|
452
|
+
atom_sequence_list.extend(range(1, num + 1))
|
|
453
|
+
|
|
454
|
+
a, b, c = np.linalg.norm(cell, axis=1)
|
|
455
|
+
alpha = np.rad2deg(np.arccos(cell[1] @ cell[2] / (b * c)))
|
|
456
|
+
beta = np.rad2deg(np.arccos(cell[0] @ cell[2] / (a * c)))
|
|
457
|
+
gamma = np.rad2deg(np.arccos(cell[0] @ cell[1] / (a * b)))
|
|
458
|
+
volume = np.linalg.det(cell)
|
|
459
|
+
if volume < 0.: warnings.warn(f'The cell of {file_name} is a left-hand system.', RuntimeWarning)
|
|
460
|
+
volume = np.abs(volume)
|
|
461
|
+
if _coord_type == 'C':
|
|
462
|
+
coord = coord @ np.linalg.inv(cell)
|
|
463
|
+
|
|
464
|
+
with open(os.path.join(output_path, file_name), "w") as cif:
|
|
465
|
+
cif.write(f'# {system_name}\ndata_{formula}\n')
|
|
466
|
+
cif.write("_symmetry_space_group_name_H-M 'P 1'\n")
|
|
467
|
+
cif.write(
|
|
468
|
+
f'_cell_length_a {a}\n'
|
|
469
|
+
f'_cell_length_b {b}\n'
|
|
470
|
+
f'_cell_length_c {c}\n'
|
|
471
|
+
f'_cell_angle_alpha {alpha}\n'
|
|
472
|
+
f'_cell_angle_beta {beta}\n'
|
|
473
|
+
f'_cell_angle_gamma {gamma}\n'
|
|
474
|
+
'_symmetry_Int_Tables_number 1\n'
|
|
475
|
+
f'_chemical_formula_structural \'{atom_label_str}\'\n'
|
|
476
|
+
f'_chemical_formula_sum {formula}\n'
|
|
477
|
+
f'_cell_volume {volume}\n'
|
|
478
|
+
'loop_\n'
|
|
479
|
+
' _symmetry_equiv_pos_site_id\n'
|
|
480
|
+
' _symmetry_equiv_pos_as_xyz\n'
|
|
481
|
+
' 1 \'x, y, z\'\n'
|
|
482
|
+
'loop_\n'
|
|
483
|
+
' _atom_site_label\n'
|
|
484
|
+
' _atom_site_type_symbol\n'
|
|
485
|
+
' _atom_site_symmetry_multiplicity\n'
|
|
486
|
+
' _atom_site_fract_x\n'
|
|
487
|
+
' _atom_site_fract_y\n'
|
|
488
|
+
' _atom_site_fract_z\n'
|
|
489
|
+
' _atom_site_occupancy\n'
|
|
490
|
+
)
|
|
491
|
+
for i, coo in enumerate(coord):
|
|
492
|
+
cif.write(
|
|
493
|
+
f'{atom_label_list[i]}{atom_sequence_list[i]:<4d} {atom_label_list[i]} 1 '
|
|
494
|
+
f'{np.array2string(coo, **FLOAT_ARRAY_FORMAT)[1:-1]} 1\n'
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
# loading data
|
|
498
|
+
n_batch = len(cells)
|
|
499
|
+
if file_name_list is None:
|
|
500
|
+
filename_list = [f'{_}.cif' for _ in range(n_batch)]
|
|
501
|
+
elif not isinstance(file_name_list, (List, Tuple)):
|
|
502
|
+
raise TypeError(f'Invalid type of `filename_list`: {type(file_name_list)}')
|
|
503
|
+
elif len(file_name_list) != n_batch:
|
|
504
|
+
raise ValueError(f'Number of file names ({len(file_name_list)}) and structures ({n_batch}) does not match')
|
|
505
|
+
# check n_core
|
|
506
|
+
if n_core == -1:
|
|
507
|
+
n_core = jb.cpu_count(True)
|
|
508
|
+
if n_core > n_batch:
|
|
509
|
+
n_core = n_batch
|
|
510
|
+
|
|
511
|
+
_para = jb.Parallel(n_core, )
|
|
512
|
+
_para(
|
|
513
|
+
jb.delayed(_write_single)(
|
|
514
|
+
fn,
|
|
515
|
+
cells[ii],
|
|
516
|
+
coords[ii],
|
|
517
|
+
atom_labels[ii],
|
|
518
|
+
atom_numbers[ii],
|
|
519
|
+
coord_type[ii],
|
|
520
|
+
system_list[ii]
|
|
521
|
+
)
|
|
522
|
+
for ii, fn in enumerate(file_name_list)
|
|
523
|
+
)
|
|
524
|
+
|