pymdkit 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pymdkit/__init__.py +3 -0
- pymdkit/commands/__init__.py +1 -0
- pymdkit/commands/_fileio.py +96 -0
- pymdkit/commands/_vaspset.py +169 -0
- pymdkit/commands/add_groups.py +77 -0
- pymdkit/commands/compute_ehull.py +230 -0
- pymdkit/commands/compute_msd_all_groups.py +224 -0
- pymdkit/commands/compute_rmsd.py +149 -0
- pymdkit/commands/gather_contcar.py +106 -0
- pymdkit/commands/outcar2xyz.py +141 -0
- pymdkit/commands/select_candidate.py +119 -0
- pymdkit/commands/stru2xyz.py +100 -0
- pymdkit/commands/supercell.py +164 -0
- pymdkit/commands/symmetrize.py +271 -0
- pymdkit/commands/vasp_relax.py +62 -0
- pymdkit/commands/vasp_static.py +59 -0
- pymdkit/pymdkit_main.py +115 -0
- pymdkit-1.0.0.dist-info/METADATA +201 -0
- pymdkit-1.0.0.dist-info/RECORD +23 -0
- pymdkit-1.0.0.dist-info/WHEEL +5 -0
- pymdkit-1.0.0.dist-info/entry_points.txt +2 -0
- pymdkit-1.0.0.dist-info/licenses/LICENSE +674 -0
- pymdkit-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Write VASP relaxation inputs (pymatgen MPRelaxSet) for a structure.
|
|
2
|
+
|
|
3
|
+
Single file: vasp-relax -i model.vasp
|
|
4
|
+
Whole folder: vasp-relax -if optimal_occupancy # one ./<name>/ job per structure
|
|
5
|
+
Trajectory: vasp-relax -it traj.xyz # one ./frame_N/ job per frame
|
|
6
|
+
Custom INCAR: vasp-relax -if optimal_occupancy -custom-setting my_settings.txt
|
|
7
|
+
|
|
8
|
+
VASP jobs are always individual (one structure per folder). The defaults below
|
|
9
|
+
can be overridden with a -custom-setting file.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
|
|
14
|
+
from pymatgen.io.vasp.sets import MPRelaxSet
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from . import _vaspset
|
|
18
|
+
except ImportError: # running as a standalone script
|
|
19
|
+
import _vaspset
|
|
20
|
+
|
|
21
|
+
COMMAND = "vasp-relax"
|
|
22
|
+
HELP = "Write VASP relaxation inputs for a structure (file, folder, or trajectory)."
|
|
23
|
+
|
|
24
|
+
# Default INCAR settings for a relaxation run.
|
|
25
|
+
DEFAULT_SETTINGS = {
|
|
26
|
+
"ALGO": "Normal",
|
|
27
|
+
"EDIFF": "1e-05",
|
|
28
|
+
"EDIFFG": "-0.02",
|
|
29
|
+
"ENCUT": "520.0",
|
|
30
|
+
"GGA": "PE",
|
|
31
|
+
"KGAMMA": "True",
|
|
32
|
+
"KSPACING": "0.4",
|
|
33
|
+
"LASPH": "True",
|
|
34
|
+
"LCHARG": "False",
|
|
35
|
+
"LREAL": "Auto",
|
|
36
|
+
"LWAVE": "False",
|
|
37
|
+
"NELM": "120",
|
|
38
|
+
"NSW": "300",
|
|
39
|
+
"ISIF": "3",
|
|
40
|
+
"IBRION": "2",
|
|
41
|
+
"PREC": "Normal",
|
|
42
|
+
"ISMEAR": "0",
|
|
43
|
+
"SIGMA": "0.05",
|
|
44
|
+
"LMAXMIX": None,
|
|
45
|
+
"MAGMOM": None,
|
|
46
|
+
"ISPIN": "1",
|
|
47
|
+
"LORBIT": None,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def add_arguments(parser):
|
|
52
|
+
_vaspset.add_vasp_arguments(parser)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def run(args):
|
|
56
|
+
return _vaspset.run_vasp_set(args, MPRelaxSet, DEFAULT_SETTINGS)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
_p = argparse.ArgumentParser(description=__doc__)
|
|
61
|
+
add_arguments(_p)
|
|
62
|
+
raise SystemExit(run(_p.parse_args()))
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Write VASP static / single-point inputs (pymatgen MPStaticSet) for a structure.
|
|
2
|
+
|
|
3
|
+
Single file: vasp-static -i model.vasp
|
|
4
|
+
Whole folder: vasp-static -if optimal_occupancy # one ./<name>/ job per structure
|
|
5
|
+
Trajectory: vasp-static -it export_FPS_Filter_structure.xyz # one ./frame_N/ job per frame
|
|
6
|
+
Custom INCAR: vasp-static -it traj.xyz -custom-setting my_settings.txt
|
|
7
|
+
|
|
8
|
+
VASP jobs are always individual (one structure per folder). The defaults below
|
|
9
|
+
(a single-point NSW=0 run) can be overridden with a -custom-setting file.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
|
|
14
|
+
from pymatgen.io.vasp.sets import MPStaticSet
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from . import _vaspset
|
|
18
|
+
except ImportError: # running as a standalone script
|
|
19
|
+
import _vaspset
|
|
20
|
+
|
|
21
|
+
COMMAND = "vasp-static"
|
|
22
|
+
HELP = "Write VASP static (single-point) inputs for a structure (file, folder, or trajectory)."
|
|
23
|
+
|
|
24
|
+
# Default INCAR settings for a static / single-point run.
|
|
25
|
+
DEFAULT_SETTINGS = {
|
|
26
|
+
"ALGO": "Normal",
|
|
27
|
+
"EDIFF": "1e-05",
|
|
28
|
+
"ENCUT": "520.0",
|
|
29
|
+
"GGA": "PE",
|
|
30
|
+
"KGAMMA": "True",
|
|
31
|
+
"KSPACING": "0.4",
|
|
32
|
+
"LASPH": "True",
|
|
33
|
+
"LCHARG": "False",
|
|
34
|
+
"LREAL": "Auto",
|
|
35
|
+
"LWAVE": "False",
|
|
36
|
+
"NELM": "120",
|
|
37
|
+
"NSW": "0",
|
|
38
|
+
"MAGMOM": None,
|
|
39
|
+
"LMAXMIX": None,
|
|
40
|
+
"LORBIT": None,
|
|
41
|
+
"ISPIN": "1",
|
|
42
|
+
"PREC": "Normal",
|
|
43
|
+
"ISMEAR": "0",
|
|
44
|
+
"SIGMA": "0.05",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def add_arguments(parser):
|
|
49
|
+
_vaspset.add_vasp_arguments(parser)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def run(args):
|
|
53
|
+
return _vaspset.run_vasp_set(args, MPStaticSet, DEFAULT_SETTINGS)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
_p = argparse.ArgumentParser(description=__doc__)
|
|
58
|
+
add_arguments(_p)
|
|
59
|
+
raise SystemExit(run(_p.parse_args()))
|
pymdkit/pymdkit_main.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pymdkit dispatcher.
|
|
3
|
+
|
|
4
|
+
Builds a single ``pymdkit`` command whose subcommands are auto-discovered from
|
|
5
|
+
the :mod:`pymdkit.commands` package. Each command module must define::
|
|
6
|
+
|
|
7
|
+
COMMAND : str # subcommand name, e.g. "msd"
|
|
8
|
+
HELP : str # one-line help string
|
|
9
|
+
def add_arguments(parser): ... # register the command's flags
|
|
10
|
+
def run(args) -> int: ... # do the work, return an exit code
|
|
11
|
+
|
|
12
|
+
Discovery is two-phase. Command *names* and *help* lines are read straight from
|
|
13
|
+
the source files via the ``ast`` module -- without importing them -- so listing
|
|
14
|
+
``pymdkit --help`` never triggers a heavy ``import pymatgen`` / ``import ase``.
|
|
15
|
+
Only the single module you actually invoke is imported. A command whose optional
|
|
16
|
+
dependency is missing therefore fails only when you run it, never for the whole
|
|
17
|
+
CLI.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import ast
|
|
24
|
+
import importlib
|
|
25
|
+
import pkgutil
|
|
26
|
+
import sys
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Dict, Tuple
|
|
29
|
+
|
|
30
|
+
from . import __version__, commands
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _read_meta(path: Path, modname: str) -> Tuple[str, str]:
|
|
34
|
+
"""Read (command_name, help_text) from a source file without importing it."""
|
|
35
|
+
command, help_text = modname, ""
|
|
36
|
+
try:
|
|
37
|
+
tree = ast.parse(path.read_text(encoding="utf-8"))
|
|
38
|
+
except (OSError, SyntaxError):
|
|
39
|
+
return command, help_text
|
|
40
|
+
|
|
41
|
+
for node in tree.body:
|
|
42
|
+
if isinstance(node, ast.Assign):
|
|
43
|
+
for target in node.targets:
|
|
44
|
+
if (isinstance(target, ast.Name)
|
|
45
|
+
and isinstance(node.value, ast.Constant)
|
|
46
|
+
and isinstance(node.value.value, str)):
|
|
47
|
+
if target.id == "COMMAND":
|
|
48
|
+
command = node.value.value
|
|
49
|
+
elif target.id == "HELP":
|
|
50
|
+
help_text = node.value.value
|
|
51
|
+
|
|
52
|
+
if not help_text:
|
|
53
|
+
doc = (ast.get_docstring(tree) or "").strip()
|
|
54
|
+
help_text = doc.splitlines()[0] if doc else ""
|
|
55
|
+
return command, help_text
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _discover() -> Dict[str, Tuple[str, str]]:
|
|
59
|
+
"""Return {command_name: (module_name, help_text)} for every command module."""
|
|
60
|
+
pkg_dir = Path(commands.__path__[0])
|
|
61
|
+
found: Dict[str, Tuple[str, str]] = {}
|
|
62
|
+
for info in pkgutil.iter_modules([str(pkg_dir)]):
|
|
63
|
+
if info.name.startswith("_"):
|
|
64
|
+
continue
|
|
65
|
+
command, help_text = _read_meta(pkg_dir / f"{info.name}.py", info.name)
|
|
66
|
+
found[command] = (info.name, help_text)
|
|
67
|
+
return found
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _build_top_parser(cmds: Dict[str, Tuple[str, str]]) -> argparse.ArgumentParser:
|
|
71
|
+
parser = argparse.ArgumentParser(
|
|
72
|
+
prog="pymdkit",
|
|
73
|
+
description="Unified CLI for atomistic / MD structure workflows.",
|
|
74
|
+
)
|
|
75
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
76
|
+
sub = parser.add_subparsers(dest="command", metavar="<command>")
|
|
77
|
+
for name in sorted(cmds):
|
|
78
|
+
sub.add_parser(name, help=cmds[name][1], add_help=False)
|
|
79
|
+
return parser
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def main(argv=None) -> int:
|
|
83
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
84
|
+
cmds = _discover()
|
|
85
|
+
top = _build_top_parser(cmds)
|
|
86
|
+
|
|
87
|
+
# No command, or top-level help/version: let the top parser handle it.
|
|
88
|
+
if not argv:
|
|
89
|
+
top.print_help()
|
|
90
|
+
return 1
|
|
91
|
+
if argv[0] in ("-h", "--help", "--version"):
|
|
92
|
+
top.parse_args(argv) # prints help/version and exits
|
|
93
|
+
return 0
|
|
94
|
+
|
|
95
|
+
command = argv[0]
|
|
96
|
+
if command not in cmds:
|
|
97
|
+
top.error(f"invalid choice: {command!r} (choose from {', '.join(sorted(cmds))})")
|
|
98
|
+
|
|
99
|
+
# Phase 2: import only the chosen module and build its dedicated parser.
|
|
100
|
+
modname = cmds[command][0]
|
|
101
|
+
module = importlib.import_module(f"{commands.__name__}.{modname}")
|
|
102
|
+
cmd_parser = argparse.ArgumentParser(
|
|
103
|
+
prog=f"pymdkit {command}",
|
|
104
|
+
description=getattr(module, "HELP", None) or (module.__doc__ or "").strip(),
|
|
105
|
+
)
|
|
106
|
+
if hasattr(module, "add_arguments"):
|
|
107
|
+
module.add_arguments(cmd_parser)
|
|
108
|
+
args = cmd_parser.parse_args(argv[1:])
|
|
109
|
+
|
|
110
|
+
result = module.run(args)
|
|
111
|
+
return 0 if result is None else int(result)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if __name__ == "__main__":
|
|
115
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pymdkit
|
|
3
|
+
Version: 1.0.0
|
|
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: scipy
|
|
18
|
+
Requires-Dist: ase
|
|
19
|
+
Requires-Dist: pymatgen
|
|
20
|
+
Requires-Dist: mp_api
|
|
21
|
+
Requires-Dist: pyxtal
|
|
22
|
+
Requires-Dist: gemmi
|
|
23
|
+
Requires-Dist: tqdm
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# pymdkit
|
|
27
|
+
|
|
28
|
+
A single command-line tool that bundles a collection of atomistic / molecular-dynamics
|
|
29
|
+
structure scripts behind one executable: `pymdkit`. Instead of copying individual
|
|
30
|
+
scripts into each working folder and running `python some_script.py`, you install
|
|
31
|
+
`pymdkit` once and call any tool from anywhere as `pymdkit <command> [options]`.
|
|
32
|
+
|
|
33
|
+
Every command exposes named `--flags` (no positional guessing), and each underlying
|
|
34
|
+
script is still runnable on its own.
|
|
35
|
+
|
|
36
|
+
## Install ("compiling" the executable)
|
|
37
|
+
|
|
38
|
+
Python isn't compiled to a binary; the equivalent step is installing the package,
|
|
39
|
+
which creates the `pymdkit` command on your `PATH`.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cd pymdkit # the folder containing pyproject.toml
|
|
43
|
+
pip install -e . # editable install: edits to scripts take effect immediately
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
On an HPC cluster, activate your conda env / `module load` first so `pymdkit` lands in
|
|
47
|
+
that environment's `bin`. This installs every dependency (numpy, scipy, ase, pymatgen,
|
|
48
|
+
pyxtal, mp_api, gemmi, tqdm), so all commands work out of the box.
|
|
49
|
+
|
|
50
|
+
Verify:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pymdkit --version
|
|
54
|
+
pymdkit --help # lists every command
|
|
55
|
+
pymdkit <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
|
+
| `add-groups` | Tag atoms with a GPUMD group index by element order |
|
|
67
|
+
| `ehull` | Auto-detect VASP job folders and compute E_hull vs Materials Project |
|
|
68
|
+
| `gather-contcar` | Collect CONTCARs from VASP job folders into one folder, renamed `<folder>.vasp` |
|
|
69
|
+
| `msd` | Diffusivity & conductivity for all groups from GPUMD MSD data |
|
|
70
|
+
| `outcar2xyz` | Collect SCF-converged VASP job folders (any name) into one extxyz file |
|
|
71
|
+
| `rmsd` | Compute RMSD between two structure files, or all pairs in a folder |
|
|
72
|
+
| `select-candidate` | Split a NEP training set into candidate/accurate sets by energy error |
|
|
73
|
+
| `stru2xyz` | Convert structure file(s) of any format to extxyz |
|
|
74
|
+
| `supercell` | Build a supercell with cell lengths capped at a maximum (Angstrom); optional per-temperature GPUMD setup |
|
|
75
|
+
| `symmetrize` | Import space-group symmetry into a structure file (or folder) -> CIF |
|
|
76
|
+
| `vasp-relax` | Write VASP relaxation inputs for a structure (or folder); INCAR tags overridable |
|
|
77
|
+
| `vasp-static` | Write VASP static / single-point inputs for a structure (or folder) |
|
|
78
|
+
|
|
79
|
+
## Examples
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pymdkit add-groups -i opted.cif --elements Li Y Cl -o model.xyz
|
|
83
|
+
pymdkit add-groups -if cifs/ --elements Li Y Cl -of cifs-grouped/ # whole folder
|
|
84
|
+
pymdkit add-groups --elements Li Y Cl # scan subfolders, tag each model.xyz in place
|
|
85
|
+
pymdkit stru2xyz -i opted.vasp -o opted.xyz # convert one file
|
|
86
|
+
pymdkit stru2xyz -if vasp-opted -of xyz-opted # convert a whole folder
|
|
87
|
+
pymdkit stru2xyz # scan subfolders, convert structures in place
|
|
88
|
+
pymdkit supercell -i opted.vasp -o sc.vasp -max-abc 20 # cell lengths <= 20 A
|
|
89
|
+
pymdkit supercell -if vasp-opted -max-abc 20 -individual # per-structure ./<name>/<name>.<ext>
|
|
90
|
+
pymdkit supercell -if extxyz-opted -max-abc 24 -individual -temp 500 600 -md-if input-files
|
|
91
|
+
# GPUMD: ./<name>/model.xyz + ./<name>/<T>/ jobs
|
|
92
|
+
pymdkit vasp-relax -i opted.vasp # relax inputs in current dir
|
|
93
|
+
pymdkit vasp-relax -if optimal_occupancy # one ./<name>/ job folder per structure
|
|
94
|
+
pymdkit vasp-static -if cifs/ -custom-setting my_incar.txt # static inputs, custom INCAR
|
|
95
|
+
pymdkit vasp-static -it traj.xyz # one ./frame_N/ job per trajectory frame
|
|
96
|
+
pymdkit msd --diffuse_ion Li --ion_charge 1 --output_dir results/
|
|
97
|
+
pymdkit ehull --mp-api-key $MP_API_KEY # scans ./ for VASP jobs -> ehull.txt
|
|
98
|
+
pymdkit gather-contcar -of vasp-opted # CONTCARs -> vasp-opted/<folder>.vasp
|
|
99
|
+
pymdkit gather-contcar -of vasp-opted -ehull 0.028 # only structures with E_hull < 0.028 eV/atom
|
|
100
|
+
pymdkit outcar2xyz # scans ./ for OUTCAR folders -> scf-converged.xyz
|
|
101
|
+
pymdkit select-candidate # RMSE bands: <low all accurate, >high all candidate, else worst 50%
|
|
102
|
+
pymdkit select-candidate -r 0.8 # in the middle band, take worst 80% as candidate.xyz
|
|
103
|
+
pymdkit rmsd a.cif b.cif # RMSD of two files -> rmsd.txt
|
|
104
|
+
pymdkit rmsd vasp-opted/ # all pairs in a folder -> rmsd.txt
|
|
105
|
+
pymdkit symmetrize opted.cif --symprec 0.01 --add_oxidation yes -o opted-symm.cif
|
|
106
|
+
pymdkit symmetrize my_cifs/ --symprec 0.01 --add_oxidation no -o my_cifs-symm
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
VASP input commands (`vasp-relax`, `vasp-static`) always produce **individual**
|
|
110
|
+
jobs (one structure per folder): `-i` writes into the current dir (or `-o`),
|
|
111
|
+
`-if` creates one `./<name>/` folder per structure, and `-it` creates one
|
|
112
|
+
`./frame_N/` folder per trajectory frame — all directly in the current path.
|
|
113
|
+
|
|
114
|
+
They start from sensible default INCAR settings; override them by passing a
|
|
115
|
+
settings file with `-custom-setting FILE`. The file may be a Python-dict block
|
|
116
|
+
or `KEY = VALUE` lines (a `None`/blank value clears a tag):
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
custom_settings = {
|
|
120
|
+
"ENCUT": "600.0",
|
|
121
|
+
"ISIF": "3",
|
|
122
|
+
"MAGMOM": None
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`vasp-static -it traj.xyz` (also available on `vasp-relax`) reads a
|
|
127
|
+
multi-structure trajectory and writes one job sub-folder per frame
|
|
128
|
+
(`frame_1/`, `frame_2/`, …, prefix configurable via `--frame-prefix`). Each
|
|
129
|
+
folder also keeps a `frame_N.xyz`, so `Config_type` survives for a later
|
|
130
|
+
`outcar2xyz`.
|
|
131
|
+
|
|
132
|
+
Each command's full flag list is in `pymdkit <command> --help`.
|
|
133
|
+
|
|
134
|
+
`ehull` auto-detects every sub-folder of the current path that contains a
|
|
135
|
+
`vasprun.xml`, groups them by chemical system (elements ordered by
|
|
136
|
+
electronegativity, e.g. `Li-Y-Cl`), and builds/reuses one `mp_cache_<system>.json`
|
|
137
|
+
per system — so a pure Li-Y-Cl batch yields a single `mp_cache_Li-Y-Cl.json`, while
|
|
138
|
+
a mixed Li-Y-Cl + La-O batch yields both `mp_cache_Li-Y-Cl.json` and
|
|
139
|
+
`mp_cache_La-O.json`. (Formation energy is reported alongside E_hull in
|
|
140
|
+
`ehull.txt`.)
|
|
141
|
+
|
|
142
|
+
## Layout
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
pymdkit/
|
|
146
|
+
├── pyproject.toml # package metadata + the `pymdkit` entry point
|
|
147
|
+
├── README.md
|
|
148
|
+
└── src/pymdkit/
|
|
149
|
+
├── pymdkit_main.py # dispatcher: discovers and runs commands
|
|
150
|
+
└── commands/ # one module per command
|
|
151
|
+
├── _fileio.py # shared -i/-o/-if/-of helper (not a command)
|
|
152
|
+
├── _vaspset.py # shared VASP input-set helper (not a command)
|
|
153
|
+
├── add_groups.py
|
|
154
|
+
├── compute_ehull.py
|
|
155
|
+
├── compute_rmsd.py
|
|
156
|
+
├── outcar2xyz.py
|
|
157
|
+
├── stru2xyz.py
|
|
158
|
+
├── supercell.py
|
|
159
|
+
├── vasp_relax.py
|
|
160
|
+
├── vasp_static.py
|
|
161
|
+
├── ...
|
|
162
|
+
└── symmetrize.py
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Modules whose name starts with `_` are shared helpers and are skipped by the
|
|
166
|
+
dispatcher, so they never appear as commands.
|
|
167
|
+
|
|
168
|
+
## Adding a new tool later
|
|
169
|
+
|
|
170
|
+
Drop a module in `src/pymdkit/commands/` that defines four things:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
COMMAND = "my-tool" # the subcommand name you'll type
|
|
174
|
+
HELP = "One-line description."
|
|
175
|
+
|
|
176
|
+
def add_arguments(parser): # register flags
|
|
177
|
+
parser.add_argument("--input", required=True)
|
|
178
|
+
|
|
179
|
+
def run(args): # do the work; return an exit code (0 = ok)
|
|
180
|
+
...
|
|
181
|
+
return 0
|
|
182
|
+
|
|
183
|
+
if __name__ == "__main__": # keeps the script runnable on its own
|
|
184
|
+
import argparse
|
|
185
|
+
_p = argparse.ArgumentParser(description=__doc__)
|
|
186
|
+
add_arguments(_p)
|
|
187
|
+
raise SystemExit(run(_p.parse_args()))
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
It will appear in `pymdkit --help` automatically — no central registration needed.
|
|
191
|
+
Put heavy imports (pymatgen, ase, …) inside `run()` where practical; the dispatcher
|
|
192
|
+
reads each command's name and help without importing it, so `pymdkit --help` stays
|
|
193
|
+
fast and a missing optional dependency only affects the one command that needs it.
|
|
194
|
+
|
|
195
|
+
## Running a script standalone
|
|
196
|
+
|
|
197
|
+
Every command module still works directly, which is handy for debugging:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
python src/pymdkit/commands/supercell.py -i in.cif -max-abc 20 -o sc.vasp
|
|
201
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
pymdkit/__init__.py,sha256=sHDqx87K1gtY_sHTD1ZMsNh7DtsjU4HKu1C79CHtVCg,94
|
|
2
|
+
pymdkit/pymdkit_main.py,sha256=IcfM0c5pMVM2vDcHUlPMJlatTKzA-k5jaH6oXr8rKVA,4160
|
|
3
|
+
pymdkit/commands/__init__.py,sha256=nN8YQnb7udqPmNxIs7AeIp2qjZdh0JC2Q4wpuvZ4pv0,84
|
|
4
|
+
pymdkit/commands/_fileio.py,sha256=xs5VVFSqJof74ojoDwITYc87j-Rs1USIP-kRyrK7YM4,3696
|
|
5
|
+
pymdkit/commands/_vaspset.py,sha256=ky8LHY6S7vBIci4gv1hL7ezoKKK3r7YXcdpuklmalR4,7100
|
|
6
|
+
pymdkit/commands/add_groups.py,sha256=6iZljChDqghYc8yW3S6hjknJ4TbsZDFKTJznY4fq6fA,2536
|
|
7
|
+
pymdkit/commands/compute_ehull.py,sha256=E5RpsVDejGfq_iY3gtjt9HZsx9A0Ykch0CrAAIhXn_c,8486
|
|
8
|
+
pymdkit/commands/compute_msd_all_groups.py,sha256=SGKcdD9_N5f4yxo1_KDN8upePELF9HKlJySiw1gJNro,9599
|
|
9
|
+
pymdkit/commands/compute_rmsd.py,sha256=Zv8lSvWnuvq7Tse92WpM8gMu1cOW8mXZlIJ7ptb4gWE,4626
|
|
10
|
+
pymdkit/commands/gather_contcar.py,sha256=ULszT5DIO30DoROthXgsfBeGpapx7IggnY1ESTFF0xA,3657
|
|
11
|
+
pymdkit/commands/outcar2xyz.py,sha256=43l74FyezhAkrxtrUfEDaxoQU5ZSbhy6ltJrhc9QeLk,4753
|
|
12
|
+
pymdkit/commands/select_candidate.py,sha256=7OB0fls_XJG5EPbSf4Z4xaY9dp83TAlCAufy0t7YVP0,4862
|
|
13
|
+
pymdkit/commands/stru2xyz.py,sha256=bb225psD9AGtVrxif6KsUax0k1_GjaIazHvhT6OvqRU,3441
|
|
14
|
+
pymdkit/commands/supercell.py,sha256=1ca6ZmO9e2F_FAj_GMpAwEOmttludEQwzu6T05exg-k,6583
|
|
15
|
+
pymdkit/commands/symmetrize.py,sha256=z7b0SvvcWyun0i4D6zeBI6_B1fYhRI1x6MaIxKQiey8,10580
|
|
16
|
+
pymdkit/commands/vasp_relax.py,sha256=s_DTFMSwr1eL9sPb-nLWUPY6nsyCts-aMi4tDVJquYs,1599
|
|
17
|
+
pymdkit/commands/vasp_static.py,sha256=Y2EHNTM7kkfa-K-zF1f2DC-rsf-LPwPlkvX40K07c0E,1605
|
|
18
|
+
pymdkit-1.0.0.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
19
|
+
pymdkit-1.0.0.dist-info/METADATA,sha256=FPHZBegHo0udyw-3c6E2f4RMrGjxjR08-ZZtwgLu2YE,9278
|
|
20
|
+
pymdkit-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
21
|
+
pymdkit-1.0.0.dist-info/entry_points.txt,sha256=20H-GHgwhttbzjAgRVk_KLlYfhNtk7BMD9ho6mTFDZ8,54
|
|
22
|
+
pymdkit-1.0.0.dist-info/top_level.txt,sha256=S215lTymVM5DZmDmG_bg9pMrtQ3z0ruY9LNL3Ud-inI,8
|
|
23
|
+
pymdkit-1.0.0.dist-info/RECORD,,
|