1
1
import itertools
2
+ from collections .abc import Sequence
2
3
3
4
import pandas as pd
4
5
from pymatgen .analysis .phase_diagram import Entry , PDEntry
6
+ from pymatgen .core import Composition
7
+ from pymatgen .util .typing import EntryLike
5
8
from tqdm import tqdm
6
9
7
10
from mb_discovery import ROOT
8
11
9
12
10
13
def get_elemental_ref_entries (
11
- entries : list [ Entry ], verbose : bool = False
14
+ entries : Sequence [ EntryLike ], verbose : bool = False
12
15
) -> dict [str , Entry ]:
16
+ """Get the lowest energy entry for each element in a list of entries.
13
17
18
+ Args:
19
+ entries (Sequence[Entry]): pymatgen Entries (PDEntry, ComputedEntry or
20
+ ComputedStructureEntry) to find elemental reference entries for.
21
+ verbose (bool, optional): _description_. Defaults to False.
22
+
23
+ Raises:
24
+ ValueError: If some elements are missing terminal reference entries.
25
+ ValueError: If there are more terminal entries than dimensions. Should never
26
+ happen.
27
+
28
+ Returns:
29
+ dict[str, Entry]: Map from element symbol to its lowest energy entry.
30
+ """
31
+ entries = [PDEntry .from_dict (e ) if isinstance (e , dict ) else e for e in entries ]
14
32
elements = {elems for entry in entries for elems in entry .composition .elements }
15
33
dim = len (elements )
16
34
17
35
if verbose :
18
36
print (f"Sorting { len (entries )} entries with { dim } dimensions..." )
37
+
19
38
entries = sorted (entries , key = lambda e : e .composition .reduced_composition )
20
39
21
40
elemental_ref_entries = {}
22
- if verbose :
23
- print ("Finding elemental reference entries..." , flush = True )
24
- for composition , group in tqdm (
25
- itertools .groupby (entries , key = lambda e : e .composition .reduced_composition )
41
+ for composition , entry_group in tqdm (
42
+ itertools .groupby (entries , key = lambda e : e .composition .reduced_composition ),
43
+ disable = not verbose ,
26
44
):
27
- min_entry = min (group , key = lambda e : e .energy_per_atom )
45
+ min_entry = min (entry_group , key = lambda e : e .energy_per_atom )
28
46
if composition .is_element :
29
47
elem_symb = str (composition .elements [0 ])
30
48
elemental_ref_entries [elem_symb ] = min_entry
@@ -53,14 +71,16 @@ def get_elemental_ref_entries(
53
71
54
72
55
73
def get_e_form_per_atom (
56
- entry : Entry , elemental_ref_entries : dict [str , Entry ] = None
74
+ entry : EntryLike ,
75
+ elemental_ref_entries : dict [str , EntryLike ] = None ,
57
76
) -> float :
58
77
"""Get the formation energy of a composition from a list of entries and elemental
59
78
reference energies.
60
79
61
80
Args:
62
- entry (Entry): pymatgen Entry (PDEntry, ComputedEntry or ComputedStructureEntry)
63
- to compute formation energy of.
81
+ entry: Entry | dict[str, float | str | Composition]: pymatgen Entry (PDEntry,
82
+ ComputedEntry or ComputedStructureEntry) or dict with energy and composition
83
+ keys to compute formation energy of.
64
84
elemental_ref_entries (dict[str, Entry], optional): Must be a complete set of
65
85
terminal (i.e. elemental) reference entries containing the lowest energy
66
86
phase for each element present in entry. Defaults to MP elemental reference
@@ -76,13 +96,25 @@ def get_e_form_per_atom(
76
96
f"Couldn't load { mp_elem_refs_path = } , you must pass "
77
97
f"{ elemental_ref_entries = } explicitly."
78
98
)
79
-
80
99
elemental_ref_entries = mp_elem_reference_entries
81
100
82
- comp = entry .composition
83
- form_energy = entry .uncorrected_energy - sum (
84
- comp [el ] * elemental_ref_entries [str (el )].energy_per_atom
85
- for el in entry .composition .elements
86
- )
101
+ if isinstance (entry , dict ):
102
+ energy = entry ["energy" ]
103
+ comp = Composition (entry ["composition" ]) # is idempotent if already Composition
104
+ elif isinstance (entry , Entry ):
105
+ energy = entry .energy
106
+ comp = entry .composition
107
+ else :
108
+ raise TypeError (
109
+ f"{ entry = } must be Entry (or subclass like ComputedEntry) or dict"
110
+ )
111
+
112
+ refs = {str (el ): elemental_ref_entries [str (el )] for el in comp }
113
+
114
+ for key , ref_entry in refs .items ():
115
+ if isinstance (ref_entry , dict ):
116
+ refs [key ] = PDEntry .from_dict (ref_entry )
117
+
118
+ form_energy = energy - sum (comp [el ] * refs [str (el )].energy_per_atom for el in comp )
87
119
88
- return form_energy / entry . composition .num_atoms
120
+ return form_energy / comp .num_atoms
0 commit comments