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.
Files changed (35) hide show
  1. {pymdkit-1.2.9/src/pymdkit.egg-info → pymdkit-1.2.11}/PKG-INFO +1 -1
  2. {pymdkit-1.2.9 → pymdkit-1.2.11}/pyproject.toml +1 -1
  3. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/__init__.py +1 -1
  4. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/stable_entry.py +52 -68
  5. {pymdkit-1.2.9 → pymdkit-1.2.11/src/pymdkit.egg-info}/PKG-INFO +1 -1
  6. {pymdkit-1.2.9 → pymdkit-1.2.11}/LICENSE +0 -0
  7. {pymdkit-1.2.9 → pymdkit-1.2.11}/README.md +0 -0
  8. {pymdkit-1.2.9 → pymdkit-1.2.11}/setup.cfg +0 -0
  9. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/__init__.py +0 -0
  10. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/_fileio.py +0 -0
  11. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/_vasp_output.py +0 -0
  12. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/_vaspset.py +0 -0
  13. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/add_groups.py +0 -0
  14. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/compute_ehull.py +0 -0
  15. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/compute_msd_all_groups.py +0 -0
  16. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/compute_rmsd.py +0 -0
  17. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/electrostatic_energy.py +0 -0
  18. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/final_energy.py +0 -0
  19. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/gather_contcar.py +0 -0
  20. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/nep_rmse.py +0 -0
  21. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/perturb.py +0 -0
  22. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/stru2xyz.py +0 -0
  23. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/submit_vasp.py +0 -0
  24. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/substitute.py +0 -0
  25. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/supercell.py +0 -0
  26. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/symmetrize.py +0 -0
  27. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/vasp2xyz.py +0 -0
  28. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/vasp_relax.py +0 -0
  29. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/commands/vasp_static.py +0 -0
  30. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit/pymdkit_main.py +0 -0
  31. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit.egg-info/SOURCES.txt +0 -0
  32. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit.egg-info/dependency_links.txt +0 -0
  33. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit.egg-info/entry_points.txt +0 -0
  34. {pymdkit-1.2.9 → pymdkit-1.2.11}/src/pymdkit.egg-info/requires.txt +0 -0
  35. {pymdkit-1.2.9 → pymdkit-1.2.11}/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.2.9
3
+ Version: 1.2.11
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pymdkit"
7
- version = "1.2.9"
7
+ version = "1.2.11"
8
8
  description = "A unified command-line toolkit for atomistic / MD structure workflows."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,3 +1,3 @@
1
1
  """pymdkit -- a unified CLI for atomistic / MD structure workflows."""
2
2
 
3
- __version__ = "1.2.9"
3
+ __version__ = "1.2.11"
@@ -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) != formula:
108
- continue
109
- if matcher.fit(entry.structure, structure):
110
- data = getattr(entry, "data", None)
111
- if data is None:
112
- entry.data = {}
113
- data = entry.data
114
- data["material_id"] = material_id
115
- break
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 api_key_value(api_key=None):
148
- if api_key:
149
- return api_key
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
- payload = json.loads(text)
164
- except json.JSONDecodeError:
165
- return None
166
- candidates = []
167
- if isinstance(payload, dict):
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
- download_mp_symmetrized_cif(mpid, path, args.mp_api_key)
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pymdkit
3
- Version: 1.2.9
3
+ Version: 1.2.11
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
File without changes
File without changes
File without changes