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