Skip to content

Commit f617529

Browse files
port get_stability functionality from legacy api + test (#981)
Co-authored-by: esoteric-ephemera <[email protected]>
1 parent 252e682 commit f617529

File tree

2 files changed

+141
-2
lines changed

2 files changed

+141
-2
lines changed

mp_api/client/mprester.py

+60-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from emmet.core.mpid import MPID
1313
from emmet.core.settings import EmmetSettings
1414
from emmet.core.tasks import TaskDoc
15+
from emmet.core.thermo import ThermoType
1516
from emmet.core.vasp.calc_types import CalcType
1617
from monty.json import MontyDecoder
1718
from packaging import version
@@ -61,8 +62,10 @@
6162
from mp_api.client.routes.molecules import MoleculeRester
6263

6364
if TYPE_CHECKING:
64-
from typing import Literal
65+
from typing import Any, Literal
6566

67+
from pymatgen.analysis.phase_diagram import PDEntry
68+
from pymatgen.entries.computed_entries import ComputedEntry
6669

6770
_EMMET_SETTINGS = EmmetSettings()
6871
_MAPI_SETTINGS = MAPIClientSettings()
@@ -1620,3 +1623,59 @@ def _get_cohesive_energy(
16201623
elif normalization == "formula_unit":
16211624
num_form_unit = comp.get_reduced_composition_and_factor()[1]
16221625
return (energy_per_atom * natom - atomic_energy) / num_form_unit
1626+
1627+
def get_stability(
1628+
self,
1629+
entries: ComputedEntry | ComputedStructureEntry | PDEntry,
1630+
thermo_type: str | ThermoType = ThermoType.GGA_GGA_U,
1631+
) -> list[dict[str, Any]] | None:
1632+
chemsys = set()
1633+
for entry in entries:
1634+
chemsys.update(entry.composition.elements)
1635+
chemsys_str = "-".join(sorted(str(ele) for ele in chemsys))
1636+
1637+
thermo_type = (
1638+
ThermoType(thermo_type) if isinstance(thermo_type, str) else thermo_type
1639+
)
1640+
1641+
corrector = None
1642+
if thermo_type == ThermoType.GGA_GGA_U:
1643+
from pymatgen.entries.compatibility import MaterialsProject2020Compatibility
1644+
1645+
corrector = MaterialsProject2020Compatibility()
1646+
1647+
elif thermo_type == ThermoType.GGA_GGA_U_R2SCAN:
1648+
from pymatgen.entries.mixing_scheme import MaterialsProjectDFTMixingScheme
1649+
1650+
corrector = MaterialsProjectDFTMixingScheme(run_type_2="r2SCAN")
1651+
1652+
try:
1653+
pd = self.materials.thermo.get_phase_diagram_from_chemsys(
1654+
chemsys_str, thermo_type=thermo_type
1655+
)
1656+
except OSError:
1657+
pd = None
1658+
1659+
if not pd:
1660+
warnings.warn(
1661+
f"No phase diagram data available for chemical system {chemsys_str} "
1662+
f"and thermo type {thermo_type}."
1663+
)
1664+
return
1665+
1666+
if corrector:
1667+
corrected_entries = corrector.process_entries(entries + pd.all_entries)
1668+
else:
1669+
corrected_entries = [*entries, *pd.all_entries]
1670+
1671+
new_pd = PhaseDiagram(corrected_entries)
1672+
1673+
return [
1674+
{
1675+
"e_above_hull": new_pd.get_e_above_hull(entry),
1676+
"composition": entry.composition.as_dict(),
1677+
"energy": entry.energy,
1678+
"entry_id": getattr(entry, "entry_id", f"user-entry-{idx}"),
1679+
}
1680+
for idx, entry in enumerate(entries)
1681+
]

tests/test_mprester.py

+81-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import numpy as np
77
import pytest
88
from emmet.core.tasks import TaskDoc
9+
from emmet.core.thermo import ThermoType
910
from emmet.core.vasp.calc_types import CalcType
1011
from pymatgen.analysis.phase_diagram import PhaseDiagram
1112
from pymatgen.analysis.pourbaix_diagram import IonEntry, PourbaixDiagram, PourbaixEntry
@@ -18,7 +19,11 @@
1819
BandStructureSymmLine,
1920
)
2021
from pymatgen.electronic_structure.dos import CompleteDos
21-
from pymatgen.entries.compatibility import MaterialsProjectAqueousCompatibility
22+
from pymatgen.entries.compatibility import (
23+
MaterialsProjectAqueousCompatibility,
24+
MaterialsProject2020Compatibility,
25+
)
26+
from pymatgen.entries.mixing_scheme import MaterialsProjectDFTMixingScheme
2227
from pymatgen.entries.computed_entries import ComputedEntry, GibbsComputedStructureEntry
2328
from pymatgen.io.cif import CifParser
2429
from pymatgen.io.vasp import Chgcar
@@ -429,3 +434,78 @@ def test_get_cohesive_energy(self):
429434
assert all(
430435
v == pytest.approx(e_coh["noserial"][k]) for k, v in e_coh["serial"].items()
431436
)
437+
438+
@pytest.mark.parametrize(
439+
"chemsys, thermo_type",
440+
[
441+
[("Fe", "P"), "GGA_GGA+U"],
442+
[("Li", "S"), ThermoType.GGA_GGA_U_R2SCAN],
443+
[("Ni", "Se"), ThermoType.R2SCAN],
444+
[("Ni", "Kr"), "R2SCAN"],
445+
],
446+
)
447+
def test_get_stability(self, chemsys, thermo_type):
448+
"""
449+
This test is adapted from the pymatgen one - the scope is broadened
450+
to include more diverse chemical environments and thermo types which
451+
reflect the scope of the current MP database.
452+
"""
453+
with MPRester() as mpr:
454+
entries = mpr.get_entries_in_chemsys(
455+
chemsys, additional_criteria={"thermo_types": [thermo_type]}
456+
)
457+
458+
no_compound_entries = all(
459+
len(entry.composition.elements) == 1 for entry in entries
460+
)
461+
462+
modified_entries = [
463+
ComputedEntry(
464+
entry.composition,
465+
entry.uncorrected_energy + 0.01,
466+
parameters=entry.parameters,
467+
entry_id=f"mod_{entry.entry_id}",
468+
)
469+
for entry in entries
470+
if entry.composition.reduced_formula in ["Fe2P", "".join(chemsys)]
471+
]
472+
473+
if len(modified_entries) == 0:
474+
# create fake entry to get PD retrieval to fail
475+
modified_entries = [
476+
ComputedEntry(
477+
"".join(chemsys),
478+
np.average([entry.energy for entry in entries]),
479+
entry_id=f"hypothetical",
480+
)
481+
]
482+
483+
if no_compound_entries:
484+
with pytest.warns(UserWarning, match="No phase diagram data available"):
485+
mpr.get_stability(modified_entries, thermo_type=thermo_type)
486+
return
487+
488+
else:
489+
rester_ehulls = mpr.get_stability(
490+
modified_entries, thermo_type=thermo_type
491+
)
492+
493+
all_entries = entries + modified_entries
494+
495+
compat = None
496+
if thermo_type == "GGA_GGA+U":
497+
compat = MaterialsProject2020Compatibility()
498+
elif thermo_type == "GGA_GGA+U_R2SCAN":
499+
compat = MaterialsProjectDFTMixingScheme(run_type_2="r2SCAN")
500+
501+
if compat:
502+
all_entries = compat.process_entries(all_entries)
503+
504+
pd = PhaseDiagram(all_entries)
505+
for entry in all_entries:
506+
if str(entry.entry_id).startswith("mod"):
507+
for dct in rester_ehulls:
508+
if dct["entry_id"] == entry.entry_id:
509+
data = dct
510+
break
511+
assert pd.get_e_above_hull(entry) == pytest.approx(data["e_above_hull"])

0 commit comments

Comments
 (0)