Skip to content

Commit b59a0de

Browse files
committed
Species: implement electron configuration methods
1 parent e3ba802 commit b59a0de

File tree

2 files changed

+70
-20
lines changed

2 files changed

+70
-20
lines changed

pymatgen/core/periodic_table.py

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,20 +1063,65 @@ def spin(self) -> float | None:
10631063

10641064
@property
10651065
def full_electronic_structure(self) -> list[tuple[int, str, int]]:
1066-
"""Full electronic structure as tuple. Not implemented for Species as of now."""
1067-
raise NotImplementedError
1066+
"""
1067+
Full electronic structure as tuple.
1068+
1069+
If the oxidation state is non-zero, it is assumed that the Species adopts
1070+
the same electronic configuration as the element with the same number of total
1071+
electrons as the Species. For example, Fe+3 would have the same electronic
1072+
structure as V, which has 23 electrons (26 for Fe minus 3 to get the +3 oxidation
1073+
state)
1074+
"""
1075+
if self.oxi_state != 0:
1076+
# adopt the electronic configuration of the element with the same atomic number
1077+
# as this element, +/- the associated number of electrons
1078+
return Element.from_Z(self.element.Z - self.oxi_state).full_electronic_structure
1079+
1080+
return self.element.full_electronic_structure
10681081

10691082
@property
1070-
def electronic_structure(self) -> list[tuple[int, str, int]]:
1071-
"""Electronic structure as tuple. Not implemented for Species as of now."""
1072-
raise NotImplementedError
1083+
def electronic_structure(self) -> str:
1084+
"""
1085+
Electronic structure as string, with only valence electrons.
1086+
e.g. The electronic structure for Fe+3 is represented as '[Ar].4s2.3d3'.
1087+
1088+
If the oxidation state is non-zero, it is assumed that the Species adopts
1089+
the same electronic configuration as the element with the same number of total
1090+
electrons as the Species. For example, Fe+3 would have the same electronic
1091+
structure as V, which has 23 electrons (26 for Fe minus 3 to get the +3 oxidation
1092+
state)
1093+
"""
1094+
if self.oxi_state != 0:
1095+
# adopt the electronic configuration of the element with the same atomic number
1096+
# as this element, +/- the associated number of electrons
1097+
return Element.from_Z(self.element.Z - self.oxi_state).electronic_structure
1098+
1099+
return self.element.electronic_structure
10731100

10741101
@property
10751102
def valence(self) -> tuple[int | np.nan, int]:
10761103
"""Valence subshell angular moment (L) and number of valence e- (v_e),
1077-
obtained from full electron config. Not implemented for Species as of now.
1104+
obtained from full electron config.
1105+
1106+
L=0 for s orbital, 1 for p, 2 for d, and 3 for 4.
10781107
"""
1079-
raise NotImplementedError
1108+
if self.group == 18:
1109+
return np.nan, 0 # The number of valence of noble gas is 0
1110+
1111+
L_symbols = "SPDFGHIKLMNOQRTUVWXYZ"
1112+
valence: list[tuple[int, int]] = []
1113+
full_electron_config = self.full_electronic_structure
1114+
last_orbital = full_electron_config[-1]
1115+
for n, l_symbol, ne in full_electron_config:
1116+
idx = L_symbols.lower().index(l_symbol)
1117+
if ne < (2 * idx + 1) * 2 or (
1118+
(n, l_symbol, ne) == last_orbital and ne == (2 * idx + 1) * 2 and len(valence) == 0
1119+
): # check for full last shell (e.g. column 2)
1120+
valence.append((idx, ne))
1121+
if len(valence) > 1:
1122+
raise ValueError(f"{self} has ambiguous valence")
1123+
1124+
return valence[0]
10801125

10811126
@property
10821127
def ionic_radius(self) -> float | None:

tests/core/test_periodic_table.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ def test_full_electronic_structure(self):
7373
(2, "p", 6),
7474
(3, "s", 2),
7575
(3, "p", 6),
76-
(3, "d", 6),
7776
(4, "s", 2),
77+
(3, "d", 6),
7878
],
7979
"Li": [(1, "s", 2), (2, "s", 1)],
8080
"U": [
@@ -83,25 +83,25 @@ def test_full_electronic_structure(self):
8383
(2, "p", 6),
8484
(3, "s", 2),
8585
(3, "p", 6),
86-
(3, "d", 10),
8786
(4, "s", 2),
87+
(3, "d", 10),
8888
(4, "p", 6),
89-
(4, "d", 10),
9089
(5, "s", 2),
90+
(4, "d", 10),
9191
(5, "p", 6),
92+
(6, "s", 2),
9293
(4, "f", 14),
9394
(5, "d", 10),
94-
(6, "s", 2),
9595
(6, "p", 6),
96+
(7, "s", 2),
9697
(5, "f", 3),
9798
(6, "d", 1),
98-
(7, "s", 2),
9999
],
100100
}
101101
for k, v in cases.items():
102102
assert Element(k).full_electronic_structure == v
103103

104-
assert Element.Ac.electronic_structure == "[Rn].6d1.7s2"
104+
assert Element.Ac.electronic_structure == "[Rn].7s2.6d1"
105105

106106
def test_group(self):
107107
cases = {
@@ -591,13 +591,18 @@ def test_sort(self):
591591
)
592592
assert sp.spin == 5
593593

594-
def test_not_implemented(self):
595-
with pytest.raises(NotImplementedError):
596-
_ = Species("Fe", 2).full_electronic_structure
597-
with pytest.raises(NotImplementedError):
598-
_ = Species("Fe", 2).electronic_structure
599-
with pytest.raises(NotImplementedError):
600-
_ = Species("Fe", 2).valence
594+
def test_species_electron_config(self):
595+
assert Species("Fe", 3).full_electronic_structure == [
596+
(1, "s", 2),
597+
(2, "s", 2),
598+
(2, "p", 6),
599+
(3, "s", 2),
600+
(3, "p", 6),
601+
(4, "s", 2),
602+
(3, "d", 3),
603+
]
604+
assert Species("Fe", 3).electronic_structure == "[Ar].4s2.3d3"
605+
assert Species("Fe", 3).valence == (2, 3)
601606

602607

603608
def test_get_el_sp():

0 commit comments

Comments
 (0)