From 3acc6aeb7864f518818371452867587476abb4c9 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Mon, 9 Dec 2024 17:58:21 -0800 Subject: [PATCH 01/10] add feature to compute cohesive energies --- mp_api/client/mprester.py | 71 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/mp_api/client/mprester.py b/mp_api/client/mprester.py index 37b2237e..3cc20b57 100644 --- a/mp_api/client/mprester.py +++ b/mp_api/client/mprester.py @@ -62,6 +62,7 @@ if TYPE_CHECKING: from typing import Literal + from pymatgen.core import Composition _EMMET_SETTINGS = EmmetSettings() @@ -1477,3 +1478,73 @@ def query(*args, **kwargs): please see the new documentation. """ ) + + def get_cohesive_energy_by_material_id(self, material_id : MPID | str | list[MPID | str]) -> float: + + if isinstance(material_id, MPID | str): + material_id = [material_id] + + mat_info = self.materials.search( + material_ids=material_id, + fields=["origins","composition","material_id"] + ) + + mpid_to_task_id = {} + for mat_doc in mat_info: + for prop in mat_doc.origins: + if prop.name.lower() in {"energy","structure",}: + mpid_to_task_id[mat_doc.material_id] = prop.task_id + break + task_id_to_mpid = {v : k for k, v in mpid_to_task_id.items()} + + tasks = self.tasks.search( + task_ids = list(task_id_to_mpid), + fields=["run_type","output.structure","output.energy","calcs_reversed","task_id"] + ) + + atomic_energies = self.get_atom_reference_data() + + e_coh_per_atom = {} + for task in tasks: + run_type = str(task.run_type or TaskDoc._get_run_type(task.calcs_reversed)) + if run_type in {"GGA","GGA+U"}: + dfa = "PBE" + elif run_type in {"r2SCAN","r2SCAN+U"}: + dfa = "r2SCAN" + else: + warnings.warn(f"No reference atomic energies for run type {run_type} available (task {task.task_id}, material {task_id_to_mpid[task.task_id]})!") + continue + + structure = task.output.structure or task.calcs_reversed[0].output.structure + energy = task.output.energy or task.calcs_reversed[0].output.energy + + if structure is not None and energy is not None: + e_coh_per_atom[task_id_to_mpid[task.task_id]] = self._get_cohesive_energy_per_atom( + structure.composition, energy, atomic_energies[dfa] + ) + + return e_coh_per_atom + + @lru_cache + def get_atom_reference_data(self, funcs : list[str] = ["PBE","r2SCAN"]) -> dict[str,dict[str, float]]: + _atomic_energies = self.contribs.query_contributions( + query = {"project": "isolated_atom_energies"}, + fields = ["formula", *[f"data.{dfa}.energy" for dfa in funcs]] + ).get("data") + + atomic_energies = {dfa : {} for dfa in {"PBE", "r2SCAN"}} + for entry in _atomic_energies: + for dfa in atomic_energies: + conv_fac = 1. + if entry["data"][dfa]["energy"]["unit"] == "meV": + conv_fac = 1e-3 + atomic_energies[dfa][entry["formula"]] = entry["data"][dfa]["energy"]["value"] * conv_fac + return atomic_energies + + @staticmethod + def _get_cohesive_energy_per_atom(composition: Composition, energy: float, atomic_energies : dict[str,float]) -> float: + comp = composition.remove_charges() + atomic_energy = sum( + coeff*atomic_energies[str(element)] for element, coeff in comp.items() + ) + return (energy - atomic_energy)/sum(comp.values()) \ No newline at end of file From 57df0437dee530815c0332a519e696aafcc3f014 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Mon, 9 Dec 2024 17:59:51 -0800 Subject: [PATCH 02/10] ruff --- mp_api/client/mprester.py | 68 ++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/mp_api/client/mprester.py b/mp_api/client/mprester.py index 3cc20b57..2decd3ec 100644 --- a/mp_api/client/mprester.py +++ b/mp_api/client/mprester.py @@ -62,6 +62,7 @@ if TYPE_CHECKING: from typing import Literal + from pymatgen.core import Composition @@ -1479,27 +1480,36 @@ def query(*args, **kwargs): """ ) - def get_cohesive_energy_by_material_id(self, material_id : MPID | str | list[MPID | str]) -> float: - + def get_cohesive_energy_by_material_id( + self, material_id: MPID | str | list[MPID | str] + ) -> float: if isinstance(material_id, MPID | str): material_id = [material_id] mat_info = self.materials.search( - material_ids=material_id, - fields=["origins","composition","material_id"] + material_ids=material_id, fields=["origins", "composition", "material_id"] ) mpid_to_task_id = {} for mat_doc in mat_info: for prop in mat_doc.origins: - if prop.name.lower() in {"energy","structure",}: + if prop.name.lower() in { + "energy", + "structure", + }: mpid_to_task_id[mat_doc.material_id] = prop.task_id break - task_id_to_mpid = {v : k for k, v in mpid_to_task_id.items()} + task_id_to_mpid = {v: k for k, v in mpid_to_task_id.items()} tasks = self.tasks.search( - task_ids = list(task_id_to_mpid), - fields=["run_type","output.structure","output.energy","calcs_reversed","task_id"] + task_ids=list(task_id_to_mpid), + fields=[ + "run_type", + "output.structure", + "output.energy", + "calcs_reversed", + "task_id", + ], ) atomic_energies = self.get_atom_reference_data() @@ -1507,44 +1517,56 @@ def get_cohesive_energy_by_material_id(self, material_id : MPID | str | list[MPI e_coh_per_atom = {} for task in tasks: run_type = str(task.run_type or TaskDoc._get_run_type(task.calcs_reversed)) - if run_type in {"GGA","GGA+U"}: + if run_type in {"GGA", "GGA+U"}: dfa = "PBE" - elif run_type in {"r2SCAN","r2SCAN+U"}: + elif run_type in {"r2SCAN", "r2SCAN+U"}: dfa = "r2SCAN" else: - warnings.warn(f"No reference atomic energies for run type {run_type} available (task {task.task_id}, material {task_id_to_mpid[task.task_id]})!") + warnings.warn( + f"No reference atomic energies for run type {run_type} available (task {task.task_id}, material {task_id_to_mpid[task.task_id]})!" + ) continue structure = task.output.structure or task.calcs_reversed[0].output.structure energy = task.output.energy or task.calcs_reversed[0].output.energy if structure is not None and energy is not None: - e_coh_per_atom[task_id_to_mpid[task.task_id]] = self._get_cohesive_energy_per_atom( + e_coh_per_atom[ + task_id_to_mpid[task.task_id] + ] = self._get_cohesive_energy_per_atom( structure.composition, energy, atomic_energies[dfa] ) return e_coh_per_atom - + @lru_cache - def get_atom_reference_data(self, funcs : list[str] = ["PBE","r2SCAN"]) -> dict[str,dict[str, float]]: + def get_atom_reference_data( + self, funcs: list[str] = None + ) -> dict[str, dict[str, float]]: + if funcs is None: + funcs = ["PBE", "r2SCAN"] _atomic_energies = self.contribs.query_contributions( - query = {"project": "isolated_atom_energies"}, - fields = ["formula", *[f"data.{dfa}.energy" for dfa in funcs]] + query={"project": "isolated_atom_energies"}, + fields=["formula", *[f"data.{dfa}.energy" for dfa in funcs]], ).get("data") - atomic_energies = {dfa : {} for dfa in {"PBE", "r2SCAN"}} + atomic_energies = {dfa: {} for dfa in {"PBE", "r2SCAN"}} for entry in _atomic_energies: for dfa in atomic_energies: - conv_fac = 1. + conv_fac = 1.0 if entry["data"][dfa]["energy"]["unit"] == "meV": conv_fac = 1e-3 - atomic_energies[dfa][entry["formula"]] = entry["data"][dfa]["energy"]["value"] * conv_fac + atomic_energies[dfa][entry["formula"]] = ( + entry["data"][dfa]["energy"]["value"] * conv_fac + ) return atomic_energies - + @staticmethod - def _get_cohesive_energy_per_atom(composition: Composition, energy: float, atomic_energies : dict[str,float]) -> float: + def _get_cohesive_energy_per_atom( + composition: Composition, energy: float, atomic_energies: dict[str, float] + ) -> float: comp = composition.remove_charges() atomic_energy = sum( - coeff*atomic_energies[str(element)] for element, coeff in comp.items() + coeff * atomic_energies[str(element)] for element, coeff in comp.items() ) - return (energy - atomic_energy)/sum(comp.values()) \ No newline at end of file + return (energy - atomic_energy) / sum(comp.values()) From b008105b311898d7902d0ba9acef1a1773e23889 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Wed, 11 Dec 2024 09:51:21 -0800 Subject: [PATCH 03/10] Streamline cohesive energy func, test with monty_decode=False, add docstr to added methods --- mp_api/client/mprester.py | 159 ++++++++++++++++++++++++-------------- 1 file changed, 100 insertions(+), 59 deletions(-) diff --git a/mp_api/client/mprester.py b/mp_api/client/mprester.py index 2decd3ec..d0c82327 100644 --- a/mp_api/client/mprester.py +++ b/mp_api/client/mprester.py @@ -17,7 +17,7 @@ from packaging import version from pymatgen.analysis.phase_diagram import PhaseDiagram from pymatgen.analysis.pourbaix_diagram import IonEntry -from pymatgen.core import SETTINGS, Element, Structure +from pymatgen.core import SETTINGS, Element, Structure, Composition from pymatgen.core.ion import Ion from pymatgen.entries.computed_entries import ComputedStructureEntry from pymatgen.io.vasp import Chgcar @@ -63,8 +63,6 @@ if TYPE_CHECKING: from typing import Literal - from pymatgen.core import Composition - _EMMET_SETTINGS = EmmetSettings() _MAPI_SETTINGS = MAPIClientSettings() @@ -1480,93 +1478,136 @@ def query(*args, **kwargs): """ ) - def get_cohesive_energy_by_material_id( + def get_cohesive_energy_per_atom_by_material_id( self, material_id: MPID | str | list[MPID | str] - ) -> float: + ) -> float | dict[str, float]: + """" + Obtain the cohesive energy, in eV/atom, of the structures corresponding to one or many MPIDs. + + Args: + material_id (MPID or str or [MPID | str]) : a single MPID or a list of many to compute + cohesive energies for. + Returns: + (float or dict[str,float]) : If only MPID was specified, returns the cohesive energy + in eV/atom for that material. If multiple MPIDs were specified, the cohesive + energies (in eV/atom) for each material are returned in a dict indexed by MPID. + """ + if isinstance(material_id, MPID | str): material_id = [material_id] - mat_info = self.materials.search( - material_ids=material_id, fields=["origins", "composition", "material_id"] - ) + entry_preference = { + k: i for i, k in enumerate(["GGA","GGA+U","SCAN","R2SCAN"]) + } + run_type_to_dfa = { + "GGA": "PBE", + "GGA+U": "PBE", + "R2SCAN": "r2SCAN" + } - mpid_to_task_id = {} - for mat_doc in mat_info: - for prop in mat_doc.origins: - if prop.name.lower() in { - "energy", - "structure", - }: - mpid_to_task_id[mat_doc.material_id] = prop.task_id - break - task_id_to_mpid = {v: k for k, v in mpid_to_task_id.items()} - - tasks = self.tasks.search( - task_ids=list(task_id_to_mpid), - fields=[ - "run_type", - "output.structure", - "output.energy", - "calcs_reversed", - "task_id", - ], - ) + energies = {mp_id: {} for mp_id in material_id} + entries = self.get_entry_by_material_id(material_id,compatible_only=False) + for entry in entries: + + # Ensure that this works with monty_decode = False and True + if isinstance(entry, dict): + entry["uncorrected_energy_per_atom"] = entry["energy"]/sum(entry["composition"].values()) + else: + entry = { + "data": entry.data, + "uncorrected_energy_per_atom": entry.uncorrected_energy_per_atom, + "composition": entry.composition + } + + mp_id = entry["data"]["material_id"] + if (run_type := entry["data"]["run_type"]) not in energies[mp_id]: + energies[mp_id][run_type] = { + "total_energy_per_atom": float("inf"), + "composition": None, + } + + if entry["uncorrected_energy_per_atom"] < energies[mp_id][run_type]["total_energy_per_atom"]: + energies[mp_id][run_type] = { + "total_energy_per_atom": entry["uncorrected_energy_per_atom"], + "composition": entry["composition"], + } atomic_energies = self.get_atom_reference_data() e_coh_per_atom = {} - for task in tasks: - run_type = str(task.run_type or TaskDoc._get_run_type(task.calcs_reversed)) - if run_type in {"GGA", "GGA+U"}: - dfa = "PBE" - elif run_type in {"r2SCAN", "r2SCAN+U"}: - dfa = "r2SCAN" - else: - warnings.warn( - f"No reference atomic energies for run type {run_type} available (task {task.task_id}, material {task_id_to_mpid[task.task_id]})!" - ) + for mp_id, entries in energies.items(): + if len(entries) == 0: continue + prefered_func = sorted(list(entries), key = lambda k : entry_preference[k])[-1] + e_coh_per_atom[str(mp_id)] = self._get_cohesive_energy_per_atom( + entries[prefered_func]["composition"], + entries[prefered_func]["total_energy_per_atom"], + atomic_energies[ + run_type_to_dfa.get(prefered_func,prefered_func) + ] + ) - structure = task.output.structure or task.calcs_reversed[0].output.structure - energy = task.output.energy or task.calcs_reversed[0].output.energy - - if structure is not None and energy is not None: - e_coh_per_atom[ - task_id_to_mpid[task.task_id] - ] = self._get_cohesive_energy_per_atom( - structure.composition, energy, atomic_energies[dfa] - ) - + if len(material_id) == 1: + return e_coh_per_atom[material_id[0]] return e_coh_per_atom @lru_cache def get_atom_reference_data( self, funcs: list[str] = None ) -> dict[str, dict[str, float]]: - if funcs is None: - funcs = ["PBE", "r2SCAN"] + """ + Retrieve energies of isolated neutral atoms from MPContribs. + + Args: + funcs ([str] or None) : list of functionals to retrieve data for. + Defaults to all available functionals ("PBE", "SCAN", "r2SCAN") + when set to None. + + Returns: + (dict[str, dict[str, float]]) : dict containing isolated atom energies, + indexed first by the functionals in funcs, and second by the atom. + """ + + conv_fac = { + "eV": 1., + "meV": 1e-3, + "µeV": 1e-6 + } + + default_funcs = {"PBE", "SCAN", "r2SCAN"} + funcs = funcs or default_funcs _atomic_energies = self.contribs.query_contributions( query={"project": "isolated_atom_energies"}, fields=["formula", *[f"data.{dfa}.energy" for dfa in funcs]], ).get("data") - atomic_energies = {dfa: {} for dfa in {"PBE", "r2SCAN"}} + atomic_energies = {dfa: {} for dfa in funcs} for entry in _atomic_energies: for dfa in atomic_energies: - conv_fac = 1.0 - if entry["data"][dfa]["energy"]["unit"] == "meV": - conv_fac = 1e-3 atomic_energies[dfa][entry["formula"]] = ( - entry["data"][dfa]["energy"]["value"] * conv_fac + entry["data"][dfa]["energy"]["value"] + * conv_fac.get(entry["data"][dfa]["energy"]["unit"]) ) return atomic_energies @staticmethod def _get_cohesive_energy_per_atom( - composition: Composition, energy: float, atomic_energies: dict[str, float] + composition: Composition | dict, energy_per_atom: float, atomic_energies: dict[str, float] ) -> float: - comp = composition.remove_charges() + """ + Obtain the cohesive energy per atom of a given composition and energy. + + Args: + composition (Composition or dict) : the composition of the structure. + energy_per_atom (float) : the energy per atom of the structure. + atomic_energies (dict[str,float]) : a dict containing reference total energies + of neutral atoms. + + Returns: + (float) : the cohesive energy per atom. + """ + comp = Composition(composition).remove_charges() atomic_energy = sum( coeff * atomic_energies[str(element)] for element, coeff in comp.items() ) - return (energy - atomic_energy) / sum(comp.values()) + return energy_per_atom - atomic_energy / sum(comp.values()) From 298fd68be63b511f9b543424976eb4bfcee6cbcf Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Wed, 11 Dec 2024 09:52:00 -0800 Subject: [PATCH 04/10] precommit --- mp_api/client/mprester.py | 65 +++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/mp_api/client/mprester.py b/mp_api/client/mprester.py index d0c82327..bdf19abb 100644 --- a/mp_api/client/mprester.py +++ b/mp_api/client/mprester.py @@ -17,7 +17,7 @@ from packaging import version from pymatgen.analysis.phase_diagram import PhaseDiagram from pymatgen.analysis.pourbaix_diagram import IonEntry -from pymatgen.core import SETTINGS, Element, Structure, Composition +from pymatgen.core import SETTINGS, Composition, Element, Structure from pymatgen.core.ion import Ion from pymatgen.entries.computed_entries import ComputedStructureEntry from pymatgen.io.vasp import Chgcar @@ -1481,42 +1481,38 @@ def query(*args, **kwargs): def get_cohesive_energy_per_atom_by_material_id( self, material_id: MPID | str | list[MPID | str] ) -> float | dict[str, float]: - """" - Obtain the cohesive energy, in eV/atom, of the structures corresponding to one or many MPIDs. + """Obtain the cohesive energy, in eV/atom, of the structures corresponding to one or many MPIDs. Args: material_id (MPID or str or [MPID | str]) : a single MPID or a list of many to compute cohesive energies for. + Returns: (float or dict[str,float]) : If only MPID was specified, returns the cohesive energy in eV/atom for that material. If multiple MPIDs were specified, the cohesive energies (in eV/atom) for each material are returned in a dict indexed by MPID. """ - if isinstance(material_id, MPID | str): material_id = [material_id] entry_preference = { - k: i for i, k in enumerate(["GGA","GGA+U","SCAN","R2SCAN"]) - } - run_type_to_dfa = { - "GGA": "PBE", - "GGA+U": "PBE", - "R2SCAN": "r2SCAN" + k: i for i, k in enumerate(["GGA", "GGA+U", "SCAN", "R2SCAN"]) } + run_type_to_dfa = {"GGA": "PBE", "GGA+U": "PBE", "R2SCAN": "r2SCAN"} energies = {mp_id: {} for mp_id in material_id} - entries = self.get_entry_by_material_id(material_id,compatible_only=False) + entries = self.get_entry_by_material_id(material_id, compatible_only=False) for entry in entries: - # Ensure that this works with monty_decode = False and True if isinstance(entry, dict): - entry["uncorrected_energy_per_atom"] = entry["energy"]/sum(entry["composition"].values()) + entry["uncorrected_energy_per_atom"] = entry["energy"] / sum( + entry["composition"].values() + ) else: entry = { "data": entry.data, "uncorrected_energy_per_atom": entry.uncorrected_energy_per_atom, - "composition": entry.composition + "composition": entry.composition, } mp_id = entry["data"]["material_id"] @@ -1526,7 +1522,10 @@ def get_cohesive_energy_per_atom_by_material_id( "composition": None, } - if entry["uncorrected_energy_per_atom"] < energies[mp_id][run_type]["total_energy_per_atom"]: + if ( + entry["uncorrected_energy_per_atom"] + < energies[mp_id][run_type]["total_energy_per_atom"] + ): energies[mp_id][run_type] = { "total_energy_per_atom": entry["uncorrected_energy_per_atom"], "composition": entry["composition"], @@ -1538,13 +1537,11 @@ def get_cohesive_energy_per_atom_by_material_id( for mp_id, entries in energies.items(): if len(entries) == 0: continue - prefered_func = sorted(list(entries), key = lambda k : entry_preference[k])[-1] + prefered_func = sorted(list(entries), key=lambda k: entry_preference[k])[-1] e_coh_per_atom[str(mp_id)] = self._get_cohesive_energy_per_atom( entries[prefered_func]["composition"], entries[prefered_func]["total_energy_per_atom"], - atomic_energies[ - run_type_to_dfa.get(prefered_func,prefered_func) - ] + atomic_energies[run_type_to_dfa.get(prefered_func, prefered_func)], ) if len(material_id) == 1: @@ -1555,25 +1552,19 @@ def get_cohesive_energy_per_atom_by_material_id( def get_atom_reference_data( self, funcs: list[str] = None ) -> dict[str, dict[str, float]]: - """ - Retrieve energies of isolated neutral atoms from MPContribs. + """Retrieve energies of isolated neutral atoms from MPContribs. Args: funcs ([str] or None) : list of functionals to retrieve data for. Defaults to all available functionals ("PBE", "SCAN", "r2SCAN") when set to None. - + Returns: (dict[str, dict[str, float]]) : dict containing isolated atom energies, indexed first by the functionals in funcs, and second by the atom. """ - - conv_fac = { - "eV": 1., - "meV": 1e-3, - "µeV": 1e-6 - } - + conv_fac = {"eV": 1.0, "meV": 1e-3, "µeV": 1e-6} + default_funcs = {"PBE", "SCAN", "r2SCAN"} funcs = funcs or default_funcs _atomic_energies = self.contribs.query_contributions( @@ -1584,25 +1575,25 @@ def get_atom_reference_data( atomic_energies = {dfa: {} for dfa in funcs} for entry in _atomic_energies: for dfa in atomic_energies: - atomic_energies[dfa][entry["formula"]] = ( - entry["data"][dfa]["energy"]["value"] - * conv_fac.get(entry["data"][dfa]["energy"]["unit"]) - ) + atomic_energies[dfa][entry["formula"]] = entry["data"][dfa]["energy"][ + "value" + ] * conv_fac.get(entry["data"][dfa]["energy"]["unit"]) return atomic_energies @staticmethod def _get_cohesive_energy_per_atom( - composition: Composition | dict, energy_per_atom: float, atomic_energies: dict[str, float] + composition: Composition | dict, + energy_per_atom: float, + atomic_energies: dict[str, float], ) -> float: - """ - Obtain the cohesive energy per atom of a given composition and energy. + """Obtain the cohesive energy per atom of a given composition and energy. Args: composition (Composition or dict) : the composition of the structure. energy_per_atom (float) : the energy per atom of the structure. atomic_energies (dict[str,float]) : a dict containing reference total energies of neutral atoms. - + Returns: (float) : the cohesive energy per atom. """ From e0ff5e8d72eddefca1ba5d89f36da18033971639 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Wed, 11 Dec 2024 10:11:00 -0800 Subject: [PATCH 05/10] add tests --- mp_api/client/mprester.py | 6 ++--- tests/test_mprester.py | 54 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/mp_api/client/mprester.py b/mp_api/client/mprester.py index bdf19abb..9175a800 100644 --- a/mp_api/client/mprester.py +++ b/mp_api/client/mprester.py @@ -1478,7 +1478,7 @@ def query(*args, **kwargs): """ ) - def get_cohesive_energy_per_atom_by_material_id( + def get_e_coh_per_atom_by_material_id( self, material_id: MPID | str | list[MPID | str] ) -> float | dict[str, float]: """Obtain the cohesive energy, in eV/atom, of the structures corresponding to one or many MPIDs. @@ -1550,7 +1550,7 @@ def get_cohesive_energy_per_atom_by_material_id( @lru_cache def get_atom_reference_data( - self, funcs: list[str] = None + self, funcs: tuple[str] = ("PBE", "SCAN", "r2SCAN",) ) -> dict[str, dict[str, float]]: """Retrieve energies of isolated neutral atoms from MPContribs. @@ -1565,8 +1565,6 @@ def get_atom_reference_data( """ conv_fac = {"eV": 1.0, "meV": 1e-3, "µeV": 1e-6} - default_funcs = {"PBE", "SCAN", "r2SCAN"} - funcs = funcs or default_funcs _atomic_energies = self.contribs.query_contributions( query={"project": "isolated_atom_energies"}, fields=["formula", *[f"data.{dfa}.energy" for dfa in funcs]], diff --git a/tests/test_mprester.py b/tests/test_mprester.py index 9d856fbb..6a5aa965 100644 --- a/tests/test_mprester.py +++ b/tests/test_mprester.py @@ -371,3 +371,57 @@ def test_invalid_api_key(self, monkeypatch): monkeypatch.setenv("MP_API_KEY", "INVALID") with pytest.raises(ValueError, match="Keys for the new API are 32 characters"): MPRester().get_structure_by_material_id("mp-149") + + def test_get_cohesive_energy_per_atom_utility(self): + + composition = {"H": 5, "V": 2,"P": 3,} + toten_per_atom = -2.e3 + atomic_energies = {"H": -13.6, "V": -7.2, "P": -0.1} + + by_hand_e_coh = toten_per_atom - sum( + atomic_energies[k] * v for k, v in composition.items() + )/ sum(composition.values()) + + assert MPRester._get_cohesive_energy_per_atom(composition,toten_per_atom,atomic_energies) == pytest.approx(by_hand_e_coh) + + def test_get_atom_references(self,mpr): + ae = mpr.get_atom_reference_data(funcs=("PBE",)) + assert list(ae) == ["PBE"] + assert len(ae["PBE"]) == 89 + assert all(isinstance(v,float) for v in ae["PBE"].values()) + + ae = mpr.get_atom_reference_data() + assert set(ae) == {"PBE","r2SCAN","SCAN"} + assert all( + len(entries) == 89 for entries in ae.values() + ) + assert all(isinstance(v,float) for entries in ae.values() for v in entries.values()) + + def test_get_cohesive_energy(self): + + ref_e_coh = { + "mp-123": -4.029208982500002, + "mp-149": -4.669184594999999, + "mp-4163": -6.351402620416668, + "mp-19017": -4.933409960714286 + } + mpids = ["mp-123","mp-149","mp-4163","mp-19017"] + e_coh = {} + for monty_decode in (True, False): + with MPRester(use_document_model=monty_decode,monty_decode=monty_decode) as _mpr: + single_e_coh = _mpr.get_e_coh_per_atom_by_material_id("mp-123") + assert isinstance(single_e_coh,float) + assert single_e_coh == pytest.approx(ref_e_coh["mp-123"]) + + _e_coh = _mpr.get_e_coh_per_atom_by_material_id(mpids) + e_coh["serial" if monty_decode else "noserial"] = _e_coh.copy() + + # Ensure energies match reference data + assert all( + v == pytest.approx(ref_e_coh[k]) for k, v in _e_coh.items() + ) + + # Ensure energies are the same regardless of serialization + assert all( + v == pytest.approx(e_coh["noserial"][k]) for k, v in e_coh["serial"].items() + ) \ No newline at end of file From c91f6aa77c68522e58ee1ad17870119d6d7eae39 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Wed, 11 Dec 2024 10:11:15 -0800 Subject: [PATCH 06/10] precommit --- mp_api/client/mprester.py | 7 ++++++- tests/test_mprester.py | 42 +++++++++++++++++++++------------------ 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/mp_api/client/mprester.py b/mp_api/client/mprester.py index 9175a800..9bedd1c6 100644 --- a/mp_api/client/mprester.py +++ b/mp_api/client/mprester.py @@ -1550,7 +1550,12 @@ def get_e_coh_per_atom_by_material_id( @lru_cache def get_atom_reference_data( - self, funcs: tuple[str] = ("PBE", "SCAN", "r2SCAN",) + self, + funcs: tuple[str] = ( + "PBE", + "SCAN", + "r2SCAN", + ), ) -> dict[str, dict[str, float]]: """Retrieve energies of isolated neutral atoms from MPContribs. diff --git a/tests/test_mprester.py b/tests/test_mprester.py index 6a5aa965..8e2e004d 100644 --- a/tests/test_mprester.py +++ b/tests/test_mprester.py @@ -373,55 +373,59 @@ def test_invalid_api_key(self, monkeypatch): MPRester().get_structure_by_material_id("mp-149") def test_get_cohesive_energy_per_atom_utility(self): - - composition = {"H": 5, "V": 2,"P": 3,} - toten_per_atom = -2.e3 + composition = { + "H": 5, + "V": 2, + "P": 3, + } + toten_per_atom = -2.0e3 atomic_energies = {"H": -13.6, "V": -7.2, "P": -0.1} by_hand_e_coh = toten_per_atom - sum( atomic_energies[k] * v for k, v in composition.items() - )/ sum(composition.values()) + ) / sum(composition.values()) - assert MPRester._get_cohesive_energy_per_atom(composition,toten_per_atom,atomic_energies) == pytest.approx(by_hand_e_coh) + assert MPRester._get_cohesive_energy_per_atom( + composition, toten_per_atom, atomic_energies + ) == pytest.approx(by_hand_e_coh) - def test_get_atom_references(self,mpr): + def test_get_atom_references(self, mpr): ae = mpr.get_atom_reference_data(funcs=("PBE",)) assert list(ae) == ["PBE"] assert len(ae["PBE"]) == 89 - assert all(isinstance(v,float) for v in ae["PBE"].values()) + assert all(isinstance(v, float) for v in ae["PBE"].values()) ae = mpr.get_atom_reference_data() - assert set(ae) == {"PBE","r2SCAN","SCAN"} + assert set(ae) == {"PBE", "r2SCAN", "SCAN"} + assert all(len(entries) == 89 for entries in ae.values()) assert all( - len(entries) == 89 for entries in ae.values() + isinstance(v, float) for entries in ae.values() for v in entries.values() ) - assert all(isinstance(v,float) for entries in ae.values() for v in entries.values()) def test_get_cohesive_energy(self): - ref_e_coh = { "mp-123": -4.029208982500002, "mp-149": -4.669184594999999, "mp-4163": -6.351402620416668, - "mp-19017": -4.933409960714286 + "mp-19017": -4.933409960714286, } - mpids = ["mp-123","mp-149","mp-4163","mp-19017"] + mpids = ["mp-123", "mp-149", "mp-4163", "mp-19017"] e_coh = {} for monty_decode in (True, False): - with MPRester(use_document_model=monty_decode,monty_decode=monty_decode) as _mpr: + with MPRester( + use_document_model=monty_decode, monty_decode=monty_decode + ) as _mpr: single_e_coh = _mpr.get_e_coh_per_atom_by_material_id("mp-123") - assert isinstance(single_e_coh,float) + assert isinstance(single_e_coh, float) assert single_e_coh == pytest.approx(ref_e_coh["mp-123"]) _e_coh = _mpr.get_e_coh_per_atom_by_material_id(mpids) e_coh["serial" if monty_decode else "noserial"] = _e_coh.copy() # Ensure energies match reference data - assert all( - v == pytest.approx(ref_e_coh[k]) for k, v in _e_coh.items() - ) + assert all(v == pytest.approx(ref_e_coh[k]) for k, v in _e_coh.items()) # Ensure energies are the same regardless of serialization assert all( v == pytest.approx(e_coh["noserial"][k]) for k, v in e_coh["serial"].items() - ) \ No newline at end of file + ) From 14b2054045cc50ac3f2d339635686b16258cfe43 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Fri, 13 Dec 2024 14:42:00 -0800 Subject: [PATCH 07/10] add option to normalize cohesive energy by formula unit, as in legacy api --- mp_api/client/mprester.py | 34 +++++++++++++++++++++++++--------- tests/test_mprester.py | 37 +++++++++++++++++++++++-------------- 2 files changed, 48 insertions(+), 23 deletions(-) diff --git a/mp_api/client/mprester.py b/mp_api/client/mprester.py index 9bedd1c6..d2edba7b 100644 --- a/mp_api/client/mprester.py +++ b/mp_api/client/mprester.py @@ -1478,14 +1478,19 @@ def query(*args, **kwargs): """ ) - def get_e_coh_per_atom_by_material_id( - self, material_id: MPID | str | list[MPID | str] + def get_cohesive_energy( + self, material_id: MPID | str | list[MPID | str], + normalization : Literal["atom","formula_unit"] = "atom" ) -> float | dict[str, float]: - """Obtain the cohesive energy, in eV/atom, of the structures corresponding to one or many MPIDs. + """Obtain the cohesive energy of the structure(s) corresponding to one or many MPIDs. Args: material_id (MPID or str or [MPID | str]) : a single MPID or a list of many to compute cohesive energies for. + normalization (str = "atom" (default) or "formula_unit") + Whether to normalize the cohesive energy by the number of atoms (default) + or by the number of formula units. + Note that the current default is inconsistent with the legacy API. Returns: (float or dict[str,float]) : If only MPID was specified, returns the cohesive energy @@ -1496,9 +1501,9 @@ def get_e_coh_per_atom_by_material_id( material_id = [material_id] entry_preference = { - k: i for i, k in enumerate(["GGA", "GGA+U", "SCAN", "R2SCAN"]) + k: i for i, k in enumerate(["GGA", "GGA_U", "SCAN", "R2SCAN"]) } - run_type_to_dfa = {"GGA": "PBE", "GGA+U": "PBE", "R2SCAN": "r2SCAN"} + run_type_to_dfa = {"GGA": "PBE", "GGA_U": "PBE", "R2SCAN": "r2SCAN"} energies = {mp_id: {} for mp_id in material_id} entries = self.get_entry_by_material_id(material_id, compatible_only=False) @@ -1538,10 +1543,11 @@ def get_e_coh_per_atom_by_material_id( if len(entries) == 0: continue prefered_func = sorted(list(entries), key=lambda k: entry_preference[k])[-1] - e_coh_per_atom[str(mp_id)] = self._get_cohesive_energy_per_atom( + e_coh_per_atom[str(mp_id)] = self._get_cohesive_energy( entries[prefered_func]["composition"], entries[prefered_func]["total_energy_per_atom"], atomic_energies[run_type_to_dfa.get(prefered_func, prefered_func)], + normalization = normalization, ) if len(material_id) == 1: @@ -1584,18 +1590,22 @@ def get_atom_reference_data( return atomic_energies @staticmethod - def _get_cohesive_energy_per_atom( + def _get_cohesive_energy( composition: Composition | dict, energy_per_atom: float, atomic_energies: dict[str, float], + normalization : Literal["atom","formula_unit"] = "atom" ) -> float: - """Obtain the cohesive energy per atom of a given composition and energy. + """Obtain the cohesive energy of a given composition and energy. Args: composition (Composition or dict) : the composition of the structure. energy_per_atom (float) : the energy per atom of the structure. atomic_energies (dict[str,float]) : a dict containing reference total energies of neutral atoms. + normalization (str = "atom" (default) or "formula_unit") + Whether to normalize the cohesive energy by the number of atoms (default) + or by the number of formula units. Returns: (float) : the cohesive energy per atom. @@ -1604,4 +1614,10 @@ def _get_cohesive_energy_per_atom( atomic_energy = sum( coeff * atomic_energies[str(element)] for element, coeff in comp.items() ) - return energy_per_atom - atomic_energy / sum(comp.values()) + + natom = sum(comp.values()) + if normalization == "atom": + return energy_per_atom - atomic_energy / natom + elif normalization == "formula_unit": + num_form_unit = comp.get_reduced_composition_and_factor()[1] + return (energy_per_atom*natom - atomic_energy) / num_form_unit \ No newline at end of file diff --git a/tests/test_mprester.py b/tests/test_mprester.py index 8e2e004d..3326971d 100644 --- a/tests/test_mprester.py +++ b/tests/test_mprester.py @@ -385,7 +385,7 @@ def test_get_cohesive_energy_per_atom_utility(self): atomic_energies[k] * v for k, v in composition.items() ) / sum(composition.values()) - assert MPRester._get_cohesive_energy_per_atom( + assert MPRester._get_cohesive_energy( composition, toten_per_atom, atomic_energies ) == pytest.approx(by_hand_e_coh) @@ -404,26 +404,35 @@ def test_get_atom_references(self, mpr): def test_get_cohesive_energy(self): ref_e_coh = { - "mp-123": -4.029208982500002, - "mp-149": -4.669184594999999, - "mp-4163": -6.351402620416668, - "mp-19017": -4.933409960714286, - } - mpids = ["mp-123", "mp-149", "mp-4163", "mp-19017"] + "atom": { + "mp-123": -4.029208982500002, + "mp-149": -4.669184594999999, + "mp-4163": -6.351402620416668, + "mp-19017": -4.933409960714286, + }, + "formula_unit": { + "mp-123": -4.029208982500002, + "mp-149": -4.669184594999999, + "mp-4163": -76.21683144500001, + "mp-19017": -34.533869725 + } + } e_coh = {} for monty_decode in (True, False): with MPRester( use_document_model=monty_decode, monty_decode=monty_decode ) as _mpr: - single_e_coh = _mpr.get_e_coh_per_atom_by_material_id("mp-123") - assert isinstance(single_e_coh, float) - assert single_e_coh == pytest.approx(ref_e_coh["mp-123"]) + for norm, refs in ref_e_coh.items(): + single_e_coh = _mpr.get_cohesive_energy("mp-123",normalization=norm) + assert isinstance(single_e_coh, float) + assert single_e_coh == pytest.approx(refs["mp-123"]) - _e_coh = _mpr.get_e_coh_per_atom_by_material_id(mpids) - e_coh["serial" if monty_decode else "noserial"] = _e_coh.copy() + _e_coh = _mpr.get_cohesive_energy(list(refs),normalization=norm) + if norm == "atom": + e_coh["serial" if monty_decode else "noserial"] = _e_coh.copy() - # Ensure energies match reference data - assert all(v == pytest.approx(ref_e_coh[k]) for k, v in _e_coh.items()) + # Ensure energies match reference data + assert all(v == pytest.approx(refs[k]) for k, v in _e_coh.items()) # Ensure energies are the same regardless of serialization assert all( From 4d0bebe93cc42eef68cd20b3f0a2388da947e3cf Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Fri, 13 Dec 2024 14:44:22 -0800 Subject: [PATCH 08/10] ruff ruff --- mp_api/client/mprester.py | 15 ++++++++------- tests/test_mprester.py | 12 +++++++----- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/mp_api/client/mprester.py b/mp_api/client/mprester.py index d2edba7b..0d6153f4 100644 --- a/mp_api/client/mprester.py +++ b/mp_api/client/mprester.py @@ -1479,15 +1479,16 @@ def query(*args, **kwargs): ) def get_cohesive_energy( - self, material_id: MPID | str | list[MPID | str], - normalization : Literal["atom","formula_unit"] = "atom" + self, + material_id: MPID | str | list[MPID | str], + normalization: Literal["atom", "formula_unit"] = "atom", ) -> float | dict[str, float]: """Obtain the cohesive energy of the structure(s) corresponding to one or many MPIDs. Args: material_id (MPID or str or [MPID | str]) : a single MPID or a list of many to compute cohesive energies for. - normalization (str = "atom" (default) or "formula_unit") + normalization (str = "atom" (default) or "formula_unit") : Whether to normalize the cohesive energy by the number of atoms (default) or by the number of formula units. Note that the current default is inconsistent with the legacy API. @@ -1547,7 +1548,7 @@ def get_cohesive_energy( entries[prefered_func]["composition"], entries[prefered_func]["total_energy_per_atom"], atomic_energies[run_type_to_dfa.get(prefered_func, prefered_func)], - normalization = normalization, + normalization=normalization, ) if len(material_id) == 1: @@ -1594,7 +1595,7 @@ def _get_cohesive_energy( composition: Composition | dict, energy_per_atom: float, atomic_energies: dict[str, float], - normalization : Literal["atom","formula_unit"] = "atom" + normalization: Literal["atom", "formula_unit"] = "atom", ) -> float: """Obtain the cohesive energy of a given composition and energy. @@ -1603,7 +1604,7 @@ def _get_cohesive_energy( energy_per_atom (float) : the energy per atom of the structure. atomic_energies (dict[str,float]) : a dict containing reference total energies of neutral atoms. - normalization (str = "atom" (default) or "formula_unit") + normalization (str = "atom" (default) or "formula_unit") : Whether to normalize the cohesive energy by the number of atoms (default) or by the number of formula units. @@ -1620,4 +1621,4 @@ def _get_cohesive_energy( return energy_per_atom - atomic_energy / natom elif normalization == "formula_unit": num_form_unit = comp.get_reduced_composition_and_factor()[1] - return (energy_per_atom*natom - atomic_energy) / num_form_unit \ No newline at end of file + return (energy_per_atom * natom - atomic_energy) / num_form_unit diff --git a/tests/test_mprester.py b/tests/test_mprester.py index 3326971d..b045a8d8 100644 --- a/tests/test_mprester.py +++ b/tests/test_mprester.py @@ -414,20 +414,22 @@ def test_get_cohesive_energy(self): "mp-123": -4.029208982500002, "mp-149": -4.669184594999999, "mp-4163": -76.21683144500001, - "mp-19017": -34.533869725 - } - } + "mp-19017": -34.533869725, + }, + } e_coh = {} for monty_decode in (True, False): with MPRester( use_document_model=monty_decode, monty_decode=monty_decode ) as _mpr: for norm, refs in ref_e_coh.items(): - single_e_coh = _mpr.get_cohesive_energy("mp-123",normalization=norm) + single_e_coh = _mpr.get_cohesive_energy( + "mp-123", normalization=norm + ) assert isinstance(single_e_coh, float) assert single_e_coh == pytest.approx(refs["mp-123"]) - _e_coh = _mpr.get_cohesive_energy(list(refs),normalization=norm) + _e_coh = _mpr.get_cohesive_energy(list(refs), normalization=norm) if norm == "atom": e_coh["serial" if monty_decode else "noserial"] = _e_coh.copy() From ecd4aee34554b41965e01c623936b799f2a2879b Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Fri, 13 Dec 2024 17:04:44 -0800 Subject: [PATCH 09/10] review comments --- mp_api/client/mprester.py | 49 ++++++++++++++++++++------------------- tests/test_mprester.py | 5 ---- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/mp_api/client/mprester.py b/mp_api/client/mprester.py index 0d6153f4..e52595fb 100644 --- a/mp_api/client/mprester.py +++ b/mp_api/client/mprester.py @@ -1480,37 +1480,39 @@ def query(*args, **kwargs): def get_cohesive_energy( self, - material_id: MPID | str | list[MPID | str], + material_ids: list[MPID | str], normalization: Literal["atom", "formula_unit"] = "atom", ) -> float | dict[str, float]: - """Obtain the cohesive energy of the structure(s) corresponding to one or many MPIDs. + """Obtain the cohesive energy of the structure(s) corresponding to multiple MPIDs. Args: - material_id (MPID or str or [MPID | str]) : a single MPID or a list of many to compute - cohesive energies for. + material_id ([MPID | str]) : List of MPIDs to compute cohesive energies. normalization (str = "atom" (default) or "formula_unit") : Whether to normalize the cohesive energy by the number of atoms (default) or by the number of formula units. Note that the current default is inconsistent with the legacy API. Returns: - (float or dict[str,float]) : If only MPID was specified, returns the cohesive energy - in eV/atom for that material. If multiple MPIDs were specified, the cohesive - energies (in eV/atom) for each material are returned in a dict indexed by MPID. + (dict[str,float]) : The cohesive energies (in eV/atom or eV/formula unit) for + each material, indexed by MPID. """ - if isinstance(material_id, MPID | str): - material_id = [material_id] entry_preference = { k: i for i, k in enumerate(["GGA", "GGA_U", "SCAN", "R2SCAN"]) } run_type_to_dfa = {"GGA": "PBE", "GGA_U": "PBE", "R2SCAN": "r2SCAN"} - energies = {mp_id: {} for mp_id in material_id} - entries = self.get_entry_by_material_id(material_id, compatible_only=False) + energies = {mp_id: {} for mp_id in material_ids} + entries = self.get_entries( + material_ids, + compatible_only=False, + inc_structure=True, + property_data=None, + conventional_unit_cell=False, + ) for entry in entries: # Ensure that this works with monty_decode = False and True - if isinstance(entry, dict): + if not self.monty_decode: entry["uncorrected_energy_per_atom"] = entry["energy"] / sum( entry["composition"].values() ) @@ -1528,6 +1530,7 @@ def get_cohesive_energy( "composition": None, } + # Obtain lowest total energy/atom within a given run type if ( entry["uncorrected_energy_per_atom"] < energies[mp_id][run_type]["total_energy_per_atom"] @@ -1541,8 +1544,10 @@ def get_cohesive_energy( e_coh_per_atom = {} for mp_id, entries in energies.items(): - if len(entries) == 0: + if not entries: + e_coh_per_atom[str(mp_id)] = None continue + # take entry from most reliable and available functional prefered_func = sorted(list(entries), key=lambda k: entry_preference[k])[-1] e_coh_per_atom[str(mp_id)] = self._get_cohesive_energy( entries[prefered_func]["composition"], @@ -1550,9 +1555,6 @@ def get_cohesive_energy( atomic_energies[run_type_to_dfa.get(prefered_func, prefered_func)], normalization=normalization, ) - - if len(material_id) == 1: - return e_coh_per_atom[material_id[0]] return e_coh_per_atom @lru_cache @@ -1575,20 +1577,19 @@ def get_atom_reference_data( (dict[str, dict[str, float]]) : dict containing isolated atom energies, indexed first by the functionals in funcs, and second by the atom. """ - conv_fac = {"eV": 1.0, "meV": 1e-3, "µeV": 1e-6} _atomic_energies = self.contribs.query_contributions( query={"project": "isolated_atom_energies"}, fields=["formula", *[f"data.{dfa}.energy" for dfa in funcs]], ).get("data") - atomic_energies = {dfa: {} for dfa in funcs} - for entry in _atomic_energies: - for dfa in atomic_energies: - atomic_energies[dfa][entry["formula"]] = entry["data"][dfa]["energy"][ - "value" - ] * conv_fac.get(entry["data"][dfa]["energy"]["unit"]) - return atomic_energies + return { + dfa : { + entry["formula"] : entry["data"][dfa]["energy"]["value"] + for entry in _atomic_energies + } + for dfa in funcs + } @staticmethod def _get_cohesive_energy( diff --git a/tests/test_mprester.py b/tests/test_mprester.py index b045a8d8..24ba8a46 100644 --- a/tests/test_mprester.py +++ b/tests/test_mprester.py @@ -423,11 +423,6 @@ def test_get_cohesive_energy(self): use_document_model=monty_decode, monty_decode=monty_decode ) as _mpr: for norm, refs in ref_e_coh.items(): - single_e_coh = _mpr.get_cohesive_energy( - "mp-123", normalization=norm - ) - assert isinstance(single_e_coh, float) - assert single_e_coh == pytest.approx(refs["mp-123"]) _e_coh = _mpr.get_cohesive_energy(list(refs), normalization=norm) if norm == "atom": From c5a0417abaffb4c391a0ab0447b2e587674badd1 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Fri, 13 Dec 2024 17:05:15 -0800 Subject: [PATCH 10/10] ruff / docstr typo --- mp_api/client/mprester.py | 10 ++++------ tests/test_mprester.py | 1 - 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/mp_api/client/mprester.py b/mp_api/client/mprester.py index e52595fb..2c3244b6 100644 --- a/mp_api/client/mprester.py +++ b/mp_api/client/mprester.py @@ -1486,17 +1486,16 @@ def get_cohesive_energy( """Obtain the cohesive energy of the structure(s) corresponding to multiple MPIDs. Args: - material_id ([MPID | str]) : List of MPIDs to compute cohesive energies. + material_ids ([MPID | str]) : List of MPIDs to compute cohesive energies. normalization (str = "atom" (default) or "formula_unit") : Whether to normalize the cohesive energy by the number of atoms (default) or by the number of formula units. Note that the current default is inconsistent with the legacy API. Returns: - (dict[str,float]) : The cohesive energies (in eV/atom or eV/formula unit) for + (dict[str,float]) : The cohesive energies (in eV/atom or eV/formula unit) for each material, indexed by MPID. """ - entry_preference = { k: i for i, k in enumerate(["GGA", "GGA_U", "SCAN", "R2SCAN"]) } @@ -1577,15 +1576,14 @@ def get_atom_reference_data( (dict[str, dict[str, float]]) : dict containing isolated atom energies, indexed first by the functionals in funcs, and second by the atom. """ - _atomic_energies = self.contribs.query_contributions( query={"project": "isolated_atom_energies"}, fields=["formula", *[f"data.{dfa}.energy" for dfa in funcs]], ).get("data") return { - dfa : { - entry["formula"] : entry["data"][dfa]["energy"]["value"] + dfa: { + entry["formula"]: entry["data"][dfa]["energy"]["value"] for entry in _atomic_energies } for dfa in funcs diff --git a/tests/test_mprester.py b/tests/test_mprester.py index 24ba8a46..f397f9f6 100644 --- a/tests/test_mprester.py +++ b/tests/test_mprester.py @@ -423,7 +423,6 @@ def test_get_cohesive_energy(self): use_document_model=monty_decode, monty_decode=monty_decode ) as _mpr: for norm, refs in ref_e_coh.items(): - _e_coh = _mpr.get_cohesive_energy(list(refs), normalization=norm) if norm == "atom": e_coh["serial" if monty_decode else "noserial"] = _e_coh.copy()