Skip to content

Commit a13b2b6

Browse files
committed
enhance unit test and add scrambled POTCARs
1 parent cd92872 commit a13b2b6

File tree

8 files changed

+68
-11
lines changed

8 files changed

+68
-11
lines changed

dev_scripts/potcar_scrambler.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
class PotcarScrambler:
2323
"""
24-
Takes a POTCAR and replaces its values with completely random values
24+
Takes a POTCAR and replaces its values with completely random values.
2525
Does type matching and attempts precision matching on floats to ensure
2626
file is read correctly by Potcar and PotcarSingle classes.
2727
@@ -40,14 +40,15 @@ class PotcarScrambler:
4040

4141
def __init__(self, potcars: Potcar | PotcarSingle) -> None:
4242
self.PSP_list = [potcars] if isinstance(potcars, PotcarSingle) else potcars
43-
self.scrambled_potcars_str = ""
43+
self.scrambled_potcars_str: str = ""
4444
for psp in self.PSP_list:
4545
scrambled_potcar_str = self.scramble_single_potcar(psp)
4646
self.scrambled_potcars_str += scrambled_potcar_str
4747

4848
def _rand_float_from_str_with_prec(self, input_str: str, bloat: float = 1.5) -> float:
49-
n_prec = len(input_str.split(".")[1])
50-
bd = max(1, bloat * abs(float(input_str))) # ensure we don't get 0
49+
"""Generate a random float from str to replace true values."""
50+
n_prec: int = len(input_str.split(".")[1])
51+
bd: float = max(1.0, bloat * abs(float(input_str))) # ensure we don't get 0
5152
return round(bd * np.random.default_rng().random(), n_prec)
5253

5354
def _read_fortran_str_and_scramble(self, input_str: str, bloat: float = 1.5):
@@ -124,14 +125,16 @@ def scramble_single_potcar(self, potcar: PotcarSingle) -> str:
124125
return scrambled_potcar_str
125126

126127
def to_file(self, filename: str) -> None:
128+
"""Write scrambled POTCAR to file."""
127129
with zopen(filename, mode="wt", encoding="utf-8") as file:
128130
file.write(self.scrambled_potcars_str)
129131

130132
@classmethod
131133
def from_file(cls, input_filename: str, output_filename: str | None = None) -> Self:
134+
"""Read a POTCAR from file and generate a scrambled version."""
132135
psp = Potcar.from_file(input_filename)
133136
psp_scrambled = cls(psp)
134-
if output_filename:
137+
if output_filename is not None:
135138
psp_scrambled.to_file(output_filename)
136139
return psp_scrambled
137140

src/pymatgen/io/vasp/inputs.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2128,11 +2128,13 @@ def __repr__(self) -> str:
21282128

21292129
@property
21302130
def electron_configuration(self) -> list[tuple[int, str, int]] | None:
2131-
"""Valence electronic configuration corresponding to the ZVAL.
2131+
"""Valence electronic configuration corresponding to the ZVAL,
2132+
read from the "Atomic configuration" section of POTCAR.
21322133
21332134
If the POTCAR defines a non-integer number of electrons,
21342135
the configuration is not well-defined, and None is returned.
21352136
"""
2137+
# TODO: test non integer cases
21362138
if not self.nelectrons.is_integer():
21372139
warnings.warn(
21382140
"POTCAR has non-integer charge, electron configuration not well-defined.",
@@ -2144,10 +2146,15 @@ def electron_configuration(self) -> list[tuple[int, str, int]] | None:
21442146
full_config: list[tuple[int, str, int]] = el.full_electronic_structure
21452147
nelect: float = self.nelectrons
21462148
config: list[tuple[int, str, int]] = []
2149+
21472150
while nelect > 0:
2148-
e_config: tuple[int, str, int] = full_config.pop(-1)
2149-
config.append(e_config)
2150-
nelect -= e_config[-1]
2151+
n, l, num_e = full_config.pop(-1)
2152+
# Skip fully filled d/f orbitals if there are higher n orbitals
2153+
if l in {"d", "f"} and num_e in {10, 14} and any(n_ > n for n_, _, _ in full_config):
2154+
continue
2155+
config.append((n, l, num_e))
2156+
nelect -= num_e
2157+
21512158
return config
21522159

21532160
@property
86.5 KB
Binary file not shown.
119 KB
Binary file not shown.
69.2 KB
Binary file not shown.
97 KB
Binary file not shown.
80.2 KB
Binary file not shown.

tests/io/vasp/test_inputs.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,13 +1377,60 @@ def test_nelectrons(self):
13771377
assert self.psingle_Mn_pv.nelectrons == 13
13781378
assert self.psingle_Fe.nelectrons == 8
13791379

1380-
def test_electron_config(self):
1380+
def test_electron_configuration(self):
1381+
# TODO: use `approx` to compare floats
1382+
1383+
# Test s-block (Li: 2s1)
1384+
assert PotcarSingle.from_file(f"{FAKE_POTCAR_DIR}/POT_GGA_PAW_PBE_54/POTCAR.Li.gz").electron_configuration == [
1385+
(2, "s", 1),
1386+
]
1387+
1388+
# Test p-block (O: 2s2 sp4)
1389+
assert PotcarSingle.from_file(f"{FAKE_POTCAR_DIR}/POT_GGA_PAW_PBE_54/POTCAR.O.gz").electron_configuration == [
1390+
(2, "s", 2),
1391+
(2, "p", 4),
1392+
]
1393+
1394+
# Test d-block (Fe: 4s1 3d7)
1395+
assert self.psingle_Fe.electron_configuration == [(3, "d", 7), (4, "s", 1)]
1396+
1397+
# Test f-block (Ce: 5s2 6s2 5p6 5d1 4f1)
1398+
assert PotcarSingle.from_file(f"{FAKE_POTCAR_DIR}/POT_GGA_PAW_PBE_54/POTCAR.Ce.gz").electron_configuration == [
1399+
(5, "s", 2),
1400+
(6, "s", 2),
1401+
(5, "p", 6),
1402+
(5, "d", 1),
1403+
(4, "f", 1),
1404+
]
1405+
1406+
# Test "sv" POTCARs (K_sv: 3s2 4s1 3p6)
1407+
assert PotcarSingle.from_file(
1408+
f"{FAKE_POTCAR_DIR}/POT_GGA_PAW_PBE_54/POTCAR.K_sv.gz"
1409+
).electron_configuration == [
1410+
(3, "s", 2),
1411+
(4, "s", 1),
1412+
(3, "p", 6),
1413+
]
1414+
1415+
# Test "pv" POTCARs
13811416
assert self.psingle_Mn_pv.electron_configuration == [
13821417
(3, "d", 5),
13831418
(4, "s", 2),
13841419
(3, "p", 6),
13851420
]
1386-
assert self.psingle_Fe.electron_configuration == [(3, "d", 6), (4, "s", 2)]
1421+
1422+
# Test non-integer occupancy (Be: 2s1.99 2p0.01)
1423+
assert PotcarSingle.from_file(f"{FAKE_POTCAR_DIR}/POT_GGA_PAW_PBE_54/POTCAR.Be.gz").electron_configuration == [
1424+
(2, "s", 1.99),
1425+
(2, "p", 0.01),
1426+
]
1427+
1428+
# Test another non-integer occupancy (H.25: 1s0.25)
1429+
assert PotcarSingle.from_file(
1430+
f"{FAKE_POTCAR_DIR}/POT_GGA_PAW_PBE_54/POTCAR.H.25.gz"
1431+
).electron_configuration == [
1432+
(1, "s", 0.25),
1433+
]
13871434

13881435
def test_attributes(self):
13891436
for key, val in self.Mn_pv_attrs.items():

0 commit comments

Comments
 (0)