Source code for smact.utils.crystal_space.download_compounds_with_mp_api
"""Utility functions for downloading compounds from the Materials Project."""
from __future__ import annotations
import itertools
import json
import logging
import string
import time
from collections import defaultdict
from pathlib import Path
from emmet.core.summary import SummaryDoc
from mp_api.client import MPRester
from pymatgen.core import Composition
from tqdm import tqdm
logger = logging.getLogger(__name__)
[docs]
def download_mp_data(
mp_api_key: str | None = None,
num_elements: int = 2,
max_stoich: int = 8,
save_dir: str | Path = "data/binary/mp_api",
request_interval: float = 0.1,
) -> None:
"""
Downloads Materials Project data for all possible combinations of `num_elements` elements.
Uses elements with atomic numbers.
When chemical formula is same, the one with lowest energy above hull is saved.
The data is saved to a specified directory.
Args:
mp_api_key (str, optional): the API key for Materials Project.
num_elements (int, optional): the number of elements in each compound to consider.
Defaults to 2.
max_stoich (int, optional): the maximum integer of stoichiometric coefficient
in chemical formula. Defaults to 8.
save_dir (str, optional): the directory to save the downloaded data to.
Defaults to "data/mp_api".
request_interval (float, optional): the time interval between API requests, in seconds.
Defaults to 1.
Returns:
None
"""
# check if MP_API_KEY is set
if mp_api_key is None:
msg = "Please set your MP_API_KEY in the environment variable."
raise ValueError(msg)
# set save directory
save_dir = Path(save_dir)
save_dir.mkdir(parents=True, exist_ok=True)
# make a list for all possible combinartions of formula anonymous
symbols = string.ascii_uppercase
formula_anonymous_list = []
for stoichs in itertools.combinations_with_replacement(range(1, max_stoich + 1), num_elements):
formula_dict = {symbols[i]: stoich for i, stoich in enumerate(stoichs)}
formula_anonymous_list.append(Composition(formula_dict).reduced_formula)
formula_anonymous_list = sorted(set(formula_anonymous_list))
e_hull_dict = defaultdict(lambda: float("inf"))
for formula_anonymous in tqdm(formula_anonymous_list):
logger.info("Downloading data for %s...", formula_anonymous)
# download data from MP
with MPRester(mp_api_key) as mpr:
docs = mpr.materials.summary.search(
formula=formula_anonymous,
fields=[
"formula_pretty",
"material_id",
"formula_anonymous",
"volume",
"density",
"density_atomic",
"energy_per_atom",
"formation_energy_per_atom",
"energy_above_hull",
"is_stable",
"band_gap",
"efermi",
"total_magnetization",
"structure",
],
)
# save data with lowest energy above hull
for doc in docs:
if not isinstance(doc, SummaryDoc):
continue
formula_pretty = doc.formula_pretty
energy_above_hull = doc.energy_above_hull
if (
energy_above_hull is not None
and formula_pretty is not None
and energy_above_hull < e_hull_dict[formula_pretty]
):
e_hull_dict[formula_pretty] = energy_above_hull
with (save_dir / f"{formula_pretty}.json").open("w") as f:
json.dump(doc.model_dump(), f, default=str)
time.sleep(request_interval)