Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Element/Species: order full_electron_structure by energy #3944

Merged
merged 2 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading