|
9 | 9 |
|
10 | 10 | from __future__ import annotations
|
11 | 11 |
|
| 12 | +import gzip |
12 | 13 | import itertools
|
13 | 14 | import json
|
14 | 15 | import logging
|
|
19 | 20 | from functools import partial
|
20 | 21 | from typing import TYPE_CHECKING, NamedTuple
|
21 | 22 |
|
| 23 | +import numpy as np |
22 | 24 | import orjson
|
23 | 25 | import requests
|
24 | 26 | from monty.json import MontyDecoder
|
25 | 27 |
|
26 |
| -from pymatgen.core import SETTINGS |
| 28 | +from pymatgen.core import SETTINGS, Lattice |
27 | 29 | from pymatgen.core import __version__ as PMG_VERSION
|
28 | 30 | from pymatgen.core.composition import Composition
|
| 31 | +from pymatgen.electronic_structure.bandstructure import Kpoint |
| 32 | +from pymatgen.phonon import CompletePhononDos, PhononBandStructureSymmLine |
29 | 33 | from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
|
30 | 34 |
|
31 | 35 | if TYPE_CHECKING:
|
32 | 36 | from collections.abc import Callable, Sequence
|
| 37 | + from typing import Any |
33 | 38 |
|
34 | 39 | from typing_extensions import Self
|
35 | 40 |
|
@@ -474,31 +479,77 @@ def get_entries_in_chemsys(self, elements: str | list[str], *args, **kwargs):
|
474 | 479 |
|
475 | 480 | return self.get_entries(criteria, *args, **kwargs)
|
476 | 481 |
|
477 |
| - def get_phonon_bandstructure_by_material_id(self, material_id: str): |
| 482 | + def _retrieve_object_from_s3(self, material_id: str, bucket: str, prefix: str, timeout: float = 60) -> Any: |
| 483 | + """ |
| 484 | + Retrieve data from Amazon S3 OpenData the non-canonical way, using requests. |
| 485 | +
|
| 486 | + This should be transitioned to boto3 if long-term support is desired, |
| 487 | + or to expand pymatgen support of, e.g., electronic DOS, bandstructure, etc. |
| 488 | +
|
| 489 | + Args: |
| 490 | + material_id (str): Materials Project material_id |
| 491 | + bucket (str): the Materials Project bucket, either materialsproject-parsed |
| 492 | + or materialsproject-build |
| 493 | + prefix (str) : the prefix of the particular S3 key. |
| 494 | + timeout (float = 60) : timeout in seconds for the requests command. |
| 495 | +
|
| 496 | + Returns: |
| 497 | + json loaded object |
| 498 | + """ |
| 499 | + response = requests.get( |
| 500 | + f"https://s3.us-east-1.amazonaws.com/{bucket}/{prefix}/{material_id}.json.gz", |
| 501 | + timeout=timeout, |
| 502 | + ) |
| 503 | + if response.status_code not in {200, 400}: |
| 504 | + raise MPRestError( |
| 505 | + f"Failed to retrieve data from OpenData with status code {response.status_code}:\n{response.reason}" |
| 506 | + ) |
| 507 | + return orjson.loads(gzip.decompress(response.content)) |
| 508 | + |
| 509 | + def get_phonon_bandstructure_by_material_id(self, material_id: str) -> PhononBandStructureSymmLine: |
478 | 510 | """Get phonon bandstructure by material_id.
|
479 | 511 |
|
| 512 | + Note that this method borrows constructor methods built into |
| 513 | + in the emmet-core model for this data. Calling the `to_pmg` |
| 514 | + method of the emmet-core data model handles this. |
| 515 | +
|
480 | 516 | Args:
|
481 | 517 | material_id (str): Materials Project material_id
|
482 | 518 |
|
483 | 519 | Returns:
|
484 | 520 | PhononBandStructureSymmLine: A phonon band structure.
|
485 | 521 | """
|
486 |
| - prop = "phonon_bandstructure" |
487 |
| - response = self.materials.phonon.search(material_ids=material_id) |
488 |
| - return response[0][prop] |
| 522 | + data = self._retrieve_object_from_s3( |
| 523 | + material_id, bucket="materialsproject-parsed", prefix="ph-bandstructures/dfpt" |
| 524 | + ) |
| 525 | + rlatt = Lattice(data["reciprocal_lattice"]) |
| 526 | + return PhononBandStructureSymmLine( |
| 527 | + [Kpoint(q, lattice=rlatt).frac_coords for q in data["qpoints"]], |
| 528 | + np.array(data["frequencies"]), |
| 529 | + rlatt, |
| 530 | + has_nac=data["has_nac"], |
| 531 | + eigendisplacements=np.array(data["eigendisplacements"]), |
| 532 | + structure=data["structure"], |
| 533 | + labels_dict={k: Kpoint(v, lattice=rlatt).frac_coords for k, v in (data["labels_dict"] or {}).items()}, |
| 534 | + coords_are_cartesian=False, |
| 535 | + ) |
489 | 536 |
|
490 |
| - def get_phonon_dos_by_material_id(self, material_id: str): |
| 537 | + def get_phonon_dos_by_material_id(self, material_id: str) -> CompletePhononDos: |
491 | 538 | """Get phonon density of states by material_id.
|
492 | 539 |
|
| 540 | + Note that this method borrows constructor methods built into |
| 541 | + in the emmet-core model for this data. Calling the `to_pmg` |
| 542 | + method of the emmet-core data model handles this. |
| 543 | +
|
493 | 544 | Args:
|
494 | 545 | material_id (str): Materials Project material_id
|
495 | 546 |
|
496 | 547 | Returns:
|
497 | 548 | CompletePhononDos: A phonon DOS object.
|
498 | 549 | """
|
499 |
| - prop = "phonon_dos" |
500 |
| - response = self.request(f"materials/phonon/?material_ids={material_id}&_fields={prop}") |
501 |
| - return response[0][prop] |
| 550 | + data = self._retrieve_object_from_s3(material_id, bucket="materialsproject-parsed", prefix="ph-dos/dfpt") |
| 551 | + data["pdos"] = data.pop("projected_densities", None) |
| 552 | + return CompletePhononDos.from_dict(data) |
502 | 553 |
|
503 | 554 |
|
504 | 555 | class MPRestError(Exception):
|
|
0 commit comments