pymdkit 1.3.0__tar.gz → 1.4.1__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.
Files changed (49) hide show
  1. pymdkit-1.4.1/PKG-INFO +278 -0
  2. pymdkit-1.4.1/README.md +252 -0
  3. {pymdkit-1.3.0 → pymdkit-1.4.1}/pyproject.toml +7 -5
  4. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/__init__.py +1 -1
  5. pymdkit-1.4.1/src/pymdkit/commands/_extxyz.py +85 -0
  6. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/_fileio.py +15 -8
  7. pymdkit-1.4.1/src/pymdkit/commands/_geometry.py +12 -0
  8. pymdkit-1.4.1/src/pymdkit/commands/_gpumd.py +172 -0
  9. pymdkit-1.4.1/src/pymdkit/commands/_reduction.py +165 -0
  10. pymdkit-1.3.0/src/pymdkit/commands/stable_entry.py → pymdkit-1.4.1/src/pymdkit/commands/chemsys_entry.py +2 -2
  11. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/compute_ehull.py +4 -8
  12. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/compute_msd_all_groups.py +2 -2
  13. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/compute_rmsd.py +10 -20
  14. pymdkit-1.4.1/src/pymdkit/commands/convert.py +160 -0
  15. pymdkit-1.3.0/src/pymdkit/commands/electrostatic_energy.py → pymdkit-1.4.1/src/pymdkit/commands/ewald.py +3 -3
  16. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/gather_contcar.py +1 -1
  17. pymdkit-1.3.0/src/pymdkit/commands/add_groups.py → pymdkit-1.4.1/src/pymdkit/commands/gpumd_group.py +1 -1
  18. pymdkit-1.4.1/src/pymdkit/commands/gpumd_thermo.py +131 -0
  19. pymdkit-1.4.1/src/pymdkit/commands/gpumd_trjcat.py +134 -0
  20. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/nep_rmse.py +28 -33
  21. pymdkit-1.4.1/src/pymdkit/commands/pca.py +39 -0
  22. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/substitute.py +11 -13
  23. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/symmetrize.py +6 -16
  24. pymdkit-1.4.1/src/pymdkit/commands/trj_extract.py +99 -0
  25. pymdkit-1.4.1/src/pymdkit/commands/umap.py +38 -0
  26. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/vasp2xyz.py +9 -17
  27. pymdkit-1.3.0/src/pymdkit/commands/final_energy.py → pymdkit-1.4.1/src/pymdkit/commands/vasp_fe.py +4 -8
  28. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/pymdkit_main.py +6 -6
  29. pymdkit-1.4.1/src/pymdkit.egg-info/PKG-INFO +278 -0
  30. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit.egg-info/SOURCES.txt +14 -5
  31. pymdkit-1.4.1/src/pymdkit.egg-info/entry_points.txt +2 -0
  32. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit.egg-info/requires.txt +2 -0
  33. pymdkit-1.3.0/PKG-INFO +0 -249
  34. pymdkit-1.3.0/README.md +0 -225
  35. pymdkit-1.3.0/src/pymdkit/commands/stru2xyz.py +0 -100
  36. pymdkit-1.3.0/src/pymdkit.egg-info/PKG-INFO +0 -249
  37. pymdkit-1.3.0/src/pymdkit.egg-info/entry_points.txt +0 -2
  38. {pymdkit-1.3.0 → pymdkit-1.4.1}/LICENSE +0 -0
  39. {pymdkit-1.3.0 → pymdkit-1.4.1}/setup.cfg +0 -0
  40. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/__init__.py +0 -0
  41. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/_vasp_output.py +0 -0
  42. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/_vaspset.py +0 -0
  43. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/perturb.py +0 -0
  44. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/submit_vasp.py +0 -0
  45. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/supercell.py +0 -0
  46. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/vasp_relax.py +0 -0
  47. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit/commands/vasp_static.py +0 -0
  48. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit.egg-info/dependency_links.txt +0 -0
  49. {pymdkit-1.3.0 → pymdkit-1.4.1}/src/pymdkit.egg-info/top_level.txt +0 -0
