diff --git a/emmet-core/emmet/core/thermo.py b/emmet-core/emmet/core/thermo.py index edb557f7b4..298b0d3d92 100644 --- a/emmet-core/emmet/core/thermo.py +++ b/emmet-core/emmet/core/thermo.py @@ -1,14 +1,15 @@ """ Core definition of a Thermo Document """ from datetime import datetime -from typing import ClassVar, Dict, List from enum import Enum +from typing import ClassVar, Dict, List from pydantic import BaseModel, Field +from pymatgen.analysis.phase_diagram import PhaseDiagram, PhaseDiagramError from pymatgen.core.periodic_table import Element from emmet.core.material import PropertyDoc from emmet.core.structure import StructureMetadata -from emmet.stubs import ComputedEntry, Composition +from emmet.stubs import Composition, ComputedEntry class DecompositionProduct(BaseModel): @@ -29,7 +30,7 @@ class DecompositionProduct(BaseModel): ) -class Thermo(PropertyDoc): +class ThermoDoc(PropertyDoc): """ A thermo entry document """ @@ -39,11 +40,11 @@ class Thermo(PropertyDoc): ) uncorrected_energy_per_atom: float = Field( - None, description="The total DFT energy of this material per atom in eV/atom" + ..., description="The total DFT energy of this material per atom in eV/atom" ) energy_per_atom: float = Field( - None, + ..., description="The total corrected DFT energy of this material per atom in eV/atom", ) @@ -54,14 +55,15 @@ class Thermo(PropertyDoc): formation_energy_per_atom: float = Field( None, description="The formation energy per atom in eV/atom" ) - e_above_hull: float = Field( - None, description="The energy above the hull in eV/Atom" + + energy_above_hull: float = Field( + ..., description="The energy above the hull in eV/Atom" ) is_stable: bool = Field( - None, + False, description="Flag for whether this material is on the hull and therefore stable", ) - equillibrium_reaction_energy_per_atmo: float = Field( + equillibrium_reaction_energy_per_atom: float = Field( None, description="The reaction energy of a stable entry from the neighboring equilibrium stable materials in eV." " Also known as the inverse distance to hull.", @@ -73,7 +75,7 @@ class Thermo(PropertyDoc): ) energy_type: str = Field( - None, + ..., description="The type of calculation this energy evaluation comes from. TODO: Convert to enum?", ) @@ -86,3 +88,54 @@ class Thermo(PropertyDoc): description="List of all entries that are valid for this material." " The keys for this dictionary are names of various calculation types", ) + + @classmethod + def from_entries(cls, entries: List[ComputedEntry], sandboxes=None): + + pd = PhaseDiagram(entries) + + docs = [] + + for e in entries: + (decomp, ehull) = pd.get_decomp_and_e_above_hull(e) + + d = { + "material_id": e.entry_id, + "uncorrected_energy_per_atom": e.uncorrected_energy + / e.composition.num_atoms, + "energy_per_atom": e.uncorrected_energy / e.composition.num_atoms, + "formation_energy_per_atom": pd.get_form_energy_per_atom(e), + "energy_above_hull": ehull, + "is_stable": e in pd.stable_entries, + "sandboxes": sandboxes, + } + + if "last_updated" in e.data: + d["last_updated"] = e.data["last_updated"] + + # Store different info if stable vs decomposes + if d["is_stable"]: + d[ + "equillibrium_reaction_energy_per_atom" + ] = pd.get_equilibrium_reaction_energy(e) + else: + d["decomposes_to"] = [ + { + "material_id": de.entry_id, + "formula": de.composition.formula, + "amount": amt, + } + for de, amt in decomp.items() + ] + + d["energy_type"] = e.parameters["run_type"] + + elsyms = sorted(set([el.symbol for el in e.composition.elements])) + d["chemsys"] = "-".join(elsyms) + d["nelements"] = len(elsyms) + d["elements"] = list(elsyms) + d["last_updated"] = e.data["last_updated"] + + docs.append(ThermoDoc(**d)) + + return docs