pymdkit 1.2.9__tar.gz → 1.2.11__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.9/src/pymdkit.egg-info → pymdkit-1.2.11}/PKG-INFO +1 -1
- {pymdkit-1.2.9 → pymdkit-1.2.11}/pyproject.toml +1 -1
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/__init__.py +1 -1
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/stable_entry.py +52 -68
- {pymdkit-1.2.9 → pymdkit-1.2.11/src/pymdkit.egg-info}/PKG-INFO +1 -1
- {pymdkit-1.2.9 → pymdkit-1.2.11}/LICENSE +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/README.md +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/setup.cfg +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/__init__.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/_fileio.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/_vasp_output.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/_vaspset.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/add_groups.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/compute_ehull.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/compute_msd_all_groups.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/compute_rmsd.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/electrostatic_energy.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/final_energy.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/gather_contcar.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/nep_rmse.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/perturb.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/stru2xyz.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/submit_vasp.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/substitute.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/supercell.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/symmetrize.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/vasp2xyz.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/vasp_relax.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/vasp_static.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/pymdkit_main.py +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit.egg-info/SOURCES.txt +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit.egg-info/dependency_links.txt +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit.egg-info/entry_points.txt +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit.egg-info/requires.txt +0 -0
- {pymdkit-1.2.9 → pymdkit-1.2.11}/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"
|
|
@@ -99,20 +96,28 @@ def resolve_public_material_ids(entries, api_key=None):
|
|
|
99
96
|
is_stable=True,
|
|
100
97
|
fields=["material_id", "formula_pretty", "structure"],
|
|
101
98
|
)
|
|
99
|
+
candidates = []
|
|
102
100
|
for doc in cache[formula]:
|
|
103
101
|
structure = doc_get(doc, "structure")
|
|
104
102
|
material_id = mpid_text(doc_get(doc, "material_id"))
|
|
105
103
|
if not structure or not is_public_mpid(material_id):
|
|
106
104
|
continue
|
|
107
|
-
if str(structure.composition.reduced_formula)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
105
|
+
if str(structure.composition.reduced_formula) == formula:
|
|
106
|
+
candidates.append((doc, structure, material_id))
|
|
107
|
+
chosen = None
|
|
108
|
+
if len(candidates) == 1:
|
|
109
|
+
chosen = candidates[0][2]
|
|
110
|
+
else:
|
|
111
|
+
for _, structure, material_id in candidates:
|
|
112
|
+
if matcher.fit(entry.structure, structure):
|
|
113
|
+
chosen = material_id
|
|
114
|
+
break
|
|
115
|
+
if chosen:
|
|
116
|
+
data = getattr(entry, "data", None)
|
|
117
|
+
if data is None:
|
|
118
|
+
entry.data = {}
|
|
119
|
+
data = entry.data
|
|
120
|
+
data["material_id"] = chosen
|
|
116
121
|
|
|
117
122
|
|
|
118
123
|
def fetch_stable_entries(elements, api_key=None):
|
|
@@ -144,65 +149,44 @@ def fetch_stable_entries(elements, api_key=None):
|
|
|
144
149
|
return list(by_mpid.values())
|
|
145
150
|
|
|
146
151
|
|
|
147
|
-
def
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
try:
|
|
151
|
-
from pymatgen.ext.matproj import MPRester
|
|
152
|
-
return getattr(MPRester(), "api_key", None)
|
|
153
|
-
except Exception:
|
|
154
|
-
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
|
|
155
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
|
|
156
178
|
|
|
157
|
-
def extract_cif_text(text):
|
|
158
|
-
stripped = text.lstrip()
|
|
159
|
-
if not stripped.startswith(("{", "[")):
|
|
160
|
-
if "_symmetry_space_group_name_H-M" in text or stripped.startswith(("data_", "#")):
|
|
161
|
-
return text
|
|
162
179
|
try:
|
|
163
|
-
|
|
164
|
-
except
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
candidates.extend(payload.get("response", []) or [])
|
|
169
|
-
candidates.extend(payload.get("data", []) or [])
|
|
170
|
-
candidates.append(payload)
|
|
171
|
-
for item in candidates:
|
|
172
|
-
if isinstance(item, dict):
|
|
173
|
-
cif = item.get("cif") or item.get("cif_symmetrized") or item.get("symmetrized_cif")
|
|
174
|
-
if cif:
|
|
175
|
-
return cif
|
|
176
|
-
return None
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def download_mp_symmetrized_cif(material_id, path, api_key=None):
|
|
180
|
-
"""Download the Materials Project CIF (Symmetrized) file for material_id."""
|
|
181
|
-
key = api_key_value(api_key)
|
|
182
|
-
headers = {"Accept": "text/plain", "User-Agent": "pymdkit"}
|
|
183
|
-
if key:
|
|
184
|
-
headers["X-API-KEY"] = key
|
|
185
|
-
quoted = quote(material_id, safe="")
|
|
186
|
-
urls = [
|
|
187
|
-
f"https://materialsproject.org/materials/{quoted}/cif?download=true&symmetrized=true",
|
|
188
|
-
f"https://materialsproject.org/materials/{quoted}/cif?symmetrized=true",
|
|
189
|
-
f"https://www.materialsproject.org/rest/v2/materials/{quoted}/cif",
|
|
190
|
-
]
|
|
191
|
-
errors = []
|
|
192
|
-
for url in urls:
|
|
193
|
-
try:
|
|
194
|
-
req = Request(url, headers=headers)
|
|
195
|
-
with urlopen(req, timeout=60) as response:
|
|
196
|
-
text = response.read().decode("utf-8")
|
|
197
|
-
cif_text = extract_cif_text(text)
|
|
198
|
-
if cif_text:
|
|
199
|
-
Path(path).write_text(cif_text, encoding="utf-8")
|
|
200
|
-
return
|
|
201
|
-
errors.append(f"{url}: response did not contain CIF text")
|
|
202
|
-
except Exception as exc: # noqa: BLE001 - try alternate MP endpoints
|
|
203
|
-
errors.append(f"{url}: {type(exc).__name__}: {exc}")
|
|
204
|
-
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.")
|
|
205
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))
|
|
206
190
|
|
|
207
191
|
def run(args) -> int:
|
|
208
192
|
|
|
@@ -227,7 +211,7 @@ def run(args) -> int:
|
|
|
227
211
|
mpid = safe_formula(entry_id_text(entry).split(" ")[0])
|
|
228
212
|
formula = safe_formula(entry.composition.reduced_formula)
|
|
229
213
|
path = out_dir / f"{mpid}-{formula}.cif"
|
|
230
|
-
|
|
214
|
+
write_mp_symmetrized_cif(mpid, entry.structure, path, args.mp_api_key)
|
|
231
215
|
print(f"{mpid}\t{entry.composition.reduced_formula}\t-> {path}")
|
|
232
216
|
written += 1
|
|
233
217
|
|
|
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
|
|
File without changes
|
|
File without changes
|