pymdkit 1.2.10__tar.gz → 1.2.12__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.2.10/src/pymdkit.egg-info → pymdkit-1.2.12}/PKG-INFO +1 -1
- {pymdkit-1.2.10 → pymdkit-1.2.12}/pyproject.toml +1 -1
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/__init__.py +1 -1
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/stable_entry.py +35 -63
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/supercell.py +28 -8
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/vasp2xyz.py +21 -3
- {pymdkit-1.2.10 → pymdkit-1.2.12/src/pymdkit.egg-info}/PKG-INFO +1 -1
- {pymdkit-1.2.10 → pymdkit-1.2.12}/LICENSE +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/README.md +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/setup.cfg +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/__init__.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/_fileio.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/_vasp_output.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/_vaspset.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/add_groups.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/compute_ehull.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/compute_msd_all_groups.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/compute_rmsd.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/electrostatic_energy.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/final_energy.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/gather_contcar.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/nep_rmse.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/perturb.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/stru2xyz.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/submit_vasp.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/substitute.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/symmetrize.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/vasp_relax.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/commands/vasp_static.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit/pymdkit_main.py +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit.egg-info/SOURCES.txt +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit.egg-info/dependency_links.txt +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit.egg-info/entry_points.txt +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit.egg-info/requires.txt +0 -0
- {pymdkit-1.2.10 → pymdkit-1.2.12}/src/pymdkit.egg-info/top_level.txt +0 -0
|
@@ -3,10 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
-
import json
|
|
7
6
|
import re
|
|
8
|
-
from urllib.parse import quote
|
|
9
|
-
from urllib.request import Request, urlopen
|
|
10
7
|
from pathlib import Path
|
|
11
8
|
|
|
12
9
|
COMMAND = "stable-entry"
|
|
@@ -152,69 +149,44 @@ def fetch_stable_entries(elements, api_key=None):
|
|
|
152
149
|
return list(by_mpid.values())
|
|
153
150
|
|
|
154
151
|
|
|
155
|
-
def
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
try:
|
|
159
|
-
from pymatgen.ext.matproj import MPRester
|
|
160
|
-
return getattr(MPRester(), "api_key", None)
|
|
161
|
-
except Exception:
|
|
162
|
-
return None
|
|
152
|
+
def get_api_structure(material_id, api_key=None):
|
|
153
|
+
"""Return the MP API structure for a public material_id."""
|
|
154
|
+
from mp_api.client import MPRester as MPApiRester
|
|
163
155
|
|
|
156
|
+
with MPApiRester(api_key) as mpr:
|
|
157
|
+
if hasattr(mpr, "get_structure_by_material_id"):
|
|
158
|
+
try:
|
|
159
|
+
return mpr.get_structure_by_material_id(
|
|
160
|
+
material_id,
|
|
161
|
+
conventional_unit_cell=True,
|
|
162
|
+
)
|
|
163
|
+
except TypeError:
|
|
164
|
+
return mpr.get_structure_by_material_id(material_id)
|
|
165
|
+
|
|
166
|
+
docs = mpr.materials.summary.search(
|
|
167
|
+
material_ids=[material_id],
|
|
168
|
+
fields=["material_id", "structure"],
|
|
169
|
+
)
|
|
170
|
+
if not docs:
|
|
171
|
+
raise RuntimeError(f"no MP summary structure found for {material_id}")
|
|
172
|
+
return doc_get(docs[0], "structure")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def write_mp_symmetrized_cif(material_id, fallback_structure, path, api_key=None):
|
|
176
|
+
"""Write an MP API structure as a symmetrized CIF."""
|
|
177
|
+
from pymatgen.io.cif import CifWriter
|
|
164
178
|
|
|
165
|
-
def extract_cif_text(text):
|
|
166
|
-
stripped = text.lstrip()
|
|
167
|
-
if not stripped.startswith(("{", "[")):
|
|
168
|
-
if "_symmetry_space_group_name_H-M" in text or stripped.startswith(("data_", "#")):
|
|
169
|
-
return text
|
|
170
179
|
try:
|
|
171
|
-
|
|
172
|
-
except
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
candidates.extend(payload.get("response", []) or [])
|
|
177
|
-
candidates.extend(payload.get("data", []) or [])
|
|
178
|
-
candidates.append(payload)
|
|
179
|
-
for item in candidates:
|
|
180
|
-
if isinstance(item, dict):
|
|
181
|
-
cif = item.get("cif") or item.get("cif_symmetrized") or item.get("symmetrized_cif")
|
|
182
|
-
if cif:
|
|
183
|
-
return cif
|
|
184
|
-
return None
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
def download_mp_symmetrized_cif(material_id, path, api_key=None):
|
|
188
|
-
"""Download the Materials Project CIF (Symmetrized) file for material_id."""
|
|
189
|
-
key = api_key_value(api_key)
|
|
190
|
-
headers = {"Accept": "text/plain", "User-Agent": "pymdkit"}
|
|
191
|
-
if key:
|
|
192
|
-
headers["X-API-KEY"] = key
|
|
193
|
-
quoted = quote(material_id, safe="")
|
|
194
|
-
urls = [
|
|
195
|
-
f"https://api.materialsproject.org/materials/{quoted}/cif?symmetrized=true",
|
|
196
|
-
f"https://api.materialsproject.org/materials/{quoted}/cif",
|
|
197
|
-
f"https://api.materialsproject.org/materials/core/{quoted}/cif?symmetrized=true",
|
|
198
|
-
f"https://api.materialsproject.org/materials/core/{quoted}/cif",
|
|
199
|
-
f"https://materialsproject.org/materials/{quoted}/cif?download=true&symmetrized=true",
|
|
200
|
-
f"https://materialsproject.org/materials/{quoted}/cif?symmetrized=true",
|
|
201
|
-
f"https://www.materialsproject.org/rest/v2/materials/{quoted}/cif",
|
|
202
|
-
]
|
|
203
|
-
errors = []
|
|
204
|
-
for url in urls:
|
|
205
|
-
try:
|
|
206
|
-
req = Request(url, headers=headers)
|
|
207
|
-
with urlopen(req, timeout=60) as response:
|
|
208
|
-
text = response.read().decode("utf-8")
|
|
209
|
-
cif_text = extract_cif_text(text)
|
|
210
|
-
if cif_text:
|
|
211
|
-
Path(path).write_text(cif_text, encoding="utf-8")
|
|
212
|
-
return
|
|
213
|
-
errors.append(f"{url}: response did not contain CIF text")
|
|
214
|
-
except Exception as exc: # noqa: BLE001 - try alternate MP endpoints
|
|
215
|
-
errors.append(f"{url}: {type(exc).__name__}: {exc}")
|
|
216
|
-
raise RuntimeError("Could not download MP CIF (Symmetrized): " + " | ".join(errors))
|
|
180
|
+
structure = get_api_structure(material_id, api_key)
|
|
181
|
+
except Exception as exc: # noqa: BLE001 - keep command useful if MP API changes
|
|
182
|
+
structure = fallback_structure
|
|
183
|
+
print(f" ! {material_id}: MP API structure fetch failed ({type(exc).__name__}); "
|
|
184
|
+
"using thermo structure fallback.")
|
|
217
185
|
|
|
186
|
+
try:
|
|
187
|
+
CifWriter(structure, symprec=0.1, refine_struct=True).write_file(str(path))
|
|
188
|
+
except TypeError:
|
|
189
|
+
CifWriter(structure, symprec=0.1).write_file(str(path))
|
|
218
190
|
|
|
219
191
|
def run(args) -> int:
|
|
220
192
|
|
|
@@ -239,7 +211,7 @@ def run(args) -> int:
|
|
|
239
211
|
mpid = safe_formula(entry_id_text(entry).split(" ")[0])
|
|
240
212
|
formula = safe_formula(entry.composition.reduced_formula)
|
|
241
213
|
path = out_dir / f"{mpid}-{formula}.cif"
|
|
242
|
-
|
|
214
|
+
write_mp_symmetrized_cif(mpid, entry.structure, path, args.mp_api_key)
|
|
243
215
|
print(f"{mpid}\t{entry.composition.reduced_formula}\t-> {path}")
|
|
244
216
|
written += 1
|
|
245
217
|
|
|
@@ -3,7 +3,8 @@ Build a supercell whose cell lengths stay within a maximum (-max-abc).
|
|
|
3
3
|
|
|
4
4
|
For each lattice vector the structure is repeated as many times as possible
|
|
5
5
|
without its length exceeding ``-max-abc`` (in Angstrom):
|
|
6
|
-
|
|
6
|
+
`
|
|
7
|
+
_i = max(1, floor(max_abc / |a_i|))``.
|
|
7
8
|
|
|
8
9
|
Single file: supercell -i example.vasp -o out.vasp -max-abc 20
|
|
9
10
|
Whole folder: supercell -if vasp-opted -of out -max-abc 20
|
|
@@ -15,20 +16,28 @@ GPUMD setup: supercell -if extxyz-vasp-opted -max-abc 24 -individual \\
|
|
|
15
16
|
temperature, each seeded with model.xyz + the shared GPUMD
|
|
16
17
|
input files (target_t in run.in replaced by the temperature).
|
|
17
18
|
With -add-groups, model.xyz is tagged with a GPUMD group index
|
|
18
|
-
by element order (Li->0, Y->1, Cl->2).
|
|
19
|
+
by element order (Li->0, Y->1, Cl->2).
|
|
20
|
+
Single GPUMD: supercell -i example.cif -max-abc 24 -temp 500 600
|
|
21
|
+
-> ./model.xyz plus ./500/ and ./600/ directly, without
|
|
22
|
+
creating a structure-name folder.
|
|
19
23
|
"""
|
|
20
24
|
|
|
21
25
|
import argparse
|
|
22
26
|
import shutil
|
|
23
27
|
from pathlib import Path
|
|
24
28
|
|
|
25
|
-
from ase.io import read, write
|
|
26
|
-
|
|
27
29
|
try:
|
|
28
|
-
from . import _fileio
|
|
30
|
+
from . import _fileio
|
|
29
31
|
except ImportError: # running as a standalone script
|
|
30
32
|
import _fileio
|
|
31
|
-
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def tag_groups(path, elements):
|
|
36
|
+
try:
|
|
37
|
+
from . import add_groups as _add_groups
|
|
38
|
+
except ImportError: # running as a standalone script
|
|
39
|
+
import add_groups as _add_groups
|
|
40
|
+
_add_groups._tag_one(path, path, elements)
|
|
32
41
|
|
|
33
42
|
COMMAND = "supercell"
|
|
34
43
|
HELP = "Build a supercell with cell lengths capped at -max-abc Angstrom."
|
|
@@ -43,6 +52,8 @@ def repetitions_for(atoms, max_abc):
|
|
|
43
52
|
|
|
44
53
|
|
|
45
54
|
def make_supercell(in_path, out_path, max_abc):
|
|
55
|
+
from ase.io import read, write
|
|
56
|
+
|
|
46
57
|
atoms = read(str(in_path))
|
|
47
58
|
reps = repetitions_for(atoms, max_abc)
|
|
48
59
|
sc = atoms.repeat(reps)
|
|
@@ -111,9 +122,18 @@ def add_arguments(parser):
|
|
|
111
122
|
|
|
112
123
|
|
|
113
124
|
def _run_gpumd(args):
|
|
114
|
-
"""GPUMD setup:
|
|
125
|
+
"""GPUMD setup: model.xyz + per-temperature job folders."""
|
|
126
|
+
if args.input:
|
|
127
|
+
model = Path("model.xyz")
|
|
128
|
+
make_supercell(args.input, model, args.max_abc)
|
|
129
|
+
if args.add_groups:
|
|
130
|
+
tag_groups(model, args.add_groups)
|
|
131
|
+
setup_temp_folders(Path("."), args.temp, args.md_input_folder)
|
|
132
|
+
print(f"Done: set up single structure with temperatures {args.temp}.")
|
|
133
|
+
return 0
|
|
134
|
+
|
|
115
135
|
if not args.input_folder:
|
|
116
|
-
raise SystemExit("Error: -temp requires -if/-input-folder.")
|
|
136
|
+
raise SystemExit("Error: -temp requires -i/-input or -if/-input-folder.")
|
|
117
137
|
count = 0
|
|
118
138
|
for f in _fileio.structure_files(args.input_folder):
|
|
119
139
|
folder = Path(_folder_name(f.stem))
|
|
@@ -22,7 +22,8 @@ except ImportError: # running as a standalone script
|
|
|
22
22
|
import _vasp_output
|
|
23
23
|
|
|
24
24
|
COMMAND = "vasp2xyz"
|
|
25
|
-
HELP = "Collect SCF-converged VASP job folders into one extxyz file."
|
|
25
|
+
HELP = "Collect SCF-converged VASP job folders into one extxyz file."
|
|
26
|
+
KBAR_TO_EV_PER_A3 = 0.0006241509125883258
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def folder_sort_key(name):
|
|
@@ -79,6 +80,23 @@ def strip_calculation_results(atoms):
|
|
|
79
80
|
atoms.arrays.pop(key, None)
|
|
80
81
|
|
|
81
82
|
|
|
83
|
+
def vasp_stress_to_ase(stress):
|
|
84
|
+
"""Convert VASP stress in kB to ASE stress in eV/A^3."""
|
|
85
|
+
if stress is None:
|
|
86
|
+
return None
|
|
87
|
+
try:
|
|
88
|
+
import numpy as np
|
|
89
|
+
return -np.asarray(stress, dtype=float) * KBAR_TO_EV_PER_A3
|
|
90
|
+
except ImportError:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
def convert(value):
|
|
94
|
+
if isinstance(value, (list, tuple)):
|
|
95
|
+
return [convert(x) for x in value]
|
|
96
|
+
return -float(value) * KBAR_TO_EV_PER_A3
|
|
97
|
+
|
|
98
|
+
return convert(stress)
|
|
99
|
+
|
|
82
100
|
def atoms_from_output(output):
|
|
83
101
|
from ase.calculators.singlepoint import SinglePointCalculator
|
|
84
102
|
from pymatgen.io.ase import AseAtomsAdaptor
|
|
@@ -92,9 +110,9 @@ def atoms_from_output(output):
|
|
|
92
110
|
if "forces" in step:
|
|
93
111
|
results["forces"] = step["forces"]
|
|
94
112
|
if "stress" in step:
|
|
95
|
-
results["stress"] = step["stress"]
|
|
113
|
+
results["stress"] = vasp_stress_to_ase(step["stress"])
|
|
96
114
|
elif "stresses" in step:
|
|
97
|
-
results["stress"] = step["stresses"]
|
|
115
|
+
results["stress"] = vasp_stress_to_ase(step["stresses"])
|
|
98
116
|
atoms.calc = SinglePointCalculator(atoms, **results)
|
|
99
117
|
return atoms
|
|
100
118
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|