Skip to content

Commit

Permalink
Element/Species: order full_electron_structure by energy (#3944)
Browse files Browse the repository at this point in the history
  • Loading branch information
rkingsbury committed Jul 23, 2024
1 parent 27d90b7 commit 09cf748
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 15 deletions.
2 changes: 1 addition & 1 deletion src/pymatgen/analysis/magnetism/jahnteller.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ def _get_number_of_d_electrons(species: Species) -> float:

# taken from get_crystal_field_spin
elec = species.element.full_electronic_structure
if len(elec) < 4 or elec[-1][1] != "s" or elec[-2][1] != "d":
if len(elec) < 4 or elec[-2][1] != "s" or elec[-1][1] != "d":
raise AttributeError(f"Invalid element {species.symbol} for crystal field calculation.")
n_electrons = int(elec[-1][2] + elec[-2][2] - species.oxi_state) # type: ignore[operator]
if n_electrons < 0 or n_electrons > 10:
Expand Down
50 changes: 44 additions & 6 deletions src/pymatgen/core/periodic_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,28 @@

_pt_row_sizes = (2, 8, 8, 18, 18, 32, 32)

_madelung = [
(1, "s"),
(2, "s"),
(2, "p"),
(3, "s"),
(3, "p"),
(4, "s"),
(3, "d"),
(4, "p"),
(5, "s"),
(4, "d"),
(5, "p"),
(6, "s"),
(4, "f"),
(5, "d"),
(6, "p"),
(7, "s"),
(5, "f"),
(6, "d"),
(7, "p"),
]


@functools.total_ordering
@unique
Expand Down Expand Up @@ -422,11 +444,12 @@ def icsd_oxidation_states(self) -> tuple[int, ...]:
@property
def full_electronic_structure(self) -> list[tuple[int, str, int]]:
"""Full electronic structure as list of tuples, in order of increasing
principal (n) and angular momentum (l) quantum numbers.
energy level (according to the Madelung rule). Therefore, the final
element in the list gives the electronic structure of the valence shell.
For example, the electronic structure for Fe is represented as:
[(1, "s", 2), (2, "s", 2), (2, "p", 6), (3, "s", 2), (3, "p", 6),
(3, "d", 6), (4, "s", 2)].
(4, "s", 2), (3, "d", 6)].
References:
Kramida, A., Ralchenko, Yu., Reader, J., and NIST ASD Team (2023). NIST
Expand All @@ -445,7 +468,13 @@ def parse_orbital(orb_str):
if data[0][0] == "[":
sym = data[0].replace("[", "").replace("]", "")
data = list(Element(sym).full_electronic_structure) + data[1:]
return data
# sort the final electronic structure by increasing energy level
return sorted(data, key=lambda x: _madelung.index((x[0], x[1])))

@property
def n_electrons(self) -> int:
"""Total number of electrons in the Element."""
return sum([t[-1] for t in self.full_electronic_structure])

@property
def valence(self) -> tuple[int | np.nan, int]:
Expand Down Expand Up @@ -1117,7 +1146,8 @@ def electronic_structure(self) -> str:
@property
def full_electronic_structure(self) -> list[tuple[int, str, int]]:
"""Full electronic structure as list of tuples, in order of increasing
principal (n) and angular momentum (l) quantum numbers.
energy level (according to the Madelung rule). Therefore, the final
element in the list gives the electronic structure of the valence shell.
For example, the electronic structure for Fe+2 is represented as:
[(1, "s", 2), (2, "s", 2), (2, "p", 6), (3, "s", 2), (3, "p", 6),
Expand All @@ -1140,7 +1170,15 @@ def parse_orbital(orb_str):
if data[0][0] == "[":
sym = data[0].replace("[", "").replace("]", "")
data = list(Element(sym).full_electronic_structure) + data[1:]
return data
# sort the final electronic structure by increasing energy level
return sorted(data, key=lambda x: _madelung.index((x[0], x[1])))

# NOTE - copied exactly from Element. Refactoring / inheritance may improve
# robustness
@property
def n_electrons(self) -> int:
"""Total number of electrons in the Species."""
return sum([t[-1] for t in self.full_electronic_structure])

# NOTE - copied exactly from Element. Refactoring / inheritance may improve
# robustness
Expand Down Expand Up @@ -1319,7 +1357,7 @@ def get_crystal_field_spin(
raise ValueError("Invalid coordination or spin config")

elec = self.element.full_electronic_structure
if len(elec) < 4 or elec[-1][1] != "s" or elec[-2][1] != "d":
if len(elec) < 4 or elec[-2][1] != "s" or elec[-1][1] != "d":
raise AttributeError(f"Invalid element {self.symbol} for crystal field calculation")

assert self.oxi_state is not None
Expand Down
44 changes: 38 additions & 6 deletions tests/core/test_periodic_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ def test_full_electronic_structure(self):
(2, "p", 6),
(3, "s", 2),
(3, "p", 6),
(3, "d", 6),
(4, "s", 2),
(3, "d", 6),
],
"Li": [(1, "s", 2), (2, "s", 1)],
"U": [
Expand All @@ -84,19 +84,19 @@ def test_full_electronic_structure(self):
(2, "p", 6),
(3, "s", 2),
(3, "p", 6),
(3, "d", 10),
(4, "s", 2),
(3, "d", 10),
(4, "p", 6),
(4, "d", 10),
(5, "s", 2),
(4, "d", 10),
(5, "p", 6),
(6, "s", 2),
(4, "f", 14),
(5, "d", 10),
(6, "s", 2),
(6, "p", 6),
(7, "s", 2),
(5, "f", 3),
(6, "d", 1),
(7, "s", 2),
],
}
for k, v in cases.items():
Expand Down Expand Up @@ -169,6 +169,11 @@ def test_from_row_and_group(self):
for k, v in cases.items():
assert ElementBase.from_row_and_group(v[0], v[1]) == Element(k)

def test_n_electrons(self):
cases = {"O": 8, "Fe": 26, "Li": 3, "Be": 4}
for k, v in cases.items():
assert Element(k).n_electrons == v

def test_valence(self):
cases = {"O": (1, 4), "Fe": (2, 6), "Li": (0, 1), "Be": (0, 2)}
for k, v in cases.items():
Expand Down Expand Up @@ -602,18 +607,20 @@ def test_sort(self):

def test_species_electronic_structure(self):
assert Species("Fe", 0).electronic_structure == "[Ar].3d6.4s2"
assert Species("Fe", 0).n_electrons == 26
assert Species("Fe", 0).full_electronic_structure == [
(1, "s", 2),
(2, "s", 2),
(2, "p", 6),
(3, "s", 2),
(3, "p", 6),
(3, "d", 6),
(4, "s", 2),
(3, "d", 6),
]
assert Species("Fe", 0).valence == (2, 6)

assert Species("Fe", 2).electronic_structure == "[Ar].3d6"
assert Species("Fe", 2).n_electrons == 24
assert Species("Fe", 2).full_electronic_structure == [
(1, "s", 2),
(2, "s", 2),
Expand All @@ -625,6 +632,7 @@ def test_species_electronic_structure(self):
assert Species("Fe", 2).valence == (2, 6)

assert Species("Fe", 3).electronic_structure == "[Ar].3d5"
assert Species("Fe", 3).n_electrons == 23
assert Species("Fe", 3).full_electronic_structure == [
(1, "s", 2),
(2, "s", 2),
Expand All @@ -635,12 +643,36 @@ def test_species_electronic_structure(self):
]
assert Species("Fe", 3).valence == (2, 5)

assert Species("Th", 4).electronic_structure == "[Hg].6p6"
assert Species("Th", 4).full_electronic_structure == [
(1, "s", 2),
(2, "s", 2),
(2, "p", 6),
(3, "s", 2),
(3, "p", 6),
(4, "s", 2),
(3, "d", 10),
(4, "p", 6),
(5, "s", 2),
(4, "d", 10),
(5, "p", 6),
(6, "s", 2),
(4, "f", 14),
(5, "d", 10),
(6, "p", 6),
]
assert Species("Th", 4).valence == (1, 6)

assert Species("Li", 1).electronic_structure == "1s2"
assert Species("Li", 1).n_electrons == 2
# alkali metals, all p
for el in ["Na", "K", "Rb", "Cs"]:
assert Species(el, 1).electronic_structure.split(".")[-1][1::] == "p6", f"Failure for {el} +1"
for el in ["Ca", "Mg", "Ba", "Sr"]:
assert Species(el, 2).electronic_structure.split(".")[-1][1::] == "p6", f"Failure for {el} +2"
# valence shell should be f (l=3) for all lanthanide ions except La+3 and Lu+3
for el in ["Ce", "Nd", "Sm", "Eu", "Gd", "Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu"]:
assert Species(el, 3).valence[0] == 3, f"Failure for {el} +3"

for el in Element:
for ox in el.common_oxidation_states:
Expand Down
4 changes: 2 additions & 2 deletions tests/io/vasp/test_inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1099,8 +1099,8 @@ def test_nelectrons(self):
assert self.psingle_Fe.nelectrons == 8

def test_electron_config(self):
assert self.psingle_Mn_pv.electron_configuration == [(4, "s", 2), (3, "d", 5), (3, "p", 6)]
assert self.psingle_Fe.electron_configuration == [(4, "s", 2), (3, "d", 6)]
assert self.psingle_Mn_pv.electron_configuration == [(3, "d", 5), (4, "s", 2), (3, "p", 6)]
assert self.psingle_Fe.electron_configuration == [(3, "d", 6), (4, "s", 2)]

def test_attributes(self):
for key, val in self.Mn_pv_attrs.items():
Expand Down

0 comments on commit 09cf748

Please sign in to comment.