pymdkit 1.6.0__tar.gz → 1.6.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.6.0 → pymdkit-1.6.2}/PKG-INFO +63 -34
- {pymdkit-1.6.0 → pymdkit-1.6.2}/README.md +78 -49
- {pymdkit-1.6.0 → pymdkit-1.6.2}/pyproject.toml +1 -1
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/__init__.py +1 -1
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/_cifio.py +17 -4
- pymdkit-1.6.2/src/pymdkit/commands/_supercell.py +136 -0
- pymdkit-1.6.0/src/pymdkit/commands/symmetrize.py → pymdkit-1.6.2/src/pymdkit/commands/_symmetry.py +18 -144
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/_vaspset.py +70 -37
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/compute_rmsd.py +3 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/convert.py +327 -87
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/gpumd_group.py +2 -2
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/realloc_occ.py +6 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/substitute.py +5 -1
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/supercell.py +98 -57
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/vasp_relax.py +1 -1
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/vasp_static.py +1 -1
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit.egg-info/PKG-INFO +63 -34
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit.egg-info/SOURCES.txt +2 -1
- {pymdkit-1.6.0 → pymdkit-1.6.2}/tests/test_convert.py +164 -6
- {pymdkit-1.6.0 → pymdkit-1.6.2}/tests/test_realloc_occ.py +4 -0
- pymdkit-1.6.2/tests/test_vaspset.py +193 -0
- pymdkit-1.6.0/tests/test_vaspset.py +0 -74
- {pymdkit-1.6.0 → pymdkit-1.6.2}/LICENSE +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/setup.cfg +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/__init__.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/_extxyz.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/_fileio.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/_geometry.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/_gpumd.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/_lattice.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/_oxidation.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/_precision.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/_reduction.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/_vasp_output.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/add_config_type.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/chemsys_entry.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/compute_ehull.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/ewald.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/gather_fs.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/gpumd_msd.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/gpumd_relax.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/gpumd_thermo.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/gpumd_trjcat.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/nep_rmse.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/pca.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/perturb.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/submit_vasp.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/trj_extract.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/trj_sparse.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/umap.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/vasp2xyz.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/commands/vasp_fe.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit/pymdkit_main.py +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit.egg-info/dependency_links.txt +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit.egg-info/entry_points.txt +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit.egg-info/requires.txt +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/src/pymdkit.egg-info/top_level.txt +0 -0
- {pymdkit-1.6.0 → pymdkit-1.6.2}/tests/test_lattice_symmetry.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pymdkit
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.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
|
|
@@ -91,11 +91,11 @@ directory for job sub-folders automatically.
|
|
|
91
91
|
| `submit-vasp` | Submit/resubmit VASP job folders while limiting active queue jobs |
|
|
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
|
-
| `convert` | Convert structures,
|
|
94
|
+
| `convert` | Convert structures/trajectories/folders, refine CIF symmetry, or extract CSV structures |
|
|
95
95
|
| `realloc-occ` | Generate charge-balanced CIF occupancy allocations for a target formula |
|
|
96
96
|
| `substitute` | Randomly substitute or remove selected atoms/sites from a structure |
|
|
97
|
-
| `supercell` | Build a supercell
|
|
98
|
-
|
|
97
|
+
| `supercell` | Build a supercell by matrix, maximum length, or nearest target length; optional GPUMD setup |
|
|
98
|
+
|
|
99
99
|
| `vasp-relax` | Write VASP relaxation inputs for a structure (or folder); INCAR tags overridable |
|
|
100
100
|
| `vasp-static` | Write VASP static / single-point inputs for a structure (or folder) |
|
|
101
101
|
|
|
@@ -111,16 +111,22 @@ pmk convert -i NaCl.vasp -o NaCl.cif # CIF defaults t
|
|
|
111
111
|
pmk convert -i NaCl.vasp -o NaCl-symm.cif -symm # detect/import symmetry
|
|
112
112
|
pmk convert -if vasp-opted -of cif-opted -oe cif # folder CIFs default to P1
|
|
113
113
|
pmk convert -it train.xyz -ot train.extxyz # convert a complete trajectory
|
|
114
|
-
pmk convert -
|
|
115
|
-
pmk
|
|
114
|
+
pmk convert -i model.cif -o matrix.cif -supercell-matrix 1x1x2
|
|
115
|
+
pmk convert -i model.cif -o capped.cif -supercell-max-abc 40
|
|
116
|
+
pmk convert -i model.cif -o close.cif -supercell-close-abc 20
|
|
117
|
+
pmk convert -if Li4YCl6-disorder -of Li4YCl6-order -d2o -supercell-matrix 1x1x1
|
|
118
|
+
pmk supercell -i opted.vasp -o matrix.vasp -matrix 1x1x2
|
|
119
|
+
pmk supercell -i opted.xyz -o grouped.xyz -matrix 2x2x2 -gpumd-group Li Y Cl
|
|
120
|
+
pmk supercell -i opted.vasp -o capped.vasp -max-abc 40
|
|
121
|
+
pmk supercell -i opted.vasp -o close.vasp -close-abc 20 # nearest target: e.g. 21,20,20 A
|
|
116
122
|
pmk supercell -if vasp-opted -max-abc 20 -individual # per-structure ./<name>/<name>.<ext>
|
|
117
|
-
pmk supercell -if extxyz-opted -max-abc 24 -individual -temp 500 600 -md-if input-files -group Li Y Cl
|
|
123
|
+
pmk supercell -if extxyz-opted -max-abc 24 -individual -temp 500 600 -md-if input-files -gpumd-group Li Y Cl
|
|
118
124
|
# GPUMD: ./<name>/model.xyz (grouped) + ./<name>/<T>/ jobs
|
|
119
|
-
pmk vasp-relax -i opted.vasp -max-abc 20
|
|
125
|
+
pmk vasp-relax -i opted.vasp -supercell-max-abc 20 # expand cell, then write relax inputs
|
|
120
126
|
pmk vasp-relax -if optimal_occupancy # one ./<name>/ job folder per structure
|
|
121
127
|
pmk gpumd-relax -if example -nep nep89_20250409.txt # model.xyz + run.in + NEP per job folder
|
|
122
128
|
pmk gpumd-relax -it train.xyz -nep nep.txt -in run.in # one ./frame_N/ GPUMD job per frame
|
|
123
|
-
pmk vasp-static -if cifs/ -
|
|
129
|
+
pmk vasp-static -if cifs/ -supercell-close-abc 20 -custom-setting my_incar.txt
|
|
124
130
|
pmk vasp-static -it traj.xyz # one ./frame_N/ job per trajectory frame
|
|
125
131
|
pmk gpumd-msd # recursively scans GPUMD jobs -> per-job msd/ + sigma-sdc-summary.txt
|
|
126
132
|
pmk gpumd-msd -diffuse_ion Li -ion_charge 1 # choose the mobile ion and signed valence
|
|
@@ -164,9 +170,9 @@ pmk rmsd -i structure-1.vasp structure-2.vasp # one pair -> rmsd.txt
|
|
|
164
170
|
pmk rmsd -if vasp-opted # every pair -> rmsd.txt
|
|
165
171
|
pmk rmsd -if example -rm-duplicate -of unique-example # RMSD <= 0.1: keep lowest energy
|
|
166
172
|
pmk rmsd -if example -rm-duplicate -of unique-example -gpu
|
|
167
|
-
pmk
|
|
168
|
-
pmk
|
|
169
|
-
pmk
|
|
173
|
+
pmk convert -i opted.cif -o opted-symm.cif -symm -add-oxidation yes
|
|
174
|
+
pmk convert -if my_cifs/ -of my_cifs-symm -oe cif -symm -symprec 0.1 -add-oxidation no
|
|
175
|
+
pmk convert -csv output_Li2YCl3_struct.csv -of Li2YCl3 -symm
|
|
170
176
|
```
|
|
171
177
|
|
|
172
178
|
VASP input commands (`vasp-relax`, `vasp-static`) always produce **individual**
|
|
@@ -174,9 +180,11 @@ jobs (one structure per folder): `-i` writes into the current dir (or `-o`),
|
|
|
174
180
|
`-if` creates one `./<name>/` folder per structure, and `-it` creates one
|
|
175
181
|
`./frame_N/` folder per trajectory frame - all directly in the current path.
|
|
176
182
|
|
|
177
|
-
Both commands accept
|
|
178
|
-
|
|
179
|
-
|
|
183
|
+
Both commands accept exactly one of `-supercell-matrix`,
|
|
184
|
+
`-supercell-max-abc`, or `-supercell-close-abc` for single files, folders, and
|
|
185
|
+
trajectories. The matrix mode accepts `1x1x2`, three diagonal factors, or nine
|
|
186
|
+
integer entries. Maximum mode uses `max(1, floor(L / cell_length))`; close mode
|
|
187
|
+
uses the positive integer repeat nearest `L / cell_length`.
|
|
180
188
|
|
|
181
189
|
They start from sensible default INCAR settings; override them by passing a
|
|
182
190
|
settings file with `-custom-setting FILE`. The file may be a Python-dict block
|
|
@@ -255,8 +263,9 @@ and `0.875`. Generated CIF occupancy fields use two decimal places.
|
|
|
255
263
|
`-max-supercell AxBxC` searches bounded diagonal supercells, smallest volume
|
|
256
264
|
first, until every multiplicity times occupancy times supercell determinant is
|
|
257
265
|
an integer. Equal-volume choices favor the most isotropic resulting cell.
|
|
258
|
-
All selections, site atom counts, oxidation states, charges,
|
|
259
|
-
recorded in
|
|
266
|
+
All selections, site atom counts, oxidation states, charges, failures, the
|
|
267
|
+
allowed occupancy grid, and the final summary are recorded in
|
|
268
|
+
`OUTPUT/realloc-occ.txt` for both single-file and folder modes.
|
|
260
269
|
|
|
261
270
|
`gather-fs` recursively scans all job folders below the current directory. For
|
|
262
271
|
VASP it follows the global `vaspout.h5 > vasprun.xml > OUTCAR` priority and
|
|
@@ -287,13 +296,13 @@ For the supplied CUDA 12.6 Slurm script, submit the GPU workflow with:
|
|
|
287
296
|
sbatch sub_pmk
|
|
288
297
|
```
|
|
289
298
|
|
|
290
|
-
`
|
|
299
|
+
`convert -symm` uses pymatgen's spglib-backed `SpacegroupAnalyzer` for authoritative
|
|
291
300
|
space-group detection and conventional-cell refinement. Its default Cartesian
|
|
292
301
|
symmetry tolerance is `0.1 Angstrom` (`-symprec`), with a `5 degree` angle
|
|
293
302
|
tolerance (`-angle-tolerance`). CIF occupancies are retained by the pymatgen
|
|
294
303
|
structure and written directly rather than reconstructed after symmetry finding.
|
|
295
|
-
CSV mode reads serialized pymatgen `Structure` dictionaries from a required `cif`
|
|
296
|
-
column. `-csv INPUT.csv -of NAME` writes `INDEX-NAME-SPACEGROUP.cif`, for
|
|
304
|
+
With `-symm`, CSV mode reads serialized pymatgen `Structure` dictionaries from a required `cif`
|
|
305
|
+
column. `-csv INPUT.csv -of NAME -symm` writes `INDEX-NAME-SPACEGROUP.cif`, for
|
|
297
306
|
example `1-Li2YCl3-66.cif`. Every run also writes `symmetry.txt` with one row
|
|
298
307
|
per successfully generated CIF: filename, crystal system, space-group symbol,
|
|
299
308
|
and space-group number. Crystal-system and space-group summaries are sorted
|
|
@@ -309,7 +318,7 @@ the `_symmetry_equiv_pos_as_xyz` loop is sourced from pymatgen's `SpaceGroup`
|
|
|
309
318
|
database instead of serializing noisy numerical translations returned by
|
|
310
319
|
structure-specific symmetry detection.
|
|
311
320
|
|
|
312
|
-
Every CIF exported by `
|
|
321
|
+
Every CIF exported by `convert`, `substitute`, `chemsys-entry`,
|
|
313
322
|
`supercell`, or `gpumd-group` is written through the same CIF helper. `convert`
|
|
314
323
|
writes P1 by default and performs symmetry detection only when `-symm` is
|
|
315
324
|
present; the other commands retain their command-specific symmetry behavior.
|
|
@@ -322,6 +331,14 @@ International Tables setting from pymatgen/spglib and correspond to the
|
|
|
322
331
|
[Bilbao Crystallographic Server WYCKPOS tables](https://cryst.ehu.es/cryst/get_wp.html);
|
|
323
332
|
the provenance URL is also recorded inside each generated CIF.
|
|
324
333
|
|
|
334
|
+
For structural conversion, supercell creation, and GPUMD grouping, PMK reads
|
|
335
|
+
CIF input with pymatgen rather than ASE. ASE interprets
|
|
336
|
+
`_space_group_crystal_system` as a setting hint only for rhombohedral groups
|
|
337
|
+
and emits a potentially misleading warning for other crystal systems. Using
|
|
338
|
+
pymatgen avoids that ambiguity while PMK continues to use ASE for non-CIF
|
|
339
|
+
formats. A partially occupied CIF must first be ordered with `convert -d2o`
|
|
340
|
+
before it can be represented as an explicit atom list.
|
|
341
|
+
|
|
325
342
|
CIF `_atom_type_symbol` and `_atom_site_label` values use per-element
|
|
326
343
|
inequivalent-site identifiers such as `Li1`, `Y1`, `Cl1`, and `Cl2`.
|
|
327
344
|
The final atom-site loop starts with `_atom_site_label`, followed by
|
|
@@ -357,18 +374,28 @@ Single-file and trajectory modes infer the target format from `-o` and `-ot`.
|
|
|
357
374
|
Because Wyckoff metadata describes one crystal structure, each `.cif` output
|
|
358
375
|
file accepts exactly one frame.
|
|
359
376
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
`
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
377
|
+
For ordinary conversion, the three supercell options are mutually exclusive and
|
|
378
|
+
are resolved separately for every input structure. For example, a cell with
|
|
379
|
+
`a=3 A`, `b=4 A`, and `c=5 A` plus `-supercell-close-abc 20` uses `7x5x4`,
|
|
380
|
+
giving approximately `21 A`, `20 A`, and `20 A`. The numeric value is the target:
|
|
381
|
+
`-supercell-close-abc 40` targets approximately `40 A`.
|
|
382
|
+
|
|
383
|
+
`convert -d2o` orders partially occupied CIF structures.
|
|
384
|
+
`-supercell-matrix` defaults internally to `1x1x1` in this mode; three
|
|
385
|
+
diagonal factors or nine integer matrix entries are accepted. Because Ewald
|
|
386
|
+
candidates must be compared using one fixed cell, `-supercell-max-abc` and
|
|
387
|
+
`-supercell-close-abc` are not accepted with `-d2o`. Folder ordering scans
|
|
388
|
+
recursively, groups `BASE-dN.cif` files by `BASE`, and writes only the
|
|
389
|
+
lowest-total-Ewald candidate into the flat output folder as `BASE-oN.cif`,
|
|
390
|
+
where `N` remains tied to its source `-dN` file. Pymatgen
|
|
391
|
+
`EnumerateStructureTransformation` is used when enumlib is available; the
|
|
392
|
+
expected missing-enumlib runtime error quietly switches to
|
|
367
393
|
`OrderDisorderedStructureTransformation`. Every output CIF records total Ewald
|
|
368
394
|
energy on line two as `# E_e = VALUE eV`, and `d2o.txt` records all candidates,
|
|
369
395
|
methods, energies, failures, and selections. Add `-symm` to import symmetry into
|
|
370
|
-
the ordered CIF; otherwise it is P1. Without `-d2o`,
|
|
371
|
-
|
|
396
|
+
the ordered CIF; otherwise it is P1. Without `-d2o`, the
|
|
397
|
+
`-supercell-matrix`, `-supercell-max-abc`, and `-supercell-close-abc` options
|
|
398
|
+
build an ordinary structural supercell and never enable ordering automatically.
|
|
372
399
|
|
|
373
400
|
After `supercell -temp` creates all requested jobs, it checks generated folders
|
|
374
401
|
that contain `run.in`, `model.xyz`, and `nep.txt`. Existing `ensemble npt_scr`
|
|
@@ -376,8 +403,10 @@ lines retain their temperature parameters but are rewritten with three
|
|
|
376
403
|
pressure/modulus components for 90-degree orthorhombic cells and all six
|
|
377
404
|
components for non-orthorhombic cells, following the
|
|
378
405
|
[official GPUMD ensemble syntax](https://gpumd.org/gpumd/input_parameters/ensemble_standard.html).
|
|
379
|
-
Other ensemble types are not modified. Use `-group Li Y Cl` to
|
|
380
|
-
|
|
406
|
+
Other ensemble types are not modified. Use `-gpumd-group Li Y Cl` to embed the
|
|
407
|
+
standalone `gpumd-group` tagging behavior in generated `model.xyz` files. The
|
|
408
|
+
same option also tags ordinary `.xyz` or `.extxyz` supercell output; formats
|
|
409
|
+
that cannot preserve GPUMD group arrays are rejected before output is written.
|
|
381
410
|
`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.
|
|
382
411
|
|
|
383
412
|
GPUMD box matrices are interpreted row by row by
|
|
@@ -460,7 +489,7 @@ pymdkit/
|
|
|
460
489
|
|-- vasp_relax.py
|
|
461
490
|
|-- vasp_static.py
|
|
462
491
|
|-- ...
|
|
463
|
-
`--
|
|
492
|
+
`-- _symmetry.py # private symmetry conversion/report helper
|
|
464
493
|
```
|
|
465
494
|
|
|
466
495
|
Modules whose name starts with `_` are shared helpers and are skipped by the
|
|
@@ -498,5 +527,5 @@ fast and a missing optional dependency only affects the one command that needs i
|
|
|
498
527
|
Every command module still works directly, which is handy for debugging:
|
|
499
528
|
|
|
500
529
|
```bash
|
|
501
|
-
python src/pymdkit/commands/supercell.py -i in.cif -
|
|
530
|
+
python src/pymdkit/commands/supercell.py -i in.cif -close-abc 20 -o sc.vasp
|
|
502
531
|
```
|
|
@@ -64,11 +64,11 @@ directory for job sub-folders automatically.
|
|
|
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,
|
|
67
|
+
| `convert` | Convert structures/trajectories/folders, refine CIF symmetry, or extract CSV structures |
|
|
68
68
|
| `realloc-occ` | Generate charge-balanced CIF occupancy allocations for a target formula |
|
|
69
69
|
| `substitute` | Randomly substitute or remove selected atoms/sites from a structure |
|
|
70
|
-
| `supercell` | Build a supercell
|
|
71
|
-
|
|
70
|
+
| `supercell` | Build a supercell by matrix, maximum length, or nearest target length; optional GPUMD setup |
|
|
71
|
+
|
|
72
72
|
| `vasp-relax` | Write VASP relaxation inputs for a structure (or folder); INCAR tags overridable |
|
|
73
73
|
| `vasp-static` | Write VASP static / single-point inputs for a structure (or folder) |
|
|
74
74
|
|
|
@@ -83,17 +83,23 @@ pmk gpumd-group -elements Li Y Cl # scan subfolders
|
|
|
83
83
|
pmk convert -i NaCl.vasp -o NaCl.cif # CIF defaults to P1
|
|
84
84
|
pmk convert -i NaCl.vasp -o NaCl-symm.cif -symm # detect/import symmetry
|
|
85
85
|
pmk convert -if vasp-opted -of cif-opted -oe cif # folder CIFs default to P1
|
|
86
|
-
pmk convert -it train.xyz -ot train.extxyz # convert a complete trajectory
|
|
87
|
-
pmk convert -
|
|
88
|
-
pmk
|
|
86
|
+
pmk convert -it train.xyz -ot train.extxyz # convert a complete trajectory
|
|
87
|
+
pmk convert -i model.cif -o matrix.cif -supercell-matrix 1x1x2
|
|
88
|
+
pmk convert -i model.cif -o capped.cif -supercell-max-abc 40
|
|
89
|
+
pmk convert -i model.cif -o close.cif -supercell-close-abc 20
|
|
90
|
+
pmk convert -if Li4YCl6-disorder -of Li4YCl6-order -d2o -supercell-matrix 1x1x1
|
|
91
|
+
pmk supercell -i opted.vasp -o matrix.vasp -matrix 1x1x2
|
|
92
|
+
pmk supercell -i opted.xyz -o grouped.xyz -matrix 2x2x2 -gpumd-group Li Y Cl
|
|
93
|
+
pmk supercell -i opted.vasp -o capped.vasp -max-abc 40
|
|
94
|
+
pmk supercell -i opted.vasp -o close.vasp -close-abc 20 # nearest target: e.g. 21,20,20 A
|
|
89
95
|
pmk supercell -if vasp-opted -max-abc 20 -individual # per-structure ./<name>/<name>.<ext>
|
|
90
|
-
pmk supercell -if extxyz-opted -max-abc 24 -individual -temp 500 600 -md-if input-files -group Li Y Cl
|
|
96
|
+
pmk supercell -if extxyz-opted -max-abc 24 -individual -temp 500 600 -md-if input-files -gpumd-group Li Y Cl
|
|
91
97
|
# GPUMD: ./<name>/model.xyz (grouped) + ./<name>/<T>/ jobs
|
|
92
|
-
pmk vasp-relax -i opted.vasp -max-abc 20
|
|
98
|
+
pmk vasp-relax -i opted.vasp -supercell-max-abc 20 # expand cell, then write relax inputs
|
|
93
99
|
pmk vasp-relax -if optimal_occupancy # one ./<name>/ job folder per structure
|
|
94
100
|
pmk gpumd-relax -if example -nep nep89_20250409.txt # model.xyz + run.in + NEP per job folder
|
|
95
101
|
pmk gpumd-relax -it train.xyz -nep nep.txt -in run.in # one ./frame_N/ GPUMD job per frame
|
|
96
|
-
pmk vasp-static -if cifs/ -
|
|
102
|
+
pmk vasp-static -if cifs/ -supercell-close-abc 20 -custom-setting my_incar.txt
|
|
97
103
|
pmk vasp-static -it traj.xyz # one ./frame_N/ job per trajectory frame
|
|
98
104
|
pmk gpumd-msd # recursively scans GPUMD jobs -> per-job msd/ + sigma-sdc-summary.txt
|
|
99
105
|
pmk gpumd-msd -diffuse_ion Li -ion_charge 1 # choose the mobile ion and signed valence
|
|
@@ -137,9 +143,9 @@ pmk rmsd -i structure-1.vasp structure-2.vasp # one pair -> rmsd.txt
|
|
|
137
143
|
pmk rmsd -if vasp-opted # every pair -> rmsd.txt
|
|
138
144
|
pmk rmsd -if example -rm-duplicate -of unique-example # RMSD <= 0.1: keep lowest energy
|
|
139
145
|
pmk rmsd -if example -rm-duplicate -of unique-example -gpu
|
|
140
|
-
pmk
|
|
141
|
-
pmk
|
|
142
|
-
pmk
|
|
146
|
+
pmk convert -i opted.cif -o opted-symm.cif -symm -add-oxidation yes
|
|
147
|
+
pmk convert -if my_cifs/ -of my_cifs-symm -oe cif -symm -symprec 0.1 -add-oxidation no
|
|
148
|
+
pmk convert -csv output_Li2YCl3_struct.csv -of Li2YCl3 -symm
|
|
143
149
|
```
|
|
144
150
|
|
|
145
151
|
VASP input commands (`vasp-relax`, `vasp-static`) always produce **individual**
|
|
@@ -147,9 +153,11 @@ jobs (one structure per folder): `-i` writes into the current dir (or `-o`),
|
|
|
147
153
|
`-if` creates one `./<name>/` folder per structure, and `-it` creates one
|
|
148
154
|
`./frame_N/` folder per trajectory frame - all directly in the current path.
|
|
149
155
|
|
|
150
|
-
Both commands accept
|
|
151
|
-
|
|
152
|
-
|
|
156
|
+
Both commands accept exactly one of `-supercell-matrix`,
|
|
157
|
+
`-supercell-max-abc`, or `-supercell-close-abc` for single files, folders, and
|
|
158
|
+
trajectories. The matrix mode accepts `1x1x2`, three diagonal factors, or nine
|
|
159
|
+
integer entries. Maximum mode uses `max(1, floor(L / cell_length))`; close mode
|
|
160
|
+
uses the positive integer repeat nearest `L / cell_length`.
|
|
153
161
|
|
|
154
162
|
They start from sensible default INCAR settings; override them by passing a
|
|
155
163
|
settings file with `-custom-setting FILE`. The file may be a Python-dict block
|
|
@@ -228,9 +236,10 @@ and `0.875`. Generated CIF occupancy fields use two decimal places.
|
|
|
228
236
|
`-max-supercell AxBxC` searches bounded diagonal supercells, smallest volume
|
|
229
237
|
first, until every multiplicity times occupancy times supercell determinant is
|
|
230
238
|
an integer. Equal-volume choices favor the most isotropic resulting cell.
|
|
231
|
-
All selections, site atom counts, oxidation states, charges,
|
|
232
|
-
recorded in
|
|
233
|
-
|
|
239
|
+
All selections, site atom counts, oxidation states, charges, failures, the
|
|
240
|
+
allowed occupancy grid, and the final summary are recorded in
|
|
241
|
+
`OUTPUT/realloc-occ.txt` for both single-file and folder modes.
|
|
242
|
+
|
|
234
243
|
`gather-fs` recursively scans all job folders below the current directory. For
|
|
235
244
|
VASP it follows the global `vaspout.h5 > vasprun.xml > OUTCAR` priority and
|
|
236
245
|
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.
|
|
@@ -260,13 +269,13 @@ For the supplied CUDA 12.6 Slurm script, submit the GPU workflow with:
|
|
|
260
269
|
sbatch sub_pmk
|
|
261
270
|
```
|
|
262
271
|
|
|
263
|
-
`
|
|
272
|
+
`convert -symm` uses pymatgen's spglib-backed `SpacegroupAnalyzer` for authoritative
|
|
264
273
|
space-group detection and conventional-cell refinement. Its default Cartesian
|
|
265
274
|
symmetry tolerance is `0.1 Angstrom` (`-symprec`), with a `5 degree` angle
|
|
266
275
|
tolerance (`-angle-tolerance`). CIF occupancies are retained by the pymatgen
|
|
267
276
|
structure and written directly rather than reconstructed after symmetry finding.
|
|
268
|
-
CSV mode reads serialized pymatgen `Structure` dictionaries from a required `cif`
|
|
269
|
-
column. `-csv INPUT.csv -of NAME` writes `INDEX-NAME-SPACEGROUP.cif`, for
|
|
277
|
+
With `-symm`, CSV mode reads serialized pymatgen `Structure` dictionaries from a required `cif`
|
|
278
|
+
column. `-csv INPUT.csv -of NAME -symm` writes `INDEX-NAME-SPACEGROUP.cif`, for
|
|
270
279
|
example `1-Li2YCl3-66.cif`. Every run also writes `symmetry.txt` with one row
|
|
271
280
|
per successfully generated CIF: filename, crystal system, space-group symbol,
|
|
272
281
|
and space-group number. Crystal-system and space-group summaries are sorted
|
|
@@ -282,7 +291,7 @@ the `_symmetry_equiv_pos_as_xyz` loop is sourced from pymatgen's `SpaceGroup`
|
|
|
282
291
|
database instead of serializing noisy numerical translations returned by
|
|
283
292
|
structure-specific symmetry detection.
|
|
284
293
|
|
|
285
|
-
Every CIF exported by `
|
|
294
|
+
Every CIF exported by `convert`, `substitute`, `chemsys-entry`,
|
|
286
295
|
`supercell`, or `gpumd-group` is written through the same CIF helper. `convert`
|
|
287
296
|
writes P1 by default and performs symmetry detection only when `-symm` is
|
|
288
297
|
present; the other commands retain their command-specific symmetry behavior.
|
|
@@ -293,7 +302,15 @@ atom-site loop. Multiplicity-letter
|
|
|
293
302
|
assignments use the conventional
|
|
294
303
|
International Tables setting from pymatgen/spglib and correspond to the
|
|
295
304
|
[Bilbao Crystallographic Server WYCKPOS tables](https://cryst.ehu.es/cryst/get_wp.html);
|
|
296
|
-
the provenance URL is also recorded inside each generated CIF.
|
|
305
|
+
the provenance URL is also recorded inside each generated CIF.
|
|
306
|
+
|
|
307
|
+
For structural conversion, supercell creation, and GPUMD grouping, PMK reads
|
|
308
|
+
CIF input with pymatgen rather than ASE. ASE interprets
|
|
309
|
+
`_space_group_crystal_system` as a setting hint only for rhombohedral groups
|
|
310
|
+
and emits a potentially misleading warning for other crystal systems. Using
|
|
311
|
+
pymatgen avoids that ambiguity while PMK continues to use ASE for non-CIF
|
|
312
|
+
formats. A partially occupied CIF must first be ordered with `convert -d2o`
|
|
313
|
+
before it can be represented as an explicit atom list.
|
|
297
314
|
|
|
298
315
|
CIF `_atom_type_symbol` and `_atom_site_label` values use per-element
|
|
299
316
|
inequivalent-site identifiers such as `Li1`, `Y1`, `Cl1`, and `Cl2`.
|
|
@@ -327,30 +344,42 @@ For folder conversion, `convert -if INPUT -o OUTPUT` and
|
|
|
327
344
|
`convert -if INPUT -of OUTPUT` are equivalent and write `.xyz` by default. Set
|
|
328
345
|
another target extension with `-oe`, for example `-oe cif` or `-oe vasp`.
|
|
329
346
|
Single-file and trajectory modes infer the target format from `-o` and `-ot`.
|
|
330
|
-
Because Wyckoff metadata describes one crystal structure, each `.cif` output
|
|
331
|
-
file accepts exactly one frame.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
`
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
`
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
347
|
+
Because Wyckoff metadata describes one crystal structure, each `.cif` output
|
|
348
|
+
file accepts exactly one frame.
|
|
349
|
+
|
|
350
|
+
For ordinary conversion, the three supercell options are mutually exclusive and
|
|
351
|
+
are resolved separately for every input structure. For example, a cell with
|
|
352
|
+
`a=3 A`, `b=4 A`, and `c=5 A` plus `-supercell-close-abc 20` uses `7x5x4`,
|
|
353
|
+
giving approximately `21 A`, `20 A`, and `20 A`. The numeric value is the target:
|
|
354
|
+
`-supercell-close-abc 40` targets approximately `40 A`.
|
|
355
|
+
|
|
356
|
+
`convert -d2o` orders partially occupied CIF structures.
|
|
357
|
+
`-supercell-matrix` defaults internally to `1x1x1` in this mode; three
|
|
358
|
+
diagonal factors or nine integer matrix entries are accepted. Because Ewald
|
|
359
|
+
candidates must be compared using one fixed cell, `-supercell-max-abc` and
|
|
360
|
+
`-supercell-close-abc` are not accepted with `-d2o`. Folder ordering scans
|
|
361
|
+
recursively, groups `BASE-dN.cif` files by `BASE`, and writes only the
|
|
362
|
+
lowest-total-Ewald candidate into the flat output folder as `BASE-oN.cif`,
|
|
363
|
+
where `N` remains tied to its source `-dN` file. Pymatgen
|
|
364
|
+
`EnumerateStructureTransformation` is used when enumlib is available; the
|
|
365
|
+
expected missing-enumlib runtime error quietly switches to
|
|
366
|
+
`OrderDisorderedStructureTransformation`. Every output CIF records total Ewald
|
|
367
|
+
energy on line two as `# E_e = VALUE eV`, and `d2o.txt` records all candidates,
|
|
368
|
+
methods, energies, failures, and selections. Add `-symm` to import symmetry into
|
|
369
|
+
the ordered CIF; otherwise it is P1. Without `-d2o`, the
|
|
370
|
+
`-supercell-matrix`, `-supercell-max-abc`, and `-supercell-close-abc` options
|
|
371
|
+
build an ordinary structural supercell and never enable ordering automatically.
|
|
372
|
+
|
|
373
|
+
After `supercell -temp` creates all requested jobs, it checks generated folders
|
|
374
|
+
that contain `run.in`, `model.xyz`, and `nep.txt`. Existing `ensemble npt_scr`
|
|
375
|
+
lines retain their temperature parameters but are rewritten with three
|
|
376
|
+
pressure/modulus components for 90-degree orthorhombic cells and all six
|
|
377
|
+
components for non-orthorhombic cells, following the
|
|
378
|
+
[official GPUMD ensemble syntax](https://gpumd.org/gpumd/input_parameters/ensemble_standard.html).
|
|
379
|
+
Other ensemble types are not modified. Use `-gpumd-group Li Y Cl` to embed the
|
|
380
|
+
standalone `gpumd-group` tagging behavior in generated `model.xyz` files. The
|
|
381
|
+
same option also tags ordinary `.xyz` or `.extxyz` supercell output; formats
|
|
382
|
+
that cannot preserve GPUMD group arrays are rejected before output is written.
|
|
354
383
|
`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.
|
|
355
384
|
|
|
356
385
|
GPUMD box matrices are interpreted row by row by
|
|
@@ -433,7 +462,7 @@ pymdkit/
|
|
|
433
462
|
|-- vasp_relax.py
|
|
434
463
|
|-- vasp_static.py
|
|
435
464
|
|-- ...
|
|
436
|
-
`--
|
|
465
|
+
`-- _symmetry.py # private symmetry conversion/report helper
|
|
437
466
|
```
|
|
438
467
|
|
|
439
468
|
Modules whose name starts with `_` are shared helpers and are skipped by the
|
|
@@ -471,5 +500,5 @@ fast and a missing optional dependency only affects the one command that needs i
|
|
|
471
500
|
Every command module still works directly, which is handy for debugging:
|
|
472
501
|
|
|
473
502
|
```bash
|
|
474
|
-
python src/pymdkit/commands/supercell.py -i in.cif -
|
|
503
|
+
python src/pymdkit/commands/supercell.py -i in.cif -close-abc 20 -o sc.vasp
|
|
475
504
|
```
|
|
@@ -424,13 +424,22 @@ def write_cif(
|
|
|
424
424
|
data_name=None,
|
|
425
425
|
refine=True,
|
|
426
426
|
symmetrize=True,
|
|
427
|
-
standardized=False,
|
|
427
|
+
standardized=False,
|
|
428
|
+
add_oxidation=True,
|
|
428
429
|
):
|
|
429
430
|
"""Write a CIF and return ``(symbol, number)``."""
|
|
430
431
|
from pymatgen.io.cif import CifWriter
|
|
431
432
|
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
|
|
432
433
|
|
|
433
|
-
structure =
|
|
434
|
+
structure = as_pymatgen_structure(structure)
|
|
435
|
+
if add_oxidation:
|
|
436
|
+
structure = _oxidation.decorate_structure(structure)
|
|
437
|
+
else:
|
|
438
|
+
structure = structure.copy()
|
|
439
|
+
try:
|
|
440
|
+
structure.remove_oxidation_states()
|
|
441
|
+
except (AttributeError, ValueError):
|
|
442
|
+
pass
|
|
434
443
|
if symmetrize:
|
|
435
444
|
analyzer = SpacegroupAnalyzer(
|
|
436
445
|
structure,
|
|
@@ -442,7 +451,8 @@ def write_cif(
|
|
|
442
451
|
structure, symprec, angle_tolerance
|
|
443
452
|
)
|
|
444
453
|
standardized = True
|
|
445
|
-
|
|
454
|
+
if add_oxidation:
|
|
455
|
+
structure = _oxidation.decorate_structure(structure)
|
|
446
456
|
symbol = analyzer.get_space_group_symbol()
|
|
447
457
|
number = analyzer.get_space_group_number()
|
|
448
458
|
crystal_system = analyzer.get_crystal_system()
|
|
@@ -460,7 +470,10 @@ def write_cif(
|
|
|
460
470
|
cif_file = getattr(writer, "cif_file", None) or writer._cf
|
|
461
471
|
block = _add_p1_wyckoff_column(cif_file)
|
|
462
472
|
|
|
463
|
-
structure_oxidations =
|
|
473
|
+
structure_oxidations = (
|
|
474
|
+
_oxidation.uniform_oxidation_states(structure)
|
|
475
|
+
if add_oxidation else {}
|
|
476
|
+
)
|
|
464
477
|
_set_crystal_system(block, crystal_system)
|
|
465
478
|
_relabel_atom_types(block, structure_oxidations)
|
|
466
479
|
_format_cif_numbers(block)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Shared supercell sizing and application helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
IDENTITY_MATRIX = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def matrix_determinant(matrix):
|
|
13
|
+
return (
|
|
14
|
+
matrix[0][0] * (matrix[1][1] * matrix[2][2]
|
|
15
|
+
- matrix[1][2] * matrix[2][1])
|
|
16
|
+
- matrix[0][1] * (matrix[1][0] * matrix[2][2]
|
|
17
|
+
- matrix[1][2] * matrix[2][0])
|
|
18
|
+
+ matrix[0][2] * (matrix[1][0] * matrix[2][1]
|
|
19
|
+
- matrix[1][1] * matrix[2][0])
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_matrix(values, option_name="-matrix"):
|
|
24
|
+
"""Parse diagonal AxBxC or a full nine-integer matrix."""
|
|
25
|
+
if values is None:
|
|
26
|
+
return [row[:] for row in IDENTITY_MATRIX]
|
|
27
|
+
if isinstance(values, str):
|
|
28
|
+
values = [values]
|
|
29
|
+
tokens = []
|
|
30
|
+
for value in values:
|
|
31
|
+
tokens.extend(
|
|
32
|
+
token for token in re.split(r"[ ,xX]+", str(value)) if token
|
|
33
|
+
)
|
|
34
|
+
try:
|
|
35
|
+
numbers = [int(token) for token in tokens]
|
|
36
|
+
except ValueError as exc:
|
|
37
|
+
raise ValueError(f"{option_name} values must be integers") from exc
|
|
38
|
+
if len(numbers) == 3:
|
|
39
|
+
if any(value <= 0 for value in numbers):
|
|
40
|
+
raise ValueError(
|
|
41
|
+
f"diagonal {option_name} factors must be positive"
|
|
42
|
+
)
|
|
43
|
+
return [
|
|
44
|
+
[numbers[0], 0, 0],
|
|
45
|
+
[0, numbers[1], 0],
|
|
46
|
+
[0, 0, numbers[2]],
|
|
47
|
+
]
|
|
48
|
+
if len(numbers) == 9:
|
|
49
|
+
matrix = [numbers[0:3], numbers[3:6], numbers[6:9]]
|
|
50
|
+
if matrix_determinant(matrix) == 0:
|
|
51
|
+
raise ValueError(f"{option_name} must be nonsingular")
|
|
52
|
+
return matrix
|
|
53
|
+
raise ValueError(
|
|
54
|
+
f"{option_name} accepts 1x1x1, three diagonal factors, "
|
|
55
|
+
"or nine matrix entries"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_identity(matrix):
|
|
60
|
+
return matrix == IDENTITY_MATRIX
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def diagonal_factors(matrix):
|
|
64
|
+
"""Return positive diagonal factors, or None for a general matrix."""
|
|
65
|
+
if any(
|
|
66
|
+
matrix[row][column] != 0
|
|
67
|
+
for row in range(3)
|
|
68
|
+
for column in range(3)
|
|
69
|
+
if row != column
|
|
70
|
+
):
|
|
71
|
+
return None
|
|
72
|
+
factors = [matrix[index][index] for index in range(3)]
|
|
73
|
+
return factors if all(value > 0 for value in factors) else None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def max_abc_matrix(lengths, maximum):
|
|
77
|
+
"""Largest diagonal repeats whose resulting lengths do not exceed L."""
|
|
78
|
+
maximum = float(maximum)
|
|
79
|
+
if maximum <= 0:
|
|
80
|
+
raise ValueError("maximum cell length must be greater than zero")
|
|
81
|
+
factors = [
|
|
82
|
+
max(1, int(maximum // float(length))) if float(length) > 0 else 1
|
|
83
|
+
for length in lengths
|
|
84
|
+
]
|
|
85
|
+
return parse_matrix(factors)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def close_abc_matrix(lengths, target):
|
|
89
|
+
"""Diagonal repeats that place each cell length nearest a target length."""
|
|
90
|
+
target = float(target)
|
|
91
|
+
if target <= 0:
|
|
92
|
+
raise ValueError("target cell length must be greater than zero")
|
|
93
|
+
factors = [
|
|
94
|
+
max(1, int(math.floor(target / float(length) + 0.5)))
|
|
95
|
+
if float(length) > 0 else 1
|
|
96
|
+
for length in lengths
|
|
97
|
+
]
|
|
98
|
+
return parse_matrix(factors)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def resolve_matrix(
|
|
102
|
+
lengths,
|
|
103
|
+
*,
|
|
104
|
+
matrix=None,
|
|
105
|
+
max_abc=None,
|
|
106
|
+
close_abc=None,
|
|
107
|
+
matrix_option="-matrix",
|
|
108
|
+
):
|
|
109
|
+
"""Resolve one mutually exclusive sizing mode to a 3x3 integer matrix."""
|
|
110
|
+
selected = sum(value is not None for value in (matrix, max_abc, close_abc))
|
|
111
|
+
if selected > 1:
|
|
112
|
+
raise ValueError("choose only one supercell sizing option")
|
|
113
|
+
if matrix is not None:
|
|
114
|
+
return parse_matrix(matrix, option_name=matrix_option)
|
|
115
|
+
if max_abc is not None:
|
|
116
|
+
return max_abc_matrix(lengths, max_abc)
|
|
117
|
+
if close_abc is not None:
|
|
118
|
+
return close_abc_matrix(lengths, close_abc)
|
|
119
|
+
return [row[:] for row in IDENTITY_MATRIX]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def apply_to_atoms(atoms, matrix):
|
|
123
|
+
"""Apply a diagonal or general integer supercell matrix to ASE Atoms."""
|
|
124
|
+
factors = diagonal_factors(matrix)
|
|
125
|
+
if factors is not None:
|
|
126
|
+
return atoms.repeat(factors)
|
|
127
|
+
from ase.build import make_supercell
|
|
128
|
+
|
|
129
|
+
return make_supercell(atoms, matrix)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def matrix_text(matrix):
|
|
133
|
+
factors = diagonal_factors(matrix)
|
|
134
|
+
if factors is not None:
|
|
135
|
+
return "x".join(str(value) for value in factors)
|
|
136
|
+
return " ".join(str(value) for row in matrix for value in row)
|