from __future__ import annotations
import json
import numpy as np
from collections import defaultdict
from monty.json import MontyDecoder
from emmet.core.phonon import PhononBSDOSDoc, PhononBS, PhononDOS
from mp_api.client.core import BaseRester, MPRestError
from mp_api.client.core.utils import validate_ids
[docs]
class PhononRester(BaseRester[PhononBSDOSDoc]):
suffix = "materials/phonon"
document_model = PhononBSDOSDoc # type: ignore
primary_key = "material_id"
[docs]
def search(
self,
material_ids: str | list[str] | None = None,
phonon_method: str | None = None,
num_chunks: int | None = None,
chunk_size: int = 1000,
all_fields: bool = True,
fields: list[str] | None = None,
) -> list[PhononBSDOSDoc] | list[dict]:
"""Query phonon docs using a variety of search criteria.
Arguments:
material_ids (str, List[str]): A single Material ID string or list of strings
(e.g., mp-149, [mp-149, mp-13]).
phonon_method (str): phonon method to search (dfpt, phonopy, pheasy)
num_chunks (int): Maximum number of chunks of data to yield. None will yield all possible.
chunk_size (int): Number of data entries per chunk.
all_fields (bool): Whether to return all fields in the document. Defaults to True.
fields (List[str]): List of fields in PhononBSDOSDoc to return data for.
Default is material_id, last_updated, and formula_pretty if all_fields is False.
Returns:
([PhononBSDOSDoc], [dict]) List of phonon documents or dictionaries.
"""
query_params = defaultdict(dict) # type: dict
if material_ids:
if isinstance(material_ids, str):
material_ids = [material_ids]
query_params["material_ids"] = ",".join(validate_ids(material_ids))
if phonon_method and phonon_method in {"dfpt", "phonopy", "pheasy"}:
query_params["phonon_method"] = phonon_method
query_params = {
entry: query_params[entry]
for entry in query_params
if query_params[entry] is not None
}
return super()._search(
num_chunks=num_chunks,
chunk_size=chunk_size,
all_fields=all_fields,
fields=fields,
**query_params,
)
[docs]
def get_bandstructure_from_material_id(self, material_id: str, phonon_method: str):
"""Get the phonon band structure pymatgen object associated with a given material ID and phonon method.
Arguments:
material_id (str): Material ID for the phonon band structure calculation
phonon_method (str): phonon method, i.e. pheasy or dfpt
Returns:
bandstructure (PhononBS): PhononBS object
"""
decoder = MontyDecoder().decode if self.monty_decode else json.loads
result = self._query_open_data(
bucket="materialsproject-parsed",
key=f"ph-bandstructures/{phonon_method}/{material_id}.json.gz",
decoder=decoder,
)[0]
if not result or not result[0]:
raise MPRestError("No object found")
if self.use_document_model:
return PhononBS(**result[0])
return result[0]
[docs]
def get_dos_from_material_id(self, material_id: str, phonon_method: str):
"""Get the phonon dos pymatgen object associated with a given material ID and phonon method.
Arguments:
material_id (str): Material ID for the phonon dos calculation
phonon_method (str): phonon method, i.e. pheasy or dfpt
Returns:
dos (PhononDOS): PhononDOS object
"""
decoder = MontyDecoder().decode if self.monty_decode else json.loads
result = self._query_open_data(
bucket="materialsproject-parsed",
key=f"ph-dos/{phonon_method}/{material_id}.json.gz",
decoder=decoder,
)[0]
if not result or not result[0]:
raise MPRestError("No object found")
if self.use_document_model:
return PhononDOS(**result[0])
return result[0]
[docs]
def get_forceconstants_from_material_id(self, material_id: str):
"""Get the force constants associated with a given material ID.
Arguments:
material_id (str): Material ID for the force constants calculation
Returns:
force constants (list[list[Matrix3D]]): PhononDOS object
"""
decoder = MontyDecoder().decode if self.monty_decode else json.loads
result = self._query_open_data(
bucket="materialsproject-parsed",
key=f"ph-force-constants/{material_id}.json.gz",
decoder=decoder,
)[0]
if not result or not result[0]:
raise MPRestError("No object found")
return result[0]
[docs]
def compute_thermo_quantities(self, material_id: str, phonon_method: str):
"""Compute thermodynamical quantities for given material ID and phonon_method.
Arguments:
material_id (str): Material ID to calculate quantities for
phonon_method (str): phonon method, i.e. pheasy or dfpt
Returns:
quantities (dict): thermodynamical quantities
"""
use_document_model = self.use_document_model
self.use_document_model = False
docs = self.search(material_ids=material_id, phonon_method=phonon_method)
if not docs or not docs[0]:
raise MPRestError("No phonon document found")
self.use_document_model = True
docs[0]["phonon_dos"] = self.get_dos_from_material_id(
material_id, phonon_method
)
doc = PhononBSDOSDoc(**docs[0])
self.use_document_model = use_document_model
return doc.compute_thermo_quantities(np.linspace(0, 800, 100))