33
33
34
34
from pymatgen .util .typing import SpeciesLike
35
35
36
- # Load element data from JSON file
36
+ # Load element data (periodic table) from JSON file
37
37
with open (Path (__file__ ).absolute ().parent / "periodic_table.json" , encoding = "utf-8" ) as ptable_json :
38
- _pt_data = json .load (ptable_json )
38
+ _PT_DATA : dict = json .load (ptable_json )
39
39
40
- _pt_row_sizes = (2 , 8 , 8 , 18 , 18 , 32 , 32 )
40
+ _PT_ROW_SIZES : tuple [ int , ...] = (2 , 8 , 8 , 18 , 18 , 32 , 32 )
41
41
42
- _madelung = [
42
+ # Madelung energy ordering rule (lower to higher energy)
43
+ _MADELUNG : list [tuple [int , str ]] = [
43
44
(1 , "s" ),
44
45
(2 , "s" ),
45
46
(2 , "p" ),
@@ -137,21 +138,21 @@ def __init__(self, symbol: SpeciesLike) -> None:
137
138
Solid State Communications, 1984.
138
139
"""
139
140
self .symbol = str (symbol )
140
- data = _pt_data [symbol ]
141
+ data = _PT_DATA [symbol ]
141
142
142
143
# Store key variables for quick access
143
144
self .Z = data ["Atomic no" ]
144
145
145
146
self ._is_named_isotope = data .get ("Is named isotope" , False )
146
147
if self ._is_named_isotope :
147
- for sym in _pt_data :
148
- if _pt_data [ sym ][ "Atomic no" ] == self .Z and not _pt_data [ sym ] .get ("Is named isotope" , False ):
148
+ for sym , info in _PT_DATA . items () :
149
+ if info [ "Atomic no" ] == self .Z and not info .get ("Is named isotope" , False ):
149
150
self .symbol = sym
150
151
break
151
152
# For specified/named isotopes, treat the same as named element
152
153
# (the most common isotope). Then we pad the data block with the
153
154
# entries for the named element.
154
- data = {** _pt_data [self .symbol ], ** data }
155
+ data = {** _PT_DATA [self .symbol ], ** data }
155
156
156
157
at_r = data .get ("Atomic radius" , "no data" )
157
158
if str (at_r ).startswith ("no data" ):
@@ -452,33 +453,48 @@ def icsd_oxidation_states(self) -> tuple[int, ...]:
452
453
453
454
@property
454
455
def full_electronic_structure (self ) -> list [tuple [int , str , int ]]:
455
- """Full electronic structure as list of tuples, in order of increasing
456
+ """Full electronic structure in order of increasing
456
457
energy level (according to the Madelung rule). Therefore, the final
457
458
element in the list gives the electronic structure of the valence shell.
458
459
459
- For example, the electronic structure for Fe is represented as :
460
- [(1, "s", 2), (2, "s", 2), (2, "p", 6), (3, "s", 2), (3, "p", 6),
461
- (4, "s", 2), (3, "d", 6)].
460
+ For example, the full electronic structure for Fe is:
461
+ [(1, "s", 2), (2, "s", 2), (2, "p", 6), (3, "s", 2), (3, "p", 6),
462
+ (4, "s", 2), (3, "d", 6)].
462
463
463
464
References:
464
465
Kramida, A., Ralchenko, Yu., Reader, J., and NIST ASD Team (2023). NIST
465
466
Atomic Spectra Database (ver. 5.11). https://physics.nist.gov/asd [2024,
466
467
June 3]. National Institute of Standards and Technology, Gaithersburg,
467
468
MD. DOI: https://doi.org/10.18434/T4W30F
469
+
470
+ Returns:
471
+ list[tuple[int, str, int]]: A list of tuples representing each subshell,
472
+ where each tuple contains:
473
+ - `n` (int): Principal quantum number.
474
+ - `orbital_type` (str): Orbital type (e.g., "s", "p", "d", "f").
475
+ - `electron_count` (int): Number of electrons in the subshell.
468
476
"""
469
- e_str = self .electronic_structure
477
+ e_str : str = self .electronic_structure
470
478
471
- def parse_orbital (orb_str ):
479
+ def parse_orbital (orb_str : str ) -> str | tuple [int , str , int ]:
480
+ """Parse orbital information from split electron configuration string."""
481
+ # Parse valence subshell notation (e.g., "3d6" -> (3, "d", 6))
472
482
if match := re .match (r"(\d+)([spdfg]+)(\d+)" , orb_str ):
473
483
return int (match [1 ]), match [2 ], int (match [3 ])
484
+
485
+ # Return core-electron configuration as-is (e.g. "[Ar]")
474
486
return orb_str
475
487
476
- data = [parse_orbital (s ) for s in e_str .split ("." )]
477
- if data [0 ][0 ] == "[" :
478
- sym = data [0 ].replace ("[" , "" ).replace ("]" , "" )
488
+ # Split e_str (e.g. for Fe "[Ar].3d6.4s2" into ["[Ar]", "3d6", "4s2"])
489
+ data : list = [parse_orbital (s ) for s in e_str .split ("." )]
490
+
491
+ # Fully expand core-electron configuration (replace noble gas notation string)
492
+ if isinstance (data [0 ], str ):
493
+ sym : str = data [0 ].replace ("[" , "" ).replace ("]" , "" )
479
494
data = list (Element (sym ).full_electronic_structure ) + data [1 :]
480
- # sort the final electronic structure by increasing energy level
481
- return sorted (data , key = lambda x : _madelung .index ((x [0 ], x [1 ])))
495
+
496
+ # Sort the final electronic structure by increasing energy level
497
+ return sorted (data , key = lambda x : _MADELUNG .index ((x [0 ], x [1 ])))
482
498
483
499
@property
484
500
def n_electrons (self ) -> int :
@@ -563,7 +579,7 @@ def ground_state_term_symbol(self) -> str:
563
579
L_symbols = "SPDFGHIKLMNOQRTUVWXYZ"
564
580
565
581
term_symbols = self .term_symbols
566
- term_symbol_flat = { # type: ignore[var-annotated]
582
+ term_symbol_flat : dict = {
567
583
term : {
568
584
"multiplicity" : int (term [0 ]),
569
585
"L" : L_symbols .index (term [1 ]),
@@ -595,7 +611,7 @@ def from_Z(Z: int, A: int | None = None) -> Element:
595
611
Returns:
596
612
Element with atomic number Z.
597
613
"""
598
- for sym , data in _pt_data .items ():
614
+ for sym , data in _PT_DATA .items ():
599
615
atomic_mass_num = data .get ("Atomic mass no" ) if A else None
600
616
if data ["Atomic no" ] == Z and atomic_mass_num == A :
601
617
return Element (sym )
@@ -616,7 +632,7 @@ def from_name(name: str) -> Element:
616
632
uk_to_us = {"aluminium" : "aluminum" , "caesium" : "cesium" }
617
633
name = uk_to_us .get (name .lower (), name )
618
634
619
- for sym , data in _pt_data .items ():
635
+ for sym , data in _PT_DATA .items ():
620
636
if data ["Name" ] == name .capitalize ():
621
637
return Element (sym )
622
638
@@ -643,7 +659,7 @@ def from_row_and_group(row: int, group: int) -> Element:
643
659
Note:
644
660
The 18 group number system is used, i.e. noble gases are group 18.
645
661
"""
646
- for sym in _pt_data :
662
+ for sym in _PT_DATA :
647
663
el = Element (sym )
648
664
if 57 <= el .Z <= 71 :
649
665
el_pseudo_row = 8
@@ -683,7 +699,7 @@ def row(self) -> int:
683
699
return 6
684
700
if 89 <= z <= 103 :
685
701
return 7
686
- for idx , size in enumerate (_pt_row_sizes , start = 1 ):
702
+ for idx , size in enumerate (_PT_ROW_SIZES , start = 1 ):
687
703
total += size
688
704
if total >= z :
689
705
return idx
@@ -1161,33 +1177,45 @@ def electronic_structure(self) -> str:
1161
1177
# robustness
1162
1178
@property
1163
1179
def full_electronic_structure (self ) -> list [tuple [int , str , int ]]:
1164
- """Full electronic structure as list of tuples, in order of increasing
1180
+ """Full electronic structure in order of increasing
1165
1181
energy level (according to the Madelung rule). Therefore, the final
1166
1182
element in the list gives the electronic structure of the valence shell.
1167
1183
1168
- For example, the electronic structure for Fe+2 is represented as :
1169
- [(1, "s", 2), (2, "s", 2), (2, "p", 6), (3, "s", 2), (3, "p", 6),
1170
- (3, "d", 6)].
1184
+ For example, the full electronic structure for Fe is:
1185
+ [(1, "s", 2), (2, "s", 2), (2, "p", 6), (3, "s", 2), (3, "p", 6),
1186
+ (4, "s", 2), (3, "d", 6)].
1171
1187
1172
1188
References:
1173
1189
Kramida, A., Ralchenko, Yu., Reader, J., and NIST ASD Team (2023). NIST
1174
1190
Atomic Spectra Database (ver. 5.11). https://physics.nist.gov/asd [2024,
1175
1191
June 3]. National Institute of Standards and Technology, Gaithersburg,
1176
1192
MD. DOI: https://doi.org/10.18434/T4W30F
1193
+
1194
+ Returns:
1195
+ list[tuple[int, str, int]]: A list of tuples representing each subshell,
1196
+ where each tuple contains:
1197
+ - `n` (int): Principal quantum number.
1198
+ - `orbital_type` (str): Orbital type (e.g., "s", "p", "d", "f").
1199
+ - `electron_count` (int): Number of electrons in the subshell.
1177
1200
"""
1178
- e_str = self .electronic_structure
1201
+ e_str : str = self .electronic_structure
1179
1202
1180
- def parse_orbital (orb_str ):
1203
+ def parse_orbital (orb_str : str ) -> str | tuple [int , str , int ]:
1204
+ """Parse orbital information from split electron configuration string."""
1205
+ # Parse valence subshell notation (e.g., "3d6" -> (3, "d", 6))
1181
1206
if match := re .match (r"(\d+)([spdfg]+)(\d+)" , orb_str ):
1182
1207
return int (match [1 ]), match [2 ], int (match [3 ])
1208
+
1209
+ # Return core-electron configuration as-is (e.g. "[Ar]")
1183
1210
return orb_str
1184
1211
1185
- data = [parse_orbital (s ) for s in e_str .split ("." )]
1186
- if data [0 ][ 0 ] == "[" :
1212
+ data : list = [parse_orbital (s ) for s in e_str .split ("." )]
1213
+ if isinstance ( data [0 ], str ) :
1187
1214
sym = data [0 ].replace ("[" , "" ).replace ("]" , "" )
1188
1215
data = list (Element (sym ).full_electronic_structure ) + data [1 :]
1189
- # sort the final electronic structure by increasing energy level
1190
- return sorted (data , key = lambda x : _madelung .index ((x [0 ], x [1 ])))
1216
+
1217
+ # Sort the final electronic structure by increasing energy level
1218
+ return sorted (data , key = lambda x : _MADELUNG .index ((x [0 ], x [1 ])))
1191
1219
1192
1220
# NOTE - copied exactly from Element. Refactoring / inheritance may improve
1193
1221
# robustness
0 commit comments