diff --git a/emmet-core/emmet/core/electrode.py b/emmet-core/emmet/core/electrode.py new file mode 100644 index 0000000000..b99dd4c469 --- /dev/null +++ b/emmet-core/emmet/core/electrode.py @@ -0,0 +1,178 @@ +from datetime import datetime +from typing import Dict, List + +from monty.json import MontyDecoder +from pydantic import BaseModel, Field, validator +from pymatgen import Structure +from pymatgen.apps.battery.battery_abc import AbstractElectrode +from pymatgen.apps.battery.insertion_battery import InsertionElectrode +from pymatgen.core.periodic_table import ElementBase +from pymatgen.entries.computed_entries import ComputedEntry + + +class WorkingIon(ElementBase): + Li = "Li" + Ca = "Ca" + Mg = "Mg" + + +class VoltagePairDoc(BaseModel): + """ + Data for individual voltage steps. + Note: Each voltage step is represented as a sub_electrode (ConversionElectrode/InsertionElectrode) + object to gain access to some basic statistics about the voltage step + """ + + max_delta_volume: str = Field( + None, + description="Volume changes in % for a particular voltage step using: " + "max(charge, discharge) / min(charge, discharge) - 1", + ) + + average_voltage: float = Field( + None, description="The average voltage in V for a particular voltage step.", + ) + + capacity_grav: float = Field(None, description="Gravimetric capacity in mAh/g.") + + capacity_vol: float = Field(None, description="Volumetric capacity in mAh/cc.") + + energy_grav: float = Field( + None, description="Gravimetric energy (Specific energy) in Wh/kg." + ) + + energy_vol: float = Field( + None, description="Volumetric energy (Energy Density) in Wh/l." + ) + + fracA_charge: float = Field( + None, description="Atomic fraction of the working ion in the charged state." + ) + fracA_discharge: float = Field( + None, description="Atomic fraction of the working ion in the discharged state." + ) + + @classmethod + def from_voltage_pair_from_sub_electrode( + cls, sub_electrode: AbstractElectrode, **kwargs + ): + """ + Convert A pymatgen electrode object to a document + """ + return cls(**sub_electrode.get_summary_dict(), **kwargs) + + +class InsertionVoltagePairDoc(VoltagePairDoc): + """ + Features specific to insertion electrode + """ + + stability_charge: float = Field( + None, description="The energy above hull of the charged material." + ) + + stability_discharge: float = Field( + None, description="The energy above hull of the discharged material." + ) + + +class InsertionElectrodeDoc(InsertionVoltagePairDoc): + """ + Insertion electrode + """ + + task_id: str = Field(None, description="The id for this battery document.") + + framework_formula: str = Field( + None, description="The id for this battery document." + ) + + host_structure: Structure = Field( + None, description="Host structure (structure without the working ion)", + ) + + adj_pairs: List[InsertionVoltagePairDoc] = Field( + None, description="Returns all the Voltage Steps", + ) + + working_ion: WorkingIon = Field( + None, description="The working ion as an Element object", + ) + + num_steps: float = Field( + None, + description="The number of distinct voltage steps in from fully charge to " + "discharge based on the stable intermediate states", + ) + + max_voltage_step: float = Field( + None, description="Maximum absolute difference in adjacent voltage steps" + ) + + last_updated: datetime = Field( + None, + description="Timestamp for the most recent calculation for this Material document", + ) + + # Make sure that the datetime field is properly formatted + @validator("last_updated", pre=True) + def last_updated_dict_ok(cls, v): + return MontyDecoder().process_decoded(v) + + @classmethod + def from_entries( + cls, + grouped_entries: List[ComputedEntry], + working_ion_entry: ComputedEntry, + task_id: str, + host_structure: Structure, + ): + ie = InsertionElectrode.from_entries( + entries=grouped_entries, working_ion_entry=working_ion_entry + ) + ie.get_summary_dict() + d = ie.get_summary_dict() + d["num_steps"] = d.pop("nsteps", None) + return cls(task_id=task_id, host_structure=host_structure, **d) + + +# class ConversionVoltagePairDoc(VoltagePairDoc): +# """ +# Features specific to conversion electrode +# """ +# +# reactions: Dict = Field( +# None, description="The reaction the characterizes that particular voltage step." +# ) +# +# +# class ConversionElectrode(ConversionVoltagePairDoc): +# task_id: str = Field(None, description="The id for this battery document.") +# +# adj_pairs: List[ConversionVoltagePairDoc] = Field( +# None, description="Returns all the adjacent Voltage Steps", +# ) +# +# working_ion: WorkingIon = Field( +# None, description="The working ion as an Element object", +# ) +# +# num_steps: float = Field( +# None, +# description="The number of distinct voltage steps in from fully charge to " +# "discharge based on the stable intermediate states", +# ) +# +# max_voltage_step: float = Field( +# None, description="Maximum absolute difference in adjacent voltage steps" +# ) +# +# last_updated: datetime = Field( +# None, +# description="Timestamp for the most recent calculation for this Material document", +# ) +# +# # Make sure that the datetime field is properly formatted +# @validator("last_updated", pre=True) +# def last_updated_dict_ok(cls, v): +# return MontyDecoder().process_decoded(v)