pymdkit-1.4.1/PKG-INFO ADDED
@@ -0,0 +1,278 @@
1
+ Metadata-Version: 2.4
2
+ Name: pymdkit
3
+ Version: 1.4.1
4
+ Summary: A unified command-line toolkit for atomistic / MD structure workflows.
5
+ Author-email: Yueda Wang <ydwang0608@ustc.edu.cn>
6
+ License-Expression: GPL-3.0-or-later
7
+ Keywords: materials-science,molecular-dynamics,vasp,gpumd,ase,pymatgen
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
12
+ Classifier: Topic :: Scientific/Engineering :: Physics
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: numpy
17
+ Requires-Dist: ase
18
+ Requires-Dist: pymatgen
19
+ Requires-Dist: mp-api
20
+ Requires-Dist: py4vasp-core
21
+ Requires-Dist: pyxtal
22
+ Requires-Dist: dpdata
23
+ Requires-Dist: matplotlib
24
+ Requires-Dist: umap-learn
25
+ Dynamic: license-file
26
+
27
+ # pymdkit
28
+
29
+ A single command-line tool that bundles a collection of atomistic / molecular-dynamics
30
+ structure scripts behind one executable: `pmk`. Instead of copying individual
31
+ scripts into each working folder and running `python some_script.py`, you install
32
+ `pymdkit` once and call any tool from anywhere as `pmk <command> [options]`.
33
+
34
+ Every command exposes named `-flags` (no positional guessing), and each underlying
35
+ script is still runnable on its own.
36
+
37
+ ## Install
38
+
39
+ Create a clean conda environment, activate it, then install `pymdkit` with pip:
40
+
41
+ ```bash
42
+ conda create -n pymdkit python=3.10
43
+ conda activate pymdkit
44
+ pip install pymdkit
45
+ ```
46
+
47
+ This installs the `pmk` command into the active conda environment, together
48
+ with its direct dependencies, including ASE, pymatgen, py4vasp-core, dpdata, matplotlib, and UMAP. Some of these packages may install their own transitive dependencies.
49
+
50
+ Verify:
51
+
52
+ ```bash
53
+ pmk -version
54
+ pmk -help # lists every command
55
+ pmk <command> -help # shows that command's flags
56
+ ```
57
+
58
+ ## Commands
59
+
60
+ Commands that transform structures accept either a single file (`-i`/`-o`) or a
61
+ whole folder (`-if`/`-of`); commands that analyse VASP runs scan the current
62
+ directory for job sub-folders automatically.
63
+
64
+ | Command | What it does |
65
+ |---|---|
66
+ | `gpumd-group` | Tag atoms with a GPUMD group index by element order |
67
+ | `ewald` | Compute CIF electrostatic energy with pymatgen EwaldSummation |
68
+ | `ehull` | Auto-detect VASP job folders and compute E_hull vs Materials Project |
69
+ | `vasp-fe` | List VASP job final energies from lowest to highest |
70
+ | `gather-contcar` | Collect CONTCARs from VASP job folders into one folder, renamed `<folder>.vasp` |
71
+ | `gpumd-thermo` | Export and plot GPUMD thermo data and detect sustained equilibrium changes |
72
+ | `gpumd-trjcat` | Merge trajectories from GPUMD jobs with stable equilibrium thermo data |
73
+ | `pca` | Reduce descriptor data to two dimensions; optional FPS sampling |
74
+ | `trj-extract` | Extract a GPUMD XYZ trajectory by time range |
75
+ | `umap` | Reduce descriptor data to two dimensions with UMAP; optional FPS sampling |
76
+ | `msd` | Diffusivity & conductivity from GPUMD MSD jobs (auto-scans `<structure>/<temp>/`) |
77
+ | `nep-rmse` | Compute NEP energy/force/stress RMSE with terminal plots and optional candidate selection |
78
+ | `perturb` | Generate perturbed structures with dpdata |
79
+ | `vasp2xyz` | Collect SCF-converged VASP job folders (any name) into one extxyz file |
80
+ | `submit-vasp` | Submit/resubmit VASP job folders while limiting active queue jobs |
81
+ | `rmsd` | Compute RMSD between two structure files, or all pairs in a folder |
82
+ | `chemsys-entry` | Download stable Materials Project structures for a chemical system |
83
+ | `convert` | Convert structures, trajectories, or folders between ASE/pymatgen formats |
84
+ | `substitute` | Randomly substitute or remove selected atoms/sites from a structure |
85
+ | `supercell` | Build a supercell with cell lengths capped at a maximum (Angstrom); optional per-temperature GPUMD setup |
86
+ | `symmetrize` | Import space-group symmetry into a structure file (or folder) -> CIF |
87
+ | `vasp-relax` | Write VASP relaxation inputs for a structure (or folder); INCAR tags overridable |
88
+ | `vasp-static` | Write VASP static / single-point inputs for a structure (or folder) |
89
+
90
+ ## Examples
91
+
92
+ ```bash
93
+ pmk gpumd-group -i opted.cif -elements Li Y Cl -o model.xyz
94
+ pmk gpumd-group -if cifs/ -elements Li Y Cl -of cifs-grouped/ # whole folder
95
+ pmk gpumd-group -elements Li Y Cl # scan subfolders, tag each model.xyz in place
96
+ pmk convert -i opted.vasp -o opted.xyz # convert one structure
97
+ pmk convert -if vasp-opted -of cif-opted -oe cif # folder conversion target format
98
+ pmk convert -it train.xyz -ot train.extxyz # convert a complete trajectory
99
+ pmk supercell -i opted.vasp -o sc.vasp -max-abc 20 # cell lengths <= 20 A
100
+ pmk supercell -if vasp-opted -max-abc 20 -individual # per-structure ./<name>/<name>.<ext>
101
+ pmk supercell -if extxyz-opted -max-abc 24 -individual -temp 500 600 -md-if input-files -add-groups Li Y Cl
102
+ # GPUMD: ./<name>/model.xyz (grouped) + ./<name>/<T>/ jobs
103
+ pmk vasp-relax -i opted.vasp # relax inputs in current dir
104
+ pmk vasp-relax -if optimal_occupancy # one ./<name>/ job folder per structure
105
+ pmk vasp-static -if cifs/ -custom-setting my_incar.txt # static inputs, custom INCAR
106
+ pmk vasp-static -it traj.xyz # one ./frame_N/ job per trajectory frame
107
+ pmk msd # scans <structure>/<temp>/ -> per-job msd/ + msd_summary.txt
108
+ pmk msd -diffuse_ion Li -ion_charge 1 # choose the mobile ion for conductivity
109
+ pmk chemsys-entry -s Li La Ta Cl # MP stable entries -> Li-La-Ta-Cl-stable-entries/
110
+ pmk ehull -mp-api-key $MP_API_KEY # scans ./ for VASP jobs -> ehull.txt
111
+ pmk ehull -local Li-La-Ta-Cl-stable-entries-opted
112
+ pmk vasp-fe # scans ./ for VASP jobs -> final-energy.txt with convergence status
113
+ pmk gather-contcar -of vasp-opted # CONTCARs -> vasp-opted/<folder>.vasp
114
+ pmk gather-contcar -of vasp-opted -ehull 0.028 # only structures with E_hull < 0.028 eV/atom
115
+ pmk vasp2xyz # scans ./ for VASP output folders -> scf-converged.xyz
116
+ pmk vasp2xyz -position-only # write positions only, without energy/forces/stress
117
+ pmk submit-vasp -subscript sub_vasp -queue slurm -max-job-num 30
118
+ nohup pmk submit-vasp -subscript sub_vasp -queue slurm -max-job-num 30 > submit-vasp.log 2>&1 &
119
+ pmk substitute -i Li3YCl6.cif -se Li -sn 3 -we Na -wn 3 -on 100
120
+ pmk substitute -i Li3YCl6.cif -se Li -sn 3 -we none -on 100
121
+ pmk substitute -i Li96Ta6La11Cl72.cif -se Li1 Li2 -sn 20 67 -we none -on 100
122
+ pmk substitute -i Li96Ta6La11Cl72.cif -se Li2 -we none -ref La Ta -d 1.01 1.02
123
+ pmk substitute -i Li96Ta6La11Cl72.cif -se Li2 -we none -ref La Ta
124
+ pmk ewald -i Li3YCl6.cif
125
+ pmk ewald -if Li3YCl6-all
126
+ pmk nep-rmse # writes energy/force/stress train txt files, rmse_value.txt, and terminal plots
127
+ pmk nep-rmse -select -xyz train.xyz # interactive candidate selection; writes candidate.xyz and accurate.xyz
128
+ pmk perturb -i example.xyz -atom 0.2 -lattice 0.03 -n 100 -o example-perturb-atom-0.2-lattice-0.03.xyz
129
+ pmk pca -i descriptor.out -o pca-descriptor.txt -it train.xyz # also split descriptors by Config_type
130
+ pmk pca -i descriptor.out -o pca-descriptor.txt -fps 0.01 -it train.xyz
131
+ pmk umap -i descriptor.out -o umap-descriptor.txt -fps 0.01 -it train.xyz
132
+ pmk gpumd-thermo # current job, or recursively scan job folders
133
+ pmk gpumd-thermo -t thermo.out -in run.in
134
+ pmk gpumd-trjcat # stable ./<job>/traj.xyz files -> train.xyz
135
+ pmk gpumd-trjcat -i traj.xyz -o train.xyz
136
+ pmk trj-extract -it traj.xyz -in run.in -b 100 -e 200 -o traj-100ps-200ps.xyz
137
+
138
+ pmk rmsd a.cif b.cif # RMSD of two files -> rmsd.txt
139
+ pmk rmsd vasp-opted/ # all pairs in a folder -> rmsd.txt
140
+ pmk symmetrize -i opted.cif -symprec 0.01 -add_oxidation yes -o opted-symm.cif
141
+ pmk symmetrize -if my_cifs/ -symprec 0.01 -add_oxidation no -of my_cifs-symm
142
+ ```
143
+
144
+ VASP input commands (`vasp-relax`, `vasp-static`) always produce **individual**
145
+ jobs (one structure per folder): `-i` writes into the current dir (or `-o`),
146
+ `-if` creates one `./<name>/` folder per structure, and `-it` creates one
147
+ `./frame_N/` folder per trajectory frame - all directly in the current path.
148
+
149
+ They start from sensible default INCAR settings; override them by passing a
150
+ settings file with `-custom-setting FILE`. The file may be a Python-dict block
151
+ or `KEY = VALUE` lines (a `None`/blank value clears a tag):
152
+
153
+ ```text
154
+ custom_settings = {
155
+ "ENCUT": "600.0",
156
+ "ISIF": "3",
157
+ "MAGMOM": None
158
+ }
159
+ ```
160
+
161
+ `vasp-static -it traj.xyz` (also available on `vasp-relax`) reads a
162
+ multi-structure trajectory and writes one job sub-folder per frame
163
+ (`frame_1/`, `frame_2/`, ..., prefix configurable via `-frame-prefix`). Each
164
+ folder also keeps a `frame_N.xyz`, so `Config_type` survives for a later
165
+ `vasp2xyz`.
166
+
167
+ `nep-rmse -select`, PCA FPS, and UMAP FPS copy selected extended-XYZ frame blocks directly from the input trajectory. No parser rewrites energy, stress, forces, positions, precision, or extra metadata.
168
+
169
+ 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.
170
+
171
+ For folder conversion, `convert -if INPUT -of OUTPUT` writes `.xyz` by default. Set another target extension with `-oe`, for example `-oe cif` or `-oe vasp`. Single-file and trajectory modes infer the target format from `-o` and `-ot`.
172
+
173
+ `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. Its warning is a numerical reminder based on sustained drift or a level shift in the last `run` segment (or the last 80% when `run.in` has no `run` record); inspect the plot before deciding whether a trajectory is equilibrated.
174
+
175
+ `gpumd-trjcat` recursively scans job subfolders for `traj.xyz`, `thermo.out`, and `run.in`. Jobs with missing, unreadable, or abnormal thermo data are skipped. Stable trajectories are merged in deterministic folder order. When `<job>/model.xyz` contains `Config_type`, that value is applied to every frame from the job while all other extended-XYZ bytes remain unchanged.
176
+
177
+ Each command's full flag list is in `pmk <command> -help`.
178
+
179
+ For long VASP batch submission, run `submit-vasp` with `nohup` and `&` if you
180
+ want it to keep sleeping, checking the queue, and submitting new jobs after you
181
+ exit the terminal:
182
+
183
+ ```bash
184
+ nohup pmk submit-vasp -subscript sub_vasp -queue slurm -max-job-num 30 > submit-vasp.log 2>&1 &
185
+ ```
186
+
187
+ The command itself controls the loop: it submits until the active queue reaches
188
+ `-max-job-num`, sleeps when the queue is full, checks again, and continues until
189
+ all needed jobs are submitted. `nohup ... &` is what makes that loop continue in
190
+ the background after logout.
191
+
192
+ `substitute -ref` removes selected sites near reference sites and writes one
193
+ `<input-stem>_substitute.cif` in the current path. If `-d` is omitted, each
194
+ cutoff is `0.7 * (selected covalent radius + reference covalent radius)` using
195
+ the covalent radii from Cordero et al., Dalton Trans., 2008, 2832-2838.
196
+
197
+ VASP-output readers (`vasp2xyz`, `ehull`, `vasp-fe`, and other future VASP-output
198
+ commands) use the global priority `vaspout.h5 > vasprun.xml > OUTCAR`.
199
+
200
+ `ehull` auto-detects every sub-folder of the current path that contains a
201
+ supported VASP output, groups them by chemical system (elements ordered by
202
+ electronegativity, e.g. `Li-Y-Cl`), and builds/reuses one `mp_cache_<system>.json`
203
+ per system - so a pure Li-Y-Cl batch yields a single `mp_cache_Li-Y-Cl.json`, while
204
+ a mixed Li-Y-Cl + La-O batch yields both `mp_cache_Li-Y-Cl.json` and
205
+ `mp_cache_La-O.json`. (Formation energy is reported alongside E_hull in
206
+ `ehull.txt`.)
207
+
208
+ ## Layout
209
+
210
+ ```
211
+ pymdkit/
212
+ |-- pyproject.toml # package metadata + the `pmk` entry point
213
+ |-- README.md
214
+ `-- src/pymdkit/
215
+ |-- pymdkit_main.py # dispatcher: discovers and runs commands
216
+ `-- commands/ # one module per command
217
+ |-- _fileio.py # shared -i/-o/-if/-of helper (not a command)
218
+ |-- _gpumd.py # shared GPUMD run.in and thermo analysis
219
+ |-- _geometry.py # shared minimum-image geometry helper
220
+ |-- _vaspset.py # shared VASP input-set helper (not a command)
221
+ |-- gpumd_group.py
222
+ |-- gpumd_thermo.py
223
+ |-- gpumd_trjcat.py
224
+ |-- compute_ehull.py
225
+ |-- compute_rmsd.py
226
+ |-- ewald.py
227
+ |-- vasp_fe.py
228
+ |-- perturb.py
229
+ |-- nep_rmse.py
230
+ |-- chemsys_entry.py
231
+ |-- submit_vasp.py
232
+ |-- convert.py
233
+ |-- substitute.py
234
+ |-- supercell.py
235
+ |-- vasp2xyz.py
236
+ |-- vasp_relax.py
237
+ |-- vasp_static.py
238
+ |-- ...
239
+ `-- symmetrize.py
240
+ ```
241
+
242
+ Modules whose name starts with `_` are shared helpers and are skipped by the
243
+ dispatcher, so they never appear as commands.
244
+
245
+ ## Adding a new tool later
246
+
247
+ Drop a module in `src/pymdkit/commands/` that defines four things:
248
+
249
+ ```python
250
+ COMMAND = "my-tool" # the subcommand name you'll type
251
+ HELP = "One-line description."
252
+
253
+ def add_arguments(parser): # register flags
254
+ parser.add_argument("-input", required=True)
255
+
256
+ def run(args): # do the work; return an exit code (0 = ok)
257
+ ...
258
+ return 0
259
+
260
+ if __name__ == "__main__": # keeps the script runnable on its own
261
+ import argparse
262
+ _p = argparse.ArgumentParser(description=__doc__)
263
+ add_arguments(_p)
264
+ raise SystemExit(run(_p.parse_args()))
265
+ ```
266
+
267
+ It will appear in `pmk -help` automatically - no central registration needed.
268
+ Put heavy imports (pymatgen, ase, ...) inside `run()` where practical; the dispatcher
269
+ reads each command's name and help without importing it, so `pmk -help` stays
270
+ fast and a missing optional dependency only affects the one command that needs it.
271
+
272
+ ## Running a script standalone
273
+
274
+ Every command module still works directly, which is handy for debugging:
275
+
276
+ ```bash
277
+ python src/pymdkit/commands/supercell.py -i in.cif -max-abc 20 -o sc.vasp
278
+ ```
@@ -0,0 +1,252 @@
1
+ # pymdkit
2
+
3
+ A single command-line tool that bundles a collection of atomistic / molecular-dynamics
4
+ structure scripts behind one executable: `pmk`. Instead of copying individual
5
+ scripts into each working folder and running `python some_script.py`, you install
6
+ `pymdkit` once and call any tool from anywhere as `pmk <command> [options]`.
7
+
8
+ Every command exposes named `-flags` (no positional guessing), and each underlying
9
+ script is still runnable on its own.
10
+
11
+ ## Install
12
+
13
+ Create a clean conda environment, activate it, then install `pymdkit` with pip:
14
+
15
+ ```bash
16
+ conda create -n pymdkit python=3.10
17
+ conda activate pymdkit
18
+ pip install pymdkit
19
+ ```
20
+
21
+ This installs the `pmk` command into the active conda environment, together
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
+
24
+ Verify:
25
+
26
+ ```bash
27
+ pmk -version
28
+ pmk -help # lists every command
29
+ pmk <command> -help # shows that command's flags
30
+ ```
31
+
32
+ ## Commands
33
+
34
+ Commands that transform structures accept either a single file (`-i`/`-o`) or a
35
+ whole folder (`-if`/`-of`); commands that analyse VASP runs scan the current
36
+ directory for job sub-folders automatically.
37
+
38
+ | Command | What it does |
39
+ |---|---|
40
+ | `gpumd-group` | Tag atoms with a GPUMD group index by element order |
41
+ | `ewald` | Compute CIF electrostatic energy with pymatgen EwaldSummation |
42
+ | `ehull` | Auto-detect VASP job folders and compute E_hull vs Materials Project |
43
+ | `vasp-fe` | List VASP job final energies from lowest to highest |
44
+ | `gather-contcar` | Collect CONTCARs from VASP job folders into one folder, renamed `<folder>.vasp` |
45
+ | `gpumd-thermo` | Export and plot GPUMD thermo data and detect sustained equilibrium changes |
46
+ | `gpumd-trjcat` | Merge trajectories from GPUMD jobs with stable equilibrium thermo data |
47
+ | `pca` | Reduce descriptor data to two dimensions; optional FPS sampling |
48
+ | `trj-extract` | Extract a GPUMD XYZ trajectory by time range |
49
+ | `umap` | Reduce descriptor data to two dimensions with UMAP; optional FPS sampling |
50
+ | `msd` | Diffusivity & conductivity from GPUMD MSD jobs (auto-scans `<structure>/<temp>/`) |
51
+ | `nep-rmse` | Compute NEP energy/force/stress RMSE with terminal plots and optional candidate selection |
52
+ | `perturb` | Generate perturbed structures with dpdata |
53
+ | `vasp2xyz` | Collect SCF-converged VASP job folders (any name) into one extxyz file |
54
+ | `submit-vasp` | Submit/resubmit VASP job folders while limiting active queue jobs |
55
+ | `rmsd` | Compute RMSD between two structure files, or all pairs in a folder |
56
+ | `chemsys-entry` | Download stable Materials Project structures for a chemical system |
57
+ | `convert` | Convert structures, trajectories, or folders between ASE/pymatgen formats |
58
+ | `substitute` | Randomly substitute or remove selected atoms/sites from a structure |
59
+ | `supercell` | Build a supercell with cell lengths capped at a maximum (Angstrom); optional per-temperature GPUMD setup |
60
+ | `symmetrize` | Import space-group symmetry into a structure file (or folder) -> CIF |
61
+ | `vasp-relax` | Write VASP relaxation inputs for a structure (or folder); INCAR tags overridable |
62
+ | `vasp-static` | Write VASP static / single-point inputs for a structure (or folder) |
63
+
64
+ ## Examples
65
+
66
+ ```bash
67
+ pmk gpumd-group -i opted.cif -elements Li Y Cl -o model.xyz
68
+ pmk gpumd-group -if cifs/ -elements Li Y Cl -of cifs-grouped/ # whole folder
69
+ pmk gpumd-group -elements Li Y Cl # scan subfolders, tag each model.xyz in place
70
+ pmk convert -i opted.vasp -o opted.xyz # convert one structure
71
+ pmk convert -if vasp-opted -of cif-opted -oe cif # folder conversion target format
72
+ pmk convert -it train.xyz -ot train.extxyz # convert a complete trajectory
73
+ pmk supercell -i opted.vasp -o sc.vasp -max-abc 20 # cell lengths <= 20 A
74
+ pmk supercell -if vasp-opted -max-abc 20 -individual # per-structure ./<name>/<name>.<ext>
75
+ pmk supercell -if extxyz-opted -max-abc 24 -individual -temp 500 600 -md-if input-files -add-groups Li Y Cl
76
+ # GPUMD: ./<name>/model.xyz (grouped) + ./<name>/<T>/ jobs
77
+ pmk vasp-relax -i opted.vasp # relax inputs in current dir
78
+ pmk vasp-relax -if optimal_occupancy # one ./<name>/ job folder per structure
79
+ pmk vasp-static -if cifs/ -custom-setting my_incar.txt # static inputs, custom INCAR
80
+ pmk vasp-static -it traj.xyz # one ./frame_N/ job per trajectory frame
81
+ pmk msd # scans <structure>/<temp>/ -> per-job msd/ + msd_summary.txt
82
+ pmk msd -diffuse_ion Li -ion_charge 1 # choose the mobile ion for conductivity
83
+ pmk chemsys-entry -s Li La Ta Cl # MP stable entries -> Li-La-Ta-Cl-stable-entries/
84
+ pmk ehull -mp-api-key $MP_API_KEY # scans ./ for VASP jobs -> ehull.txt
85
+ pmk ehull -local Li-La-Ta-Cl-stable-entries-opted
86
+ pmk vasp-fe # scans ./ for VASP jobs -> final-energy.txt with convergence status
87
+ pmk gather-contcar -of vasp-opted # CONTCARs -> vasp-opted/<folder>.vasp
88
+ pmk gather-contcar -of vasp-opted -ehull 0.028 # only structures with E_hull < 0.028 eV/atom
89
+ pmk vasp2xyz # scans ./ for VASP output folders -> scf-converged.xyz
90
+ pmk vasp2xyz -position-only # write positions only, without energy/forces/stress
91
+ pmk submit-vasp -subscript sub_vasp -queue slurm -max-job-num 30
92
+ nohup pmk submit-vasp -subscript sub_vasp -queue slurm -max-job-num 30 > submit-vasp.log 2>&1 &
93
+ pmk substitute -i Li3YCl6.cif -se Li -sn 3 -we Na -wn 3 -on 100
94
+ pmk substitute -i Li3YCl6.cif -se Li -sn 3 -we none -on 100
95
+ pmk substitute -i Li96Ta6La11Cl72.cif -se Li1 Li2 -sn 20 67 -we none -on 100
96
+ pmk substitute -i Li96Ta6La11Cl72.cif -se Li2 -we none -ref La Ta -d 1.01 1.02
97
+ pmk substitute -i Li96Ta6La11Cl72.cif -se Li2 -we none -ref La Ta
98
+ pmk ewald -i Li3YCl6.cif
99
+ pmk ewald -if Li3YCl6-all
100
+ pmk nep-rmse # writes energy/force/stress train txt files, rmse_value.txt, and terminal plots
101
+ pmk nep-rmse -select -xyz train.xyz # interactive candidate selection; writes candidate.xyz and accurate.xyz
102
+ pmk perturb -i example.xyz -atom 0.2 -lattice 0.03 -n 100 -o example-perturb-atom-0.2-lattice-0.03.xyz
103
+ pmk pca -i descriptor.out -o pca-descriptor.txt -it train.xyz # also split descriptors by Config_type
104
+ pmk pca -i descriptor.out -o pca-descriptor.txt -fps 0.01 -it train.xyz
105
+ pmk umap -i descriptor.out -o umap-descriptor.txt -fps 0.01 -it train.xyz
106
+ pmk gpumd-thermo # current job, or recursively scan job folders
107
+ pmk gpumd-thermo -t thermo.out -in run.in
108
+ pmk gpumd-trjcat # stable ./<job>/traj.xyz files -> train.xyz
109
+ pmk gpumd-trjcat -i traj.xyz -o train.xyz
110
+ pmk trj-extract -it traj.xyz -in run.in -b 100 -e 200 -o traj-100ps-200ps.xyz
111
+
112
+ pmk rmsd a.cif b.cif # RMSD of two files -> rmsd.txt
113
+ pmk rmsd vasp-opted/ # all pairs in a folder -> rmsd.txt
114
+ pmk symmetrize -i opted.cif -symprec 0.01 -add_oxidation yes -o opted-symm.cif
115
+ pmk symmetrize -if my_cifs/ -symprec 0.01 -add_oxidation no -of my_cifs-symm
116
+ ```
117
+
118
+ VASP input commands (`vasp-relax`, `vasp-static`) always produce **individual**
119
+ jobs (one structure per folder): `-i` writes into the current dir (or `-o`),
120
+ `-if` creates one `./<name>/` folder per structure, and `-it` creates one
121
+ `./frame_N/` folder per trajectory frame - all directly in the current path.
122
+
123
+ They start from sensible default INCAR settings; override them by passing a
124
+ settings file with `-custom-setting FILE`. The file may be a Python-dict block
125
+ or `KEY = VALUE` lines (a `None`/blank value clears a tag):
126
+
127
+ ```text
128
+ custom_settings = {
129
+ "ENCUT": "600.0",
130
+ "ISIF": "3",
131
+ "MAGMOM": None
132
+ }
133
+ ```
134
+
135
+ `vasp-static -it traj.xyz` (also available on `vasp-relax`) reads a
136
+ multi-structure trajectory and writes one job sub-folder per frame
137
+ (`frame_1/`, `frame_2/`, ..., prefix configurable via `-frame-prefix`). Each
138
+ folder also keeps a `frame_N.xyz`, so `Config_type` survives for a later
139
+ `vasp2xyz`.
140
+
141
+ `nep-rmse -select`, PCA FPS, and UMAP FPS copy selected extended-XYZ frame blocks directly from the input trajectory. No parser rewrites energy, stress, forces, positions, precision, or extra metadata.
142
+
143
+ 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.
144
+
145
+ For folder conversion, `convert -if INPUT -of OUTPUT` writes `.xyz` by default. Set another target extension with `-oe`, for example `-oe cif` or `-oe vasp`. Single-file and trajectory modes infer the target format from `-o` and `-ot`.
146
+
147
+ `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. Its warning is a numerical reminder based on sustained drift or a level shift in the last `run` segment (or the last 80% when `run.in` has no `run` record); inspect the plot before deciding whether a trajectory is equilibrated.
148
+
149
+ `gpumd-trjcat` recursively scans job subfolders for `traj.xyz`, `thermo.out`, and `run.in`. Jobs with missing, unreadable, or abnormal thermo data are skipped. Stable trajectories are merged in deterministic folder order. When `<job>/model.xyz` contains `Config_type`, that value is applied to every frame from the job while all other extended-XYZ bytes remain unchanged.
150
+
151
+ Each command's full flag list is in `pmk <command> -help`.
152
+
153
+ For long VASP batch submission, run `submit-vasp` with `nohup` and `&` if you
154
+ want it to keep sleeping, checking the queue, and submitting new jobs after you
155
+ exit the terminal:
156
+
157
+ ```bash
158
+ nohup pmk submit-vasp -subscript sub_vasp -queue slurm -max-job-num 30 > submit-vasp.log 2>&1 &
159
+ ```
160
+
161
+ The command itself controls the loop: it submits until the active queue reaches
162
+ `-max-job-num`, sleeps when the queue is full, checks again, and continues until
163
+ all needed jobs are submitted. `nohup ... &` is what makes that loop continue in
164
+ the background after logout.
165
+
166
+ `substitute -ref` removes selected sites near reference sites and writes one
167
+ `<input-stem>_substitute.cif` in the current path. If `-d` is omitted, each
168
+ cutoff is `0.7 * (selected covalent radius + reference covalent radius)` using
169
+ the covalent radii from Cordero et al., Dalton Trans., 2008, 2832-2838.
170
+
171
+ VASP-output readers (`vasp2xyz`, `ehull`, `vasp-fe`, and other future VASP-output
172
+ commands) use the global priority `vaspout.h5 > vasprun.xml > OUTCAR`.
173
+
174
+ `ehull` auto-detects every sub-folder of the current path that contains a
175
+ supported VASP output, groups them by chemical system (elements ordered by
176
+ electronegativity, e.g. `Li-Y-Cl`), and builds/reuses one `mp_cache_<system>.json`
177
+ per system - so a pure Li-Y-Cl batch yields a single `mp_cache_Li-Y-Cl.json`, while
178
+ a mixed Li-Y-Cl + La-O batch yields both `mp_cache_Li-Y-Cl.json` and
179
+ `mp_cache_La-O.json`. (Formation energy is reported alongside E_hull in
180
+ `ehull.txt`.)
181
+
182
+ ## Layout
183
+
184
+ ```
185
+ pymdkit/
186
+ |-- pyproject.toml # package metadata + the `pmk` entry point
187
+ |-- README.md
188
+ `-- src/pymdkit/
189
+ |-- pymdkit_main.py # dispatcher: discovers and runs commands
190
+ `-- commands/ # one module per command
191
+ |-- _fileio.py # shared -i/-o/-if/-of helper (not a command)
192
+ |-- _gpumd.py # shared GPUMD run.in and thermo analysis
193
+ |-- _geometry.py # shared minimum-image geometry helper
194
+ |-- _vaspset.py # shared VASP input-set helper (not a command)
195
+ |-- gpumd_group.py
196
+ |-- gpumd_thermo.py
197
+ |-- gpumd_trjcat.py
198
+ |-- compute_ehull.py
199
+ |-- compute_rmsd.py
200
+ |-- ewald.py
201
+ |-- vasp_fe.py
202
+ |-- perturb.py
203
+ |-- nep_rmse.py
204
+ |-- chemsys_entry.py
205
+ |-- submit_vasp.py
206
+ |-- convert.py
207
+ |-- substitute.py
208
+ |-- supercell.py
209
+ |-- vasp2xyz.py
210
+ |-- vasp_relax.py
211
+ |-- vasp_static.py
212
+ |-- ...
213
+ `-- symmetrize.py
214
+ ```
215
+
216
+ Modules whose name starts with `_` are shared helpers and are skipped by the
217
+ dispatcher, so they never appear as commands.
218
+
219
+ ## Adding a new tool later
220
+
221
+ Drop a module in `src/pymdkit/commands/` that defines four things:
222
+
223
+ ```python
224
+ COMMAND = "my-tool" # the subcommand name you'll type
225
+ HELP = "One-line description."
226
+
227
+ def add_arguments(parser): # register flags
228
+ parser.add_argument("-input", required=True)
229
+
230
+ def run(args): # do the work; return an exit code (0 = ok)
231
+ ...
232
+ return 0
233
+
234
+ if __name__ == "__main__": # keeps the script runnable on its own
235
+ import argparse
236
+ _p = argparse.ArgumentParser(description=__doc__)
237
+ add_arguments(_p)
238
+ raise SystemExit(run(_p.parse_args()))
239
+ ```
240
+
241
+ It will appear in `pmk -help` automatically - no central registration needed.
242
+ Put heavy imports (pymatgen, ase, ...) inside `run()` where practical; the dispatcher
243
+ reads each command's name and help without importing it, so `pmk -help` stays
244
+ fast and a missing optional dependency only affects the one command that needs it.
245
+
246
+ ## Running a script standalone
247
+
248
+ Every command module still works directly, which is handy for debugging:
249
+
250
+ ```bash
251
+ python src/pymdkit/commands/supercell.py -i in.cif -max-abc 20 -o sc.vasp
252
+ ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pymdkit"
7
- version = "1.3.0"
7
+ version = "1.4.1"
8
8
  description = "A unified command-line toolkit for atomistic / MD structure workflows."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -20,9 +20,9 @@ classifiers = [
20
20
  "Topic :: Scientific/Engineering :: Physics",
21
21
  ]
22
22
 
23
- # All dependencies are installed up front so a plain `pip install pymdkit`
23
+ # All dependencies are installed up front so a plain ppip install pymdkitp
24
24
  # gives a fully working CLI. Heavy libraries are still imported lazily inside
25
- # individual commands, so the dispatcher and `pymdkit -help` stay fast.
25
+ # individual commands, so the dispatcher and pmk -help stay fast.
26
26
  dependencies = [
27
27
  "numpy",
28
28
  "ase",
@@ -31,11 +31,13 @@ dependencies = [
31
31
  "py4vasp-core",
32
32
  "pyxtal",
33
33
  "dpdata",
34
+ "matplotlib",
35
+ "umap-learn",
34
36
  ]
35
37
 
36
38
  [project.scripts]
37
- # This is what turns the package into the `pymdkit` executable on your PATH.
38
- pymdkit = "pymdkit.pymdkit_main:main"
39
+ # The distribution remains pymdkit; the concise public executable is ppmkp.
40
+ pmk = "pymdkit.pymdkit_main:main"
39
41
 
40
42
  [tool.setuptools.packages.find]
41
43
  where = ["src"]
@@ -1,3 +1,3 @@
1
1
  """pymdkit -- a unified CLI for atomistic / MD structure workflows."""
2
2
 
3
- __version__ = "1.3.0"
3
+ __version__ = "1.4.1"
@@ -0,0 +1,85 @@
1
+ """Exact, byte-preserving helpers for extended-XYZ trajectories."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from pathlib import Path
7
+
8
+ _CONFIG_TYPE_RE = re.compile(r'(?<!\S)Config_type=(?:"([^"]*)"|(\S+))')
9
+
10
+
11
+ def read_blocks(path):
12
+ """Return ``(blocks, atom_counts)`` without parsing or reformatting data."""
13
+ path = Path(path)
14
+ lines = path.read_bytes().splitlines(keepends=True)
15
+ blocks = []
16
+ atom_counts = []
17
+ index = 0
18
+ while index < len(lines):
19
+ if not lines[index].strip():
20
+ index += 1
21
+ continue
22
+ try:
23
+ count = int(lines[index].strip())
24
+ except ValueError as exc:
25
+ raise ValueError(
26
+ f"{path}: expected an atom count at line {index + 1}"
27
+ ) from exc
28
+ stop = index + count + 2
29
+ if stop > len(lines):
30
+ raise ValueError(f"{path}: incomplete frame beginning at line {index + 1}")
31
+ blocks.append(b"".join(lines[index:stop]))
32
+ atom_counts.append(count)
33
+ index = stop
34
+ return blocks, atom_counts
35
+
36
+
37
+ def write_blocks(path, blocks, indices):
38
+ """Write selected frames exactly as they appeared in the source file."""
39
+ Path(path).write_bytes(b"".join(blocks[int(index)] for index in indices))
40
+
41
+
42
+ def _comment_line(block):
43
+ lines = block.splitlines(keepends=True)
44
+ if len(lines) < 2:
45
+ raise ValueError("incomplete XYZ frame: missing comment line")
46
+ raw = lines[1]
47
+ if raw.endswith(b"\r\n"):
48
+ body, newline = raw[:-2], b"\r\n"
49
+ elif raw.endswith(b"\n") or raw.endswith(b"\r"):
50
+ body, newline = raw[:-1], raw[-1:]
51
+ else:
52
+ body, newline = raw, b""
53
+ return lines, body.decode("utf-8"), newline
54
+
55
+
56
+ def config_type_from_block(block):
57
+ """Return Config_type from one raw XYZ frame, if present."""
58
+ _, comment, _ = _comment_line(block)
59
+ match = _CONFIG_TYPE_RE.search(comment)
60
+ if not match:
61
+ return None
62
+ return match.group(1) if match.group(1) is not None else match.group(2)
63
+
64
+
65
+ def read_config_type(path):
66
+ """Read Config_type from the first frame of an XYZ file, if present."""
67
+ blocks, _ = read_blocks(path)
68
+ return config_type_from_block(blocks[0]) if blocks else None
69
+
70
+
71
+ def set_config_type(block, value):
72
+ """Set Config_type while preserving all other bytes in an XYZ frame."""
73
+ lines, comment, newline = _comment_line(block)
74
+ value = str(value)
75
+ if re.fullmatch(r'[^\s"=]+', value):
76
+ encoded_value = value
77
+ else:
78
+ encoded_value = '"' + value.replace('\\', '\\\\').replace('"', '\\"') + '"'
79
+ assignment = f"Config_type={encoded_value}"
80
+ if _CONFIG_TYPE_RE.search(comment):
81
+ comment = _CONFIG_TYPE_RE.sub(assignment, comment, count=1)
82
+ else:
83
+ comment += (" " if comment else "") + assignment
84
+ lines[1] = comment.encode("utf-8") + newline
85
+ return b"".join(lines)