pymdkit 1.5.0__tar.gz → 1.5.2__tar.gz
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.
- {pymdkit-1.5.0 → pymdkit-1.5.2}/PKG-INFO +64 -12
- {pymdkit-1.5.0 → pymdkit-1.5.2}/README.md +89 -37
- {pymdkit-1.5.0 → pymdkit-1.5.2}/pyproject.toml +7 -7
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/__init__.py +1 -1
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/_cifio.py +106 -16
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/_gpumd.py +19 -8
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/convert.py +8 -3
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/gpumd_group.py +3 -3
- pymdkit-1.5.2/src/pymdkit/commands/gpumd_msd.py +461 -0
- pymdkit-1.5.2/src/pymdkit/commands/realloc_occ.py +452 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/supercell.py +116 -23
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/symmetrize.py +1 -1
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit.egg-info/PKG-INFO +64 -12
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit.egg-info/SOURCES.txt +2 -1
- pymdkit-1.5.0/src/pymdkit/commands/compute_msd_all_groups.py +0 -375
- {pymdkit-1.5.0 → pymdkit-1.5.2}/LICENSE +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/setup.cfg +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/__init__.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/_extxyz.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/_fileio.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/_geometry.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/_precision.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/_reduction.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/_vasp_output.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/_vaspset.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/add_config_type.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/chemsys_entry.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/compute_ehull.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/compute_rmsd.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/ewald.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/gather_fs.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/gpumd_relax.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/gpumd_thermo.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/gpumd_trjcat.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/nep_rmse.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/pca.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/perturb.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/submit_vasp.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/substitute.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/trj_extract.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/trj_sparse.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/umap.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/vasp2xyz.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/vasp_fe.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/vasp_relax.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/commands/vasp_static.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit/pymdkit_main.py +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit.egg-info/dependency_links.txt +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit.egg-info/entry_points.txt +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit.egg-info/requires.txt +0 -0
- {pymdkit-1.5.0 → pymdkit-1.5.2}/src/pymdkit.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pymdkit
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.2
|
|
4
4
|
Summary: A unified command-line toolkit for atomistic / MD structure workflows.
|
|
5
5
|
Author-email: Yueda Wang <ydwang0608@ustc.edu.cn>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -84,7 +84,7 @@ directory for job sub-folders automatically.
|
|
|
84
84
|
| `trj-extract` | Extract a GPUMD XYZ trajectory by time range |
|
|
85
85
|
| `trj-sparse` | Deterministically retain a requested fraction of XYZ trajectory frames |
|
|
86
86
|
| `umap` | Reduce descriptor data to two dimensions with UMAP; optional FPS sampling |
|
|
87
|
-
| `msd` |
|
|
87
|
+
| `gpumd-msd` | Recursively extract directional SDC and Nernst-Einstein conductivity from GPUMD jobs |
|
|
88
88
|
| `nep-rmse` | Compute NEP energy/force/stress RMSE with terminal plots and optional candidate selection |
|
|
89
89
|
| `perturb` | Generate perturbed structures with dpdata |
|
|
90
90
|
| `vasp2xyz` | Collect SCF-converged VASP job folders (any name) into one extxyz file |
|
|
@@ -92,6 +92,7 @@ directory for job sub-folders automatically.
|
|
|
92
92
|
| `rmsd` | Compare structures by RMSD and optionally remove higher-energy duplicates |
|
|
93
93
|
| `chemsys-entry` | Download stable Materials Project structures for a chemical system |
|
|
94
94
|
| `convert` | Convert structures, trajectories, or folders between ASE/pymatgen formats |
|
|
95
|
+
| `realloc-occ` | Generate charge-balanced CIF occupancy allocations for a target formula |
|
|
95
96
|
| `substitute` | Randomly substitute or remove selected atoms/sites from a structure |
|
|
96
97
|
| `supercell` | Build a supercell with cell lengths capped at a maximum (Angstrom); optional per-temperature GPUMD setup |
|
|
97
98
|
| `symmetrize` | Detect/refine symmetry for structure files, folders, or CSV rows -> CIF |
|
|
@@ -111,7 +112,7 @@ pmk convert -if vasp-opted -of cif-opted -oe cif # folder convers
|
|
|
111
112
|
pmk convert -it train.xyz -ot train.extxyz # convert a complete trajectory
|
|
112
113
|
pmk supercell -i opted.vasp -o sc.vasp -max-abc 20 # cell lengths <= 20 A
|
|
113
114
|
pmk supercell -if vasp-opted -max-abc 20 -individual # per-structure ./<name>/<name>.<ext>
|
|
114
|
-
pmk supercell -if extxyz-opted -max-abc 24 -individual -temp 500 600 -md-if input-files -
|
|
115
|
+
pmk supercell -if extxyz-opted -max-abc 24 -individual -temp 500 600 -md-if input-files -group Li Y Cl
|
|
115
116
|
# GPUMD: ./<name>/model.xyz (grouped) + ./<name>/<T>/ jobs
|
|
116
117
|
pmk vasp-relax -i opted.vasp # relax inputs in current dir
|
|
117
118
|
pmk vasp-relax -if optimal_occupancy # one ./<name>/ job folder per structure
|
|
@@ -119,8 +120,8 @@ pmk gpumd-relax -if example -nep nep89_20250409.txt # model.xyz + run
|
|
|
119
120
|
pmk gpumd-relax -it train.xyz -nep nep.txt -in run.in # one ./frame_N/ GPUMD job per frame
|
|
120
121
|
pmk vasp-static -if cifs/ -custom-setting my_incar.txt # static inputs, custom INCAR
|
|
121
122
|
pmk vasp-static -it traj.xyz # one ./frame_N/ job per trajectory frame
|
|
122
|
-
pmk msd
|
|
123
|
-
pmk msd -diffuse_ion Li -ion_charge 1
|
|
123
|
+
pmk gpumd-msd # recursively scans GPUMD jobs -> per-job msd/ + sigma-sdc-summary.txt
|
|
124
|
+
pmk gpumd-msd -diffuse_ion Li -ion_charge 1 # choose the mobile ion and signed valence
|
|
124
125
|
pmk chemsys-entry -s Li La Ta Cl # MP stable entries -> Li-La-Ta-Cl-stable-entries/
|
|
125
126
|
pmk ehull -mp-api-key $MP_API_KEY # scans ./ for VASP jobs -> ehull.txt
|
|
126
127
|
pmk ehull -local Li-La-Ta-Cl-stable-entries-opted
|
|
@@ -132,6 +133,8 @@ pmk vasp2xyz # scans ./ for VASP output folde
|
|
|
132
133
|
pmk vasp2xyz -position-only # write positions only, without energy/forces/stress
|
|
133
134
|
pmk submit-vasp -subscript sub_vasp -queue slurm -max-job-num 30
|
|
134
135
|
nohup pmk submit-vasp -subscript sub_vasp -queue slurm -max-job-num 30 > submit-vasp.log 2>&1 &
|
|
136
|
+
pmk realloc-occ -i 66-Li4YCl6-5.cif -of chg-balance -formula Li3YCl6
|
|
137
|
+
pmk realloc-occ -if cifs -of chg-balance -formula Li3YCl6
|
|
135
138
|
pmk substitute -i Li3YCl6.cif -se Li -sn 3 -we Na -wn 3 -on 100
|
|
136
139
|
pmk substitute -i Li3YCl6.cif -se Li -sn 3 -we none -on 100
|
|
137
140
|
pmk substitute -i Li96Ta6La11Cl72.cif -se Li1 Li2 -sn 20 67 -we none -on 100
|
|
@@ -200,6 +203,36 @@ dump_xyz -1 0 1 relaxed.xyz force
|
|
|
200
203
|
run 1
|
|
201
204
|
```
|
|
202
205
|
|
|
206
|
+
`gpumd-msd` recursively discovers job folders at any depth below `-entry-path`
|
|
207
|
+
(the current directory by default). A job must contain `msd.out`, `model.xyz`,
|
|
208
|
+
`run.in`, and `thermo.out`; the common accidental name `thermos.out` is also
|
|
209
|
+
accepted. Following the [official GPUMD `msd.out` format](https://gpumd.org/gpumd/output_files/msd_out.html), each group block is read as three MSD columns
|
|
210
|
+
followed by three directional SDC columns. PMK takes the arithmetic mean over
|
|
211
|
+
the last 50% of the SDC correlation-time range, converts A^2/ps to cm^2/s by
|
|
212
|
+
multiplying by `1e-4`, and reports `D_total = (D_x + D_y + D_z) / 3`.
|
|
213
|
+
Each job receives `msd/group_INDEX_ELEMENT_TEMP_msd.txt`, a matching `_sdc.txt`,
|
|
214
|
+
and a `_results.txt`. The root receives `sigma-sdc-summary.txt` with directional
|
|
215
|
+
and total diffusion coefficients and conductivities.
|
|
216
|
+
|
|
217
|
+
Ionic conductivity uses the Nernst-Einstein relation with the mean temperature
|
|
218
|
+
of the final SDC-producing MD run and the official 18-column
|
|
219
|
+
[`thermo.out` format](https://gpumd.org/gpumd/output_files/thermo_out.html):
|
|
220
|
+
for NPT ensembles (`npt_scr`, `npt_mttk`, and other `npt_*` forms), PMK uses
|
|
221
|
+
the mean triclinic-box volume over that run; for NVT ensembles (`nvt_bdp`,
|
|
222
|
+
`nvt_ber`, and other `nvt_*` forms), it uses the final fixed-cell volume.
|
|
223
|
+
`V(A^3) * 1e-24 -> V(cm^3)`, exact SI values for elementary charge and the
|
|
224
|
+
Boltzmann constant, and a final `* 1000` conversion from S/cm to mS/cm.
|
|
225
|
+
|
|
226
|
+
`realloc-occ -i INPUT.cif -of OUTPUT -formula FORMULA` computes atom counts as
|
|
227
|
+
Wyckoff multiplicity times occupancy and uses `_cell_formula_units_Z` to map the
|
|
228
|
+
target reduced formula into the existing cell. It enumerates finite site-wise
|
|
229
|
+
occupancy allocations, retains only charge-balanced candidates using the CIF
|
|
230
|
+
oxidation-number loop, and preserves the original symmetry, coordinates,
|
|
231
|
+
Wyckoff labels, and CIF layout. Every input receives `OUTPUT/INPUT_STEM/`; for
|
|
232
|
+
the supplied Li4YCl6 example, the two outputs are `...-d1.cif` with `Li1=0.50`
|
|
233
|
+
and `...-d2.cif` with `Li2=0.50`. Folder mode uses `-if` with the same `-of` and
|
|
234
|
+
`-formula` options.
|
|
235
|
+
|
|
203
236
|
`gather-fs` recursively scans all job folders below the current directory. For
|
|
204
237
|
VASP it follows the global `vaspout.h5 > vasprun.xml > OUTCAR` priority and
|
|
205
238
|
requires full convergence before copying the selected `-fs-name` as `.vasp`. The gathered VASP file's first line is replaced with `energy=<final_energy> eV` for later screening.
|
|
@@ -241,20 +274,25 @@ per successfully generated CIF: filename, crystal system, space-group symbol,
|
|
|
241
274
|
and space-group number. Crystal-system and space-group summaries are sorted
|
|
242
275
|
from highest to lowest count, with alphabetical ordering for ties, followed by
|
|
243
276
|
the total number of structures. Single-file mode places the report beside the
|
|
244
|
-
output CIF; folder and CSV modes place it inside `-of`.
|
|
277
|
+
output CIF; folder and CSV modes place it inside `-of`. Folder mode keeps each
|
|
278
|
+
input stem unchanged, writing `NAME.cif` rather than `NAME-symm.cif`.
|
|
245
279
|
|
|
246
280
|
Every CIF exported by `symmetrize`, `convert`, `substitute`, `chemsys-entry`,
|
|
247
281
|
`supercell`, or `gpumd-group` is written through the same symmetry-aware helper.
|
|
248
282
|
Each CIF places the IUCr-defined `_space_group_crystal_system` item immediately
|
|
249
|
-
below its `data_...` header and contains both
|
|
250
|
-
and `_atom_site_Wyckoff_symbol` in the
|
|
283
|
+
below its `data_...` header and contains both
|
|
284
|
+
`_atom_site_site_symmetry_multiplicity` and `_atom_site_Wyckoff_symbol` in the
|
|
285
|
+
atom-site loop. Multiplicity-letter
|
|
251
286
|
assignments use the conventional
|
|
252
287
|
International Tables setting from pymatgen/spglib and correspond to the
|
|
253
288
|
[Bilbao Crystallographic Server WYCKPOS tables](https://cryst.ehu.es/cryst/get_wp.html);
|
|
254
289
|
the provenance URL is also recorded inside each generated CIF.
|
|
255
290
|
|
|
256
|
-
CIF
|
|
257
|
-
such as `Li1`, `Y1`, `Cl1`, and `Cl2
|
|
291
|
+
CIF `_atom_type_symbol` and `_atom_site_label` values use per-element
|
|
292
|
+
inequivalent-site identifiers such as `Li1`, `Y1`, `Cl1`, and `Cl2`.
|
|
293
|
+
The final atom-site loop starts with `_atom_site_label`, followed by
|
|
294
|
+
`_atom_site_type_symbol`, whose values are plain elements such as `Li`, `Y`,
|
|
295
|
+
and `Cl`. Oxidation numbers remain in the separate
|
|
258
296
|
`_atom_type_oxidation_number` column. PMK does not add an
|
|
259
297
|
`_audit_creation_method` block or a generated-by header comment.
|
|
260
298
|
|
|
@@ -273,8 +311,20 @@ existing trajectory frames continue copying those frame bytes exactly.
|
|
|
273
311
|
|
|
274
312
|
When `pca` or `umap` receives `-it train.xyz`, it reads `Config_type` from every frame and writes one additional descriptor table per value, such as `pca-descriptor-P-3m1.txt` and `pca-descriptor-Pnma.txt`. With `-fps`, it also writes matching files such as `fps-0.01-pca-descriptor-P-3m1.txt` and `fps-0.01-train-P-3m1.xyz`. These files partition the single global FPS selection; FPS is not rerun independently for each `Config_type`. Frames without `Config_type` remain in the main outputs but do not receive a type-specific file.
|
|
275
313
|
|
|
276
|
-
For folder conversion, `convert -if INPUT -
|
|
277
|
-
|
|
314
|
+
For folder conversion, `convert -if INPUT -o OUTPUT` and
|
|
315
|
+
`convert -if INPUT -of OUTPUT` are equivalent and write `.xyz` by default. Set
|
|
316
|
+
another target extension with `-oe`, for example `-oe cif` or `-oe vasp`.
|
|
317
|
+
Single-file and trajectory modes infer the target format from `-o` and `-ot`.
|
|
318
|
+
Because Wyckoff metadata describes one crystal structure, each `.cif` output
|
|
319
|
+
file accepts exactly one frame.
|
|
320
|
+
|
|
321
|
+
After `supercell -temp` creates all requested jobs, it checks generated folders
|
|
322
|
+
that contain `run.in`, `model.xyz`, and `nep.txt`. Existing `ensemble npt_scr`
|
|
323
|
+
lines retain their temperature parameters but are rewritten with three
|
|
324
|
+
pressure/modulus components for 90-degree orthorhombic cells and all six
|
|
325
|
+
components for non-orthorhombic cells, following the
|
|
326
|
+
[official GPUMD ensemble syntax](https://gpumd.org/gpumd/input_parameters/ensemble_standard.html).
|
|
327
|
+
Other ensemble types are not modified. Use `-group Li Y Cl` to add GPUMD groups.
|
|
278
328
|
|
|
279
329
|
`gpumd-thermo` writes `temperature.txt`, `potential-energy.txt`, `pressure.txt`, `lattice-parameters.txt`, `volume.txt`, `lattice-angles.txt`, and a headless `thermo.png` in every detected GPUMD job folder. For an abnormal run it also writes `abnormal-time.txt`, with one `BEGIN ps to END ps` interval per line; a normal rerun removes a stale file. Potential energy is the primary equilibrium signal. Detection requires a persistent change with a meaningful magnitude relative to the robust natural energy fluctuations, so ordinary correlated thermal noise and isolated spikes are not classified as abnormal. An abnormal classification always requires a mapped potential-energy change. When at least two lattice-parameter or volume signals show the same mapped transition, they may enable a more sensitive noise-relative energy test; this avoids a fixed eV or percentage-of-total-energy threshold that would depend on system size. Cell signals can corroborate and refine the energy interval, but can never classify a run by themselves. Temperature and all six pressure components remain diagnostics only and cannot classify an otherwise stationary run as abnormal by themselves. A lone cell-axis drift, an isolated spike, or an empty anomaly mask never triggers a whole-run fallback. Thermo plots use Arial when available and Matplotlib's built-in qualitative `Dark2` color cycle. They plot all six pressure components, use frameless upper-right legends with added vertical headroom, preserve correct Å units, and show abnormal regions as shaded legend entries on potential energy, lattice parameters, and volume. Potential energy and volume are divided by 1000 and labeled ×10³ only when their absolute plotted values reach 1000. The analysis uses the last `run` segment, or the last 80% when `run.in` has no `run` record; inspect the plot before making a final scientific judgment.
|
|
280
330
|
|
|
@@ -329,6 +379,7 @@ pymdkit/
|
|
|
329
379
|
|-- _geometry.py # shared minimum-image geometry helper
|
|
330
380
|
|-- _vaspset.py # shared VASP input-set helper (not a command)
|
|
331
381
|
|-- gpumd_group.py
|
|
382
|
+
|-- gpumd_msd.py
|
|
332
383
|
|-- gpumd_relax.py
|
|
333
384
|
|-- gpumd_thermo.py
|
|
334
385
|
|-- gpumd_trjcat.py
|
|
@@ -343,6 +394,7 @@ pymdkit/
|
|
|
343
394
|
|-- chemsys_entry.py
|
|
344
395
|
|-- submit_vasp.py
|
|
345
396
|
|-- convert.py
|
|
397
|
+
|-- realloc_occ.py
|
|
346
398
|
|-- substitute.py
|
|
347
399
|
|-- supercell.py
|
|
348
400
|
|-- vasp2xyz.py
|
|
@@ -21,13 +21,13 @@ pip install pymdkit
|
|
|
21
21
|
This installs the `pmk` command into the active conda environment, together
|
|
22
22
|
with its direct dependencies, including ASE, pymatgen, py4vasp-core, dpdata, matplotlib, and UMAP. Some of these packages may install their own transitive dependencies.
|
|
23
23
|
|
|
24
|
-
GPU-accelerated RMSD screening is optional. For a CUDA 12.x cluster (including
|
|
25
|
-
the supplied `module load cuda/12.6` setup), install the matching CuPy extra:
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
python -m pip install "pymdkit[gpu-cuda12]"
|
|
29
|
-
```
|
|
30
|
-
|
|
24
|
+
GPU-accelerated RMSD screening is optional. For a CUDA 12.x cluster (including
|
|
25
|
+
the supplied `module load cuda/12.6` setup), install the matching CuPy extra:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
python -m pip install "pymdkit[gpu-cuda12]"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
31
|
Verify:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
@@ -57,14 +57,15 @@ directory for job sub-folders automatically.
|
|
|
57
57
|
| `trj-extract` | Extract a GPUMD XYZ trajectory by time range |
|
|
58
58
|
| `trj-sparse` | Deterministically retain a requested fraction of XYZ trajectory frames |
|
|
59
59
|
| `umap` | Reduce descriptor data to two dimensions with UMAP; optional FPS sampling |
|
|
60
|
-
| `msd` |
|
|
60
|
+
| `gpumd-msd` | Recursively extract directional SDC and Nernst-Einstein conductivity from GPUMD jobs |
|
|
61
61
|
| `nep-rmse` | Compute NEP energy/force/stress RMSE with terminal plots and optional candidate selection |
|
|
62
62
|
| `perturb` | Generate perturbed structures with dpdata |
|
|
63
63
|
| `vasp2xyz` | Collect SCF-converged VASP job folders (any name) into one extxyz file |
|
|
64
64
|
| `submit-vasp` | Submit/resubmit VASP job folders while limiting active queue jobs |
|
|
65
65
|
| `rmsd` | Compare structures by RMSD and optionally remove higher-energy duplicates |
|
|
66
66
|
| `chemsys-entry` | Download stable Materials Project structures for a chemical system |
|
|
67
|
-
| `convert` | Convert structures, trajectories, or folders between ASE/pymatgen formats |
|
|
67
|
+
| `convert` | Convert structures, trajectories, or folders between ASE/pymatgen formats |
|
|
68
|
+
| `realloc-occ` | Generate charge-balanced CIF occupancy allocations for a target formula |
|
|
68
69
|
| `substitute` | Randomly substitute or remove selected atoms/sites from a structure |
|
|
69
70
|
| `supercell` | Build a supercell with cell lengths capped at a maximum (Angstrom); optional per-temperature GPUMD setup |
|
|
70
71
|
| `symmetrize` | Detect/refine symmetry for structure files, folders, or CSV rows -> CIF |
|
|
@@ -84,7 +85,7 @@ pmk convert -if vasp-opted -of cif-opted -oe cif # folder convers
|
|
|
84
85
|
pmk convert -it train.xyz -ot train.extxyz # convert a complete trajectory
|
|
85
86
|
pmk supercell -i opted.vasp -o sc.vasp -max-abc 20 # cell lengths <= 20 A
|
|
86
87
|
pmk supercell -if vasp-opted -max-abc 20 -individual # per-structure ./<name>/<name>.<ext>
|
|
87
|
-
pmk supercell -if extxyz-opted -max-abc 24 -individual -temp 500 600 -md-if input-files -
|
|
88
|
+
pmk supercell -if extxyz-opted -max-abc 24 -individual -temp 500 600 -md-if input-files -group Li Y Cl
|
|
88
89
|
# GPUMD: ./<name>/model.xyz (grouped) + ./<name>/<T>/ jobs
|
|
89
90
|
pmk vasp-relax -i opted.vasp # relax inputs in current dir
|
|
90
91
|
pmk vasp-relax -if optimal_occupancy # one ./<name>/ job folder per structure
|
|
@@ -92,8 +93,8 @@ pmk gpumd-relax -if example -nep nep89_20250409.txt # model.xyz + run
|
|
|
92
93
|
pmk gpumd-relax -it train.xyz -nep nep.txt -in run.in # one ./frame_N/ GPUMD job per frame
|
|
93
94
|
pmk vasp-static -if cifs/ -custom-setting my_incar.txt # static inputs, custom INCAR
|
|
94
95
|
pmk vasp-static -it traj.xyz # one ./frame_N/ job per trajectory frame
|
|
95
|
-
pmk msd
|
|
96
|
-
pmk msd -diffuse_ion Li -ion_charge 1
|
|
96
|
+
pmk gpumd-msd # recursively scans GPUMD jobs -> per-job msd/ + sigma-sdc-summary.txt
|
|
97
|
+
pmk gpumd-msd -diffuse_ion Li -ion_charge 1 # choose the mobile ion and signed valence
|
|
97
98
|
pmk chemsys-entry -s Li La Ta Cl # MP stable entries -> Li-La-Ta-Cl-stable-entries/
|
|
98
99
|
pmk ehull -mp-api-key $MP_API_KEY # scans ./ for VASP jobs -> ehull.txt
|
|
99
100
|
pmk ehull -local Li-La-Ta-Cl-stable-entries-opted
|
|
@@ -105,6 +106,8 @@ pmk vasp2xyz # scans ./ for VASP output folde
|
|
|
105
106
|
pmk vasp2xyz -position-only # write positions only, without energy/forces/stress
|
|
106
107
|
pmk submit-vasp -subscript sub_vasp -queue slurm -max-job-num 30
|
|
107
108
|
nohup pmk submit-vasp -subscript sub_vasp -queue slurm -max-job-num 30 > submit-vasp.log 2>&1 &
|
|
109
|
+
pmk realloc-occ -i 66-Li4YCl6-5.cif -of chg-balance -formula Li3YCl6
|
|
110
|
+
pmk realloc-occ -if cifs -of chg-balance -formula Li3YCl6
|
|
108
111
|
pmk substitute -i Li3YCl6.cif -se Li -sn 3 -we Na -wn 3 -on 100
|
|
109
112
|
pmk substitute -i Li3YCl6.cif -se Li -sn 3 -we none -on 100
|
|
110
113
|
pmk substitute -i Li96Ta6La11Cl72.cif -se Li1 Li2 -sn 20 67 -we none -on 100
|
|
@@ -128,7 +131,7 @@ pmk trj-sparse -it example.xyz -r 0.5 -o sparse-example.xyz
|
|
|
128
131
|
|
|
129
132
|
pmk rmsd -i structure-1.vasp structure-2.vasp # one pair -> rmsd.txt
|
|
130
133
|
pmk rmsd -if vasp-opted # every pair -> rmsd.txt
|
|
131
|
-
pmk rmsd -if example -rm-duplicate -of unique-example # RMSD <= 0.1: keep lowest energy
|
|
134
|
+
pmk rmsd -if example -rm-duplicate -of unique-example # RMSD <= 0.1: keep lowest energy
|
|
132
135
|
pmk rmsd -if example -rm-duplicate -of unique-example -gpu
|
|
133
136
|
pmk symmetrize -i opted.cif -add_oxidation yes -o opted-symm.cif
|
|
134
137
|
pmk symmetrize -if my_cifs/ -symprec 0.1 -add_oxidation no -of my_cifs-symm
|
|
@@ -173,6 +176,36 @@ dump_xyz -1 0 1 relaxed.xyz force
|
|
|
173
176
|
run 1
|
|
174
177
|
```
|
|
175
178
|
|
|
179
|
+
`gpumd-msd` recursively discovers job folders at any depth below `-entry-path`
|
|
180
|
+
(the current directory by default). A job must contain `msd.out`, `model.xyz`,
|
|
181
|
+
`run.in`, and `thermo.out`; the common accidental name `thermos.out` is also
|
|
182
|
+
accepted. Following the [official GPUMD `msd.out` format](https://gpumd.org/gpumd/output_files/msd_out.html), each group block is read as three MSD columns
|
|
183
|
+
followed by three directional SDC columns. PMK takes the arithmetic mean over
|
|
184
|
+
the last 50% of the SDC correlation-time range, converts A^2/ps to cm^2/s by
|
|
185
|
+
multiplying by `1e-4`, and reports `D_total = (D_x + D_y + D_z) / 3`.
|
|
186
|
+
Each job receives `msd/group_INDEX_ELEMENT_TEMP_msd.txt`, a matching `_sdc.txt`,
|
|
187
|
+
and a `_results.txt`. The root receives `sigma-sdc-summary.txt` with directional
|
|
188
|
+
and total diffusion coefficients and conductivities.
|
|
189
|
+
|
|
190
|
+
Ionic conductivity uses the Nernst-Einstein relation with the mean temperature
|
|
191
|
+
of the final SDC-producing MD run and the official 18-column
|
|
192
|
+
[`thermo.out` format](https://gpumd.org/gpumd/output_files/thermo_out.html):
|
|
193
|
+
for NPT ensembles (`npt_scr`, `npt_mttk`, and other `npt_*` forms), PMK uses
|
|
194
|
+
the mean triclinic-box volume over that run; for NVT ensembles (`nvt_bdp`,
|
|
195
|
+
`nvt_ber`, and other `nvt_*` forms), it uses the final fixed-cell volume.
|
|
196
|
+
`V(A^3) * 1e-24 -> V(cm^3)`, exact SI values for elementary charge and the
|
|
197
|
+
Boltzmann constant, and a final `* 1000` conversion from S/cm to mS/cm.
|
|
198
|
+
|
|
199
|
+
`realloc-occ -i INPUT.cif -of OUTPUT -formula FORMULA` computes atom counts as
|
|
200
|
+
Wyckoff multiplicity times occupancy and uses `_cell_formula_units_Z` to map the
|
|
201
|
+
target reduced formula into the existing cell. It enumerates finite site-wise
|
|
202
|
+
occupancy allocations, retains only charge-balanced candidates using the CIF
|
|
203
|
+
oxidation-number loop, and preserves the original symmetry, coordinates,
|
|
204
|
+
Wyckoff labels, and CIF layout. Every input receives `OUTPUT/INPUT_STEM/`; for
|
|
205
|
+
the supplied Li4YCl6 example, the two outputs are `...-d1.cif` with `Li1=0.50`
|
|
206
|
+
and `...-d2.cif` with `Li2=0.50`. Folder mode uses `-if` with the same `-of` and
|
|
207
|
+
`-formula` options.
|
|
208
|
+
|
|
176
209
|
`gather-fs` recursively scans all job folders below the current directory. For
|
|
177
210
|
VASP it follows the global `vaspout.h5 > vasprun.xml > OUTCAR` priority and
|
|
178
211
|
requires full convergence before copying the selected `-fs-name` as `.vasp`. The gathered VASP file's first line is replaced with `energy=<final_energy> eV` for later screening.
|
|
@@ -188,20 +221,20 @@ its RMSD to an already retained lower-energy representative is no greater than `
|
|
|
188
221
|
energy from the second-line `energy=` tag. The report records every RMSD,
|
|
189
222
|
duplicate decision, energy, group, and retained representative.
|
|
190
223
|
|
|
191
|
-
`rmsd -if FOLDER -rm-duplicate -of OUTPUT -gpu` is intended for a GPU batch
|
|
192
|
-
job such as the supplied Slurm `sub_pmk`. CuPy computes permutation-invariant,
|
|
193
|
-
periodic distance fingerprints in CUDA batches and skips clearly dissimilar
|
|
194
|
-
pairs; pymatgen `StructureMatcher` remains the final authority for every
|
|
195
|
-
candidate RMSD and duplicate decision. `rmsd.txt` labels pair rows as `exact`,
|
|
196
|
-
`gpu-filtered`, or `incompatible` and records the CUDA device and pair counts.
|
|
197
|
-
The `-gpu` option requires both `-if` and `-rm-duplicate`.
|
|
198
|
-
|
|
199
|
-
For the supplied CUDA 12.6 Slurm script, submit the GPU workflow with:
|
|
200
|
-
|
|
201
|
-
```bash
|
|
202
|
-
sbatch sub_pmk
|
|
203
|
-
```
|
|
204
|
-
|
|
224
|
+
`rmsd -if FOLDER -rm-duplicate -of OUTPUT -gpu` is intended for a GPU batch
|
|
225
|
+
job such as the supplied Slurm `sub_pmk`. CuPy computes permutation-invariant,
|
|
226
|
+
periodic distance fingerprints in CUDA batches and skips clearly dissimilar
|
|
227
|
+
pairs; pymatgen `StructureMatcher` remains the final authority for every
|
|
228
|
+
candidate RMSD and duplicate decision. `rmsd.txt` labels pair rows as `exact`,
|
|
229
|
+
`gpu-filtered`, or `incompatible` and records the CUDA device and pair counts.
|
|
230
|
+
The `-gpu` option requires both `-if` and `-rm-duplicate`.
|
|
231
|
+
|
|
232
|
+
For the supplied CUDA 12.6 Slurm script, submit the GPU workflow with:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
sbatch sub_pmk
|
|
236
|
+
```
|
|
237
|
+
|
|
205
238
|
`symmetrize` uses pymatgen's spglib-backed `SpacegroupAnalyzer` for authoritative
|
|
206
239
|
space-group detection and conventional-cell refinement. Its default Cartesian
|
|
207
240
|
symmetry tolerance is `0.1 Angstrom` (`-symprec`), with a `5 degree` angle
|
|
@@ -214,26 +247,31 @@ per successfully generated CIF: filename, crystal system, space-group symbol,
|
|
|
214
247
|
and space-group number. Crystal-system and space-group summaries are sorted
|
|
215
248
|
from highest to lowest count, with alphabetical ordering for ties, followed by
|
|
216
249
|
the total number of structures. Single-file mode places the report beside the
|
|
217
|
-
output CIF; folder and CSV modes place it inside `-of`.
|
|
250
|
+
output CIF; folder and CSV modes place it inside `-of`. Folder mode keeps each
|
|
251
|
+
input stem unchanged, writing `NAME.cif` rather than `NAME-symm.cif`.
|
|
218
252
|
|
|
219
253
|
Every CIF exported by `symmetrize`, `convert`, `substitute`, `chemsys-entry`,
|
|
220
254
|
`supercell`, or `gpumd-group` is written through the same symmetry-aware helper.
|
|
221
255
|
Each CIF places the IUCr-defined `_space_group_crystal_system` item immediately
|
|
222
|
-
below its `data_...` header and contains both
|
|
223
|
-
and `_atom_site_Wyckoff_symbol` in the
|
|
256
|
+
below its `data_...` header and contains both
|
|
257
|
+
`_atom_site_site_symmetry_multiplicity` and `_atom_site_Wyckoff_symbol` in the
|
|
258
|
+
atom-site loop. Multiplicity-letter
|
|
224
259
|
assignments use the conventional
|
|
225
260
|
International Tables setting from pymatgen/spglib and correspond to the
|
|
226
261
|
[Bilbao Crystallographic Server WYCKPOS tables](https://cryst.ehu.es/cryst/get_wp.html);
|
|
227
262
|
the provenance URL is also recorded inside each generated CIF.
|
|
228
263
|
|
|
229
|
-
CIF
|
|
230
|
-
such as `Li1`, `Y1`, `Cl1`, and `Cl2
|
|
264
|
+
CIF `_atom_type_symbol` and `_atom_site_label` values use per-element
|
|
265
|
+
inequivalent-site identifiers such as `Li1`, `Y1`, `Cl1`, and `Cl2`.
|
|
266
|
+
The final atom-site loop starts with `_atom_site_label`, followed by
|
|
267
|
+
`_atom_site_type_symbol`, whose values are plain elements such as `Li`, `Y`,
|
|
268
|
+
and `Cl`. Oxidation numbers remain in the separate
|
|
231
269
|
`_atom_type_oxidation_number` column. PMK does not add an
|
|
232
270
|
`_audit_creation_method` block or a generated-by header comment.
|
|
233
271
|
|
|
234
272
|
Newly serialized floating-point values use eight digits after the decimal
|
|
235
273
|
point across PMK structure, trajectory, data, report, and screen output. CIF
|
|
236
|
-
`_atom_site_occupancy` values use two digits and
|
|
274
|
+
`_atom_site_occupancy` values use two digits and
|
|
237
275
|
`_atom_type_oxidation_number` values use one digit. In extended XYZ, zero components
|
|
238
276
|
inside the second-line `Lattice="..."` value remain `0.0`; other generated
|
|
239
277
|
floating-point values use eight digits. Commands that select or concatenate
|
|
@@ -246,8 +284,20 @@ existing trajectory frames continue copying those frame bytes exactly.
|
|
|
246
284
|
|
|
247
285
|
When `pca` or `umap` receives `-it train.xyz`, it reads `Config_type` from every frame and writes one additional descriptor table per value, such as `pca-descriptor-P-3m1.txt` and `pca-descriptor-Pnma.txt`. With `-fps`, it also writes matching files such as `fps-0.01-pca-descriptor-P-3m1.txt` and `fps-0.01-train-P-3m1.xyz`. These files partition the single global FPS selection; FPS is not rerun independently for each `Config_type`. Frames without `Config_type` remain in the main outputs but do not receive a type-specific file.
|
|
248
286
|
|
|
249
|
-
For folder conversion, `convert -if INPUT -
|
|
250
|
-
|
|
287
|
+
For folder conversion, `convert -if INPUT -o OUTPUT` and
|
|
288
|
+
`convert -if INPUT -of OUTPUT` are equivalent and write `.xyz` by default. Set
|
|
289
|
+
another target extension with `-oe`, for example `-oe cif` or `-oe vasp`.
|
|
290
|
+
Single-file and trajectory modes infer the target format from `-o` and `-ot`.
|
|
291
|
+
Because Wyckoff metadata describes one crystal structure, each `.cif` output
|
|
292
|
+
file accepts exactly one frame.
|
|
293
|
+
|
|
294
|
+
After `supercell -temp` creates all requested jobs, it checks generated folders
|
|
295
|
+
that contain `run.in`, `model.xyz`, and `nep.txt`. Existing `ensemble npt_scr`
|
|
296
|
+
lines retain their temperature parameters but are rewritten with three
|
|
297
|
+
pressure/modulus components for 90-degree orthorhombic cells and all six
|
|
298
|
+
components for non-orthorhombic cells, following the
|
|
299
|
+
[official GPUMD ensemble syntax](https://gpumd.org/gpumd/input_parameters/ensemble_standard.html).
|
|
300
|
+
Other ensemble types are not modified. Use `-group Li Y Cl` to add GPUMD groups.
|
|
251
301
|
|
|
252
302
|
`gpumd-thermo` writes `temperature.txt`, `potential-energy.txt`, `pressure.txt`, `lattice-parameters.txt`, `volume.txt`, `lattice-angles.txt`, and a headless `thermo.png` in every detected GPUMD job folder. For an abnormal run it also writes `abnormal-time.txt`, with one `BEGIN ps to END ps` interval per line; a normal rerun removes a stale file. Potential energy is the primary equilibrium signal. Detection requires a persistent change with a meaningful magnitude relative to the robust natural energy fluctuations, so ordinary correlated thermal noise and isolated spikes are not classified as abnormal. An abnormal classification always requires a mapped potential-energy change. When at least two lattice-parameter or volume signals show the same mapped transition, they may enable a more sensitive noise-relative energy test; this avoids a fixed eV or percentage-of-total-energy threshold that would depend on system size. Cell signals can corroborate and refine the energy interval, but can never classify a run by themselves. Temperature and all six pressure components remain diagnostics only and cannot classify an otherwise stationary run as abnormal by themselves. A lone cell-axis drift, an isolated spike, or an empty anomaly mask never triggers a whole-run fallback. Thermo plots use Arial when available and Matplotlib's built-in qualitative `Dark2` color cycle. They plot all six pressure components, use frameless upper-right legends with added vertical headroom, preserve correct Å units, and show abnormal regions as shaded legend entries on potential energy, lattice parameters, and volume. Potential energy and volume are divided by 1000 and labeled ×10³ only when their absolute plotted values reach 1000. The analysis uses the last `run` segment, or the last 80% when `run.in` has no `run` record; inspect the plot before making a final scientific judgment.
|
|
253
303
|
|
|
@@ -301,7 +351,8 @@ pymdkit/
|
|
|
301
351
|
|-- _gpumd.py # shared GPUMD run.in and thermo analysis
|
|
302
352
|
|-- _geometry.py # shared minimum-image geometry helper
|
|
303
353
|
|-- _vaspset.py # shared VASP input-set helper (not a command)
|
|
304
|
-
|-- gpumd_group.py
|
|
354
|
+
|-- gpumd_group.py
|
|
355
|
+
|-- gpumd_msd.py
|
|
305
356
|
|-- gpumd_relax.py
|
|
306
357
|
|-- gpumd_thermo.py
|
|
307
358
|
|-- gpumd_trjcat.py
|
|
@@ -315,7 +366,8 @@ pymdkit/
|
|
|
315
366
|
|-- _cifio.py
|
|
316
367
|
|-- chemsys_entry.py
|
|
317
368
|
|-- submit_vasp.py
|
|
318
|
-
|-- convert.py
|
|
369
|
+
|-- convert.py
|
|
370
|
+
|-- realloc_occ.py
|
|
319
371
|
|-- substitute.py
|
|
320
372
|
|-- supercell.py
|
|
321
373
|
|-- vasp2xyz.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pymdkit"
|
|
7
|
-
version = "1.5.
|
|
7
|
+
version = "1.5.2"
|
|
8
8
|
description = "A unified command-line toolkit for atomistic / MD structure workflows."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -26,7 +26,7 @@ classifiers = [
|
|
|
26
26
|
dependencies = [
|
|
27
27
|
"numpy",
|
|
28
28
|
"ase",
|
|
29
|
-
"pymatgen",
|
|
29
|
+
"pymatgen",
|
|
30
30
|
"mp-api",
|
|
31
31
|
"py4vasp-core",
|
|
32
32
|
"dpdata",
|
|
@@ -34,14 +34,14 @@ dependencies = [
|
|
|
34
34
|
"umap-learn",
|
|
35
35
|
]
|
|
36
36
|
|
|
37
|
-
[project.optional-dependencies]
|
|
38
|
-
# CuPy wheels are CUDA-major-specific, so GPU support stays optional.
|
|
39
|
-
gpu-cuda12 = ["cupy-cuda12x"]
|
|
40
|
-
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
# CuPy wheels are CUDA-major-specific, so GPU support stays optional.
|
|
39
|
+
gpu-cuda12 = ["cupy-cuda12x"]
|
|
40
|
+
|
|
41
41
|
[project.scripts]
|
|
42
42
|
# The distribution remains pymdkit; the concise public executable is `pmk`.
|
|
43
43
|
pmk = "pymdkit.pymdkit_main:main"
|
|
44
44
|
|
|
45
45
|
[tool.setuptools.packages.find]
|
|
46
46
|
where = ["src"]
|
|
47
|
-
|
|
47
|
+
|
|
@@ -15,14 +15,18 @@ DEFAULT_SYMPREC = 0.02
|
|
|
15
15
|
DEFAULT_ANGLE_TOLERANCE = 5.0
|
|
16
16
|
BILBAO_WYCKPOS_URL = "https://cryst.ehu.es/cryst/get_wp.html"
|
|
17
17
|
WYCKOFF_TAG = "_atom_site_Wyckoff_symbol"
|
|
18
|
-
|
|
18
|
+
SOURCE_MULTIPLICITY_TAG = "_atom_site_symmetry_multiplicity"
|
|
19
|
+
MULTIPLICITY_TAG = "_atom_site_site_symmetry_multiplicity"
|
|
19
20
|
CRYSTAL_SYSTEM_TAG = "_space_group_crystal_system"
|
|
20
21
|
CRYSTAL_SYSTEMS = {
|
|
21
22
|
"triclinic", "monoclinic", "orthorhombic", "tetragonal",
|
|
22
23
|
"trigonal", "hexagonal", "cubic",
|
|
23
24
|
}
|
|
24
|
-
OCCUPANCY_TAG = "_atom_site_occupancy"
|
|
25
|
+
OCCUPANCY_TAG = "_atom_site_occupancy"
|
|
25
26
|
OXIDATION_TAG = "_atom_type_oxidation_number"
|
|
27
|
+
ATOM_TYPE_TAG = "_atom_type_symbol"
|
|
28
|
+
SITE_LABEL_TAG = "_atom_site_label"
|
|
29
|
+
SITE_TYPE_TAG = "_atom_site_type_symbol"
|
|
26
30
|
CIF_FLOAT_TAGS = {
|
|
27
31
|
"_cell_length_a", "_cell_length_b", "_cell_length_c",
|
|
28
32
|
"_cell_angle_alpha", "_cell_angle_beta", "_cell_angle_gamma",
|
|
@@ -133,8 +137,27 @@ def _site_wyckoff_columns(block, symmetrized):
|
|
|
133
137
|
return letters
|
|
134
138
|
|
|
135
139
|
|
|
140
|
+
def _rename_block_tag(block, old_tag, new_tag):
|
|
141
|
+
if old_tag not in block.data:
|
|
142
|
+
if new_tag not in block.data:
|
|
143
|
+
raise KeyError(f"CIF block has neither {old_tag} nor {new_tag}")
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
ordered = []
|
|
147
|
+
for tag, value in block.data.items():
|
|
148
|
+
if tag == old_tag:
|
|
149
|
+
ordered.append((new_tag, value))
|
|
150
|
+
elif tag != new_tag:
|
|
151
|
+
ordered.append((tag, value))
|
|
152
|
+
block.data.clear()
|
|
153
|
+
block.data.update(ordered)
|
|
154
|
+
for loop in block.loops:
|
|
155
|
+
loop[:] = [new_tag if tag == old_tag else tag for tag in loop]
|
|
156
|
+
|
|
157
|
+
|
|
136
158
|
def _add_wyckoff_column(cif_file, symmetrized):
|
|
137
159
|
block = next(iter(cif_file.data.values()))
|
|
160
|
+
_rename_block_tag(block, SOURCE_MULTIPLICITY_TAG, MULTIPLICITY_TAG)
|
|
138
161
|
letters = _site_wyckoff_columns(block, symmetrized)
|
|
139
162
|
block.data[WYCKOFF_TAG] = letters
|
|
140
163
|
atom_loop = next(
|
|
@@ -174,11 +197,11 @@ def _format_cif_scalar(value, precision, force=False):
|
|
|
174
197
|
|
|
175
198
|
def _format_cif_numbers(block):
|
|
176
199
|
for tag, value in list(block.data.items()):
|
|
177
|
-
if tag == OCCUPANCY_TAG:
|
|
178
|
-
precision = 2
|
|
179
|
-
elif tag == OXIDATION_TAG:
|
|
180
|
-
precision = 1
|
|
181
|
-
else:
|
|
200
|
+
if tag == OCCUPANCY_TAG:
|
|
201
|
+
precision = 2
|
|
202
|
+
elif tag == OXIDATION_TAG:
|
|
203
|
+
precision = 1
|
|
204
|
+
else:
|
|
182
205
|
precision = _precision.FLOAT_PRECISION
|
|
183
206
|
force = tag == OCCUPANCY_TAG or tag in CIF_FLOAT_TAGS
|
|
184
207
|
if isinstance(value, (list, tuple)):
|
|
@@ -198,25 +221,92 @@ def _element_symbol(type_symbol):
|
|
|
198
221
|
|
|
199
222
|
|
|
200
223
|
def _relabel_atom_types(block):
|
|
201
|
-
"""
|
|
202
|
-
|
|
203
|
-
|
|
224
|
+
"""Separate unique site labels from elemental site type symbols."""
|
|
225
|
+
site_types = list(block.data[SITE_TYPE_TAG])
|
|
226
|
+
old_types = list(block.data.get(ATOM_TYPE_TAG, []))
|
|
227
|
+
old_oxidations = list(block.data.get(OXIDATION_TAG, []))
|
|
204
228
|
oxidation_by_type = dict(zip(old_types, old_oxidations))
|
|
205
|
-
|
|
229
|
+
oxidation_by_element = {}
|
|
230
|
+
for old_type, oxidation in oxidation_by_type.items():
|
|
231
|
+
oxidation_by_element.setdefault(_element_symbol(old_type), oxidation)
|
|
206
232
|
|
|
207
233
|
counts = {}
|
|
208
234
|
labels = []
|
|
235
|
+
elements = []
|
|
209
236
|
oxidations = []
|
|
210
237
|
for old_type in site_types:
|
|
211
238
|
element = _element_symbol(old_type)
|
|
212
239
|
counts[element] = counts.get(element, 0) + 1
|
|
213
240
|
labels.append(f"{element}{counts[element]}")
|
|
214
|
-
|
|
241
|
+
elements.append(element)
|
|
242
|
+
oxidations.append(
|
|
243
|
+
oxidation_by_type.get(
|
|
244
|
+
old_type,
|
|
245
|
+
oxidation_by_element.get(element, 0),
|
|
246
|
+
)
|
|
247
|
+
)
|
|
215
248
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
249
|
+
atom_site_loop = next(
|
|
250
|
+
(loop for loop in block.loops if SITE_TYPE_TAG in loop),
|
|
251
|
+
None,
|
|
252
|
+
)
|
|
253
|
+
if atom_site_loop is None:
|
|
254
|
+
raise KeyError(f"CIF block has no atom-site loop containing {SITE_TYPE_TAG}")
|
|
255
|
+
atom_site_loop[:] = [
|
|
256
|
+
SITE_LABEL_TAG,
|
|
257
|
+
SITE_TYPE_TAG,
|
|
258
|
+
*(
|
|
259
|
+
tag for tag in atom_site_loop
|
|
260
|
+
if tag not in {SITE_LABEL_TAG, SITE_TYPE_TAG}
|
|
261
|
+
),
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
type_loop_index = None
|
|
265
|
+
cleaned_loops = []
|
|
266
|
+
for loop in block.loops:
|
|
267
|
+
if ATOM_TYPE_TAG in loop or OXIDATION_TAG in loop:
|
|
268
|
+
if type_loop_index is None:
|
|
269
|
+
type_loop_index = len(cleaned_loops)
|
|
270
|
+
remaining = [
|
|
271
|
+
tag for tag in loop
|
|
272
|
+
if tag not in {ATOM_TYPE_TAG, OXIDATION_TAG}
|
|
273
|
+
]
|
|
274
|
+
if remaining:
|
|
275
|
+
cleaned_loops.append(remaining)
|
|
276
|
+
else:
|
|
277
|
+
cleaned_loops.append(loop)
|
|
278
|
+
atom_type_loop = [ATOM_TYPE_TAG, OXIDATION_TAG]
|
|
279
|
+
if type_loop_index is None:
|
|
280
|
+
site_loop_index = cleaned_loops.index(atom_site_loop)
|
|
281
|
+
cleaned_loops.insert(site_loop_index, atom_type_loop)
|
|
282
|
+
else:
|
|
283
|
+
cleaned_loops.insert(type_loop_index, atom_type_loop)
|
|
284
|
+
block.loops[:] = cleaned_loops
|
|
285
|
+
|
|
286
|
+
ordered = []
|
|
287
|
+
atom_types_inserted = False
|
|
288
|
+
for tag, value in block.data.items():
|
|
289
|
+
if tag in {ATOM_TYPE_TAG, OXIDATION_TAG}:
|
|
290
|
+
continue
|
|
291
|
+
if not atom_types_inserted and tag in atom_site_loop:
|
|
292
|
+
ordered.extend([
|
|
293
|
+
(ATOM_TYPE_TAG, labels),
|
|
294
|
+
(OXIDATION_TAG, oxidations),
|
|
295
|
+
])
|
|
296
|
+
atom_types_inserted = True
|
|
297
|
+
if tag == SITE_LABEL_TAG:
|
|
298
|
+
value = labels
|
|
299
|
+
elif tag == SITE_TYPE_TAG:
|
|
300
|
+
value = elements
|
|
301
|
+
ordered.append((tag, value))
|
|
302
|
+
if not atom_types_inserted:
|
|
303
|
+
ordered.extend([
|
|
304
|
+
(ATOM_TYPE_TAG, labels),
|
|
305
|
+
(OXIDATION_TAG, oxidations),
|
|
306
|
+
])
|
|
307
|
+
|
|
308
|
+
block.data.clear()
|
|
309
|
+
block.data.update(ordered)
|
|
220
310
|
return labels
|
|
221
311
|
|
|
222
312
|
|
|
@@ -109,14 +109,13 @@ def equilibrium_start(length, runs):
|
|
|
109
109
|
return max(0, length - points)
|
|
110
110
|
return int(length * 0.2)
|
|
111
111
|
|
|
112
|
-
def
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
vectors =
|
|
119
|
-
lengths = np.linalg.norm(vectors, axis=2)
|
|
112
|
+
def lattice_angles(vectors):
|
|
113
|
+
"""Return alpha, beta, gamma in degrees for one or more 3x3 cells."""
|
|
114
|
+
vectors = np.asarray(vectors, dtype=float)
|
|
115
|
+
single = vectors.ndim == 2
|
|
116
|
+
if vectors.shape[-2:] != (3, 3):
|
|
117
|
+
raise ValueError(f"cell vectors must have shape (..., 3, 3), got {vectors.shape}")
|
|
118
|
+
vectors = vectors.reshape(-1, 3, 3)
|
|
120
119
|
|
|
121
120
|
def angle(first, second):
|
|
122
121
|
denominator = np.linalg.norm(first, axis=1) * np.linalg.norm(second, axis=1)
|
|
@@ -131,6 +130,18 @@ def cell_quantities(data):
|
|
|
131
130
|
angle(vectors[:, 2], vectors[:, 0]),
|
|
132
131
|
angle(vectors[:, 0], vectors[:, 1]),
|
|
133
132
|
])
|
|
133
|
+
return angles[0] if single else angles
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def cell_quantities(data):
|
|
137
|
+
if data.shape[1] != 18:
|
|
138
|
+
raise ValueError(
|
|
139
|
+
f"thermo.out has {data.shape[1]} columns; expected the official 18-column format"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
vectors = data[:, 9:18].reshape(-1, 3, 3)
|
|
143
|
+
lengths = np.linalg.norm(vectors, axis=2)
|
|
144
|
+
angles = lattice_angles(vectors)
|
|
134
145
|
return lengths, angles, np.abs(np.linalg.det(vectors))
|
|
135
146
|
|
|
136
147
|
|