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

Add support for parsing orbital moments from OUTCAR #3463

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
74 changes: 69 additions & 5 deletions src/pymatgen/io/vasp/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1862,6 +1862,8 @@ class Outcar:
Attributes:
magnetization (tuple): Magnetization on each ion as a tuple of dict, e.g.
({"d": 0.0, "p": 0.003, "s": 0.002, "tot": 0.005}, ... )
orbital_moment (tuple): Orbital moments on each ion as a tuple of dict, e.g.,
({"d": 0.109, "p": -0.001, "tot": 0.108}, ... )
chemical_shielding (dict): Chemical shielding on each ion as a dictionary with core and valence contributions.
unsym_cs_tensor (list): Unsymmetrized chemical shielding tensor matrixes on each ion as a list.
e.g. [[[sigma11, sigma12, sigma13], [sigma21, sigma22, sigma23], [sigma31, sigma32, sigma33]], ...]
Expand Down Expand Up @@ -1927,6 +1929,7 @@ def __init__(self, filename: PathLike) -> None:
mag_x = []
mag_y = []
mag_z = []
orbmom = [[], [], []]
header = []
run_stats: dict[str, float | None] = {}
total_mag = nelect = efermi = e_fr_energy = e_wo_entrp = e0 = None
Expand Down Expand Up @@ -1988,9 +1991,10 @@ def __init__(self, filename: PathLike) -> None:
read_mag_x = False
read_mag_y = False # for SOC calculations only
read_mag_z = False
read_orbmom = [False, False, False] # For SOC calculations with LORBMOM=.TRUE.
all_lines.reverse()
for clean in all_lines:
if read_charge or read_mag_x or read_mag_y or read_mag_z:
if read_charge or read_mag_x or read_mag_y or read_mag_z or any(read_orbmom):
if clean.startswith("# of ion"):
header = re.split(r"\s{2,}", clean.strip())
header.pop(0)
Expand All @@ -2005,34 +2009,86 @@ def __init__(self, filename: PathLike) -> None:
mag_y.append(dict(zip(header, tokens, strict=True)))
elif read_mag_z:
mag_z.append(dict(zip(header, tokens, strict=True)))
elif any(read_orbmom):
idx = read_orbmom.index(True)
orbmom[idx].append(dict(zip(header, tokens, strict=True)))
elif clean.startswith("tot"):
read_charge = False
read_mag_x = False
read_mag_y = False
read_mag_z = False
read_orbmom = [False, False, False]
if clean == "total charge":
charge = []
read_charge = True
read_mag_x, read_mag_y, read_mag_z = False, False, False
read_orbmom = [False, False, False]
read_mag_x, read_mag_y, read_mag_z = (
False,
False,
False,
)
elif clean == "magnetization (x)":
mag_x = []
read_mag_x = True
read_charge, read_mag_y, read_mag_z = False, False, False
read_orbmom = [False, False, False]
read_charge, read_mag_y, read_mag_z = (
False,
False,
False,
)
elif clean == "magnetization (y)":
mag_y = []
read_mag_y = True
read_charge, read_mag_x, read_mag_z = False, False, False
read_orbmom = [False, False, False]
read_charge, read_mag_x, read_mag_z = (
False,
False,
False,
)
elif clean == "magnetization (z)":
mag_z = []
read_mag_z = True
read_charge, read_mag_x, read_mag_y = False, False, False
read_charge, read_mag_x, read_mag_y = (
False,
False,
False,
)
read_orbmom = [False, False, False]
elif clean == "orbital moment (x)":
read_charge, read_mag_x, read_mag_y, read_mag_z = (
False,
False,
False,
False,
)
orbmom[0] = []
read_orbmom = [True, False, False]
elif clean == "orbital moment (y)":
read_charge, read_mag_x, read_mag_y, read_mag_z = (
False,
False,
False,
False,
)
orbmom[1] = []
read_orbmom = [False, True, False]
elif clean == "orbital moment (z)":
read_charge, read_mag_x, read_mag_y, read_mag_z = (
False,
False,
False,
False,
)
orbmom[2] = []
read_orbmom = [False, False, True]
elif re.search("electrostatic", clean):
read_charge, read_mag_x, read_mag_y, read_mag_z = (
False,
False,
False,
False,
)
read_orbmom = [False, False, False]

# Merge x, y and z components of magmoms if present (SOC calculation)
if mag_y and mag_z:
Expand All @@ -2042,6 +2098,13 @@ def __init__(self, filename: PathLike) -> None:
mag.append({key: Magmom([mag_x[idx][key], mag_y[idx][key], mag_z[idx][key]]) for key in mag_x[0]})
else:
mag = mag_x
# merge x, y and z components of orbmoms if present (SOC calculation with LORBMOM=.TRUE.)
orbital_moment = []
if all(orbmom): # Check if all elements in orbmom[0], orbmom[1], and orbmom[2] are non-empty
orbital_moment = [
{key: Magmom([x[key], y[key], z[key]]) for key in x}
for x, y, z in zip(orbmom[0], orbmom[1], orbmom[2], strict=True)
]

# Data from beginning of OUTCAR
run_stats["cores"] = None
Expand All @@ -2061,6 +2124,7 @@ def __init__(self, filename: PathLike) -> None:

self.run_stats = run_stats
self.magnetization = tuple(mag)
self.orbital_moment = tuple(orbital_moment)
self.charge = tuple(charge)
self.efermi = efermi
self.nelect = nelect
Expand Down
23 changes: 23 additions & 0 deletions tests/io/vasp/test_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -932,9 +932,32 @@ def test_soc(self):
"tot": Magmom([0.0, 0.0, 0.0]),
},
)
expected_orbmom = (
{
"p": Magmom([0.0, 0.0, 0.0]),
"d": Magmom([0.109, 0.109, 0.109]),
"tot": Magmom([0.108, 0.108, 0.108]),
},
{
"p": Magmom([0.0, 0.0, 0.0]),
"d": Magmom([-0.109, -0.109, -0.109]),
"tot": Magmom([-0.108, -0.108, -0.108]),
},
{
"p": Magmom([0.0, 0.0, 0.0]),
"d": Magmom([0.0, 0.0, 0.0]),
"tot": Magmom([0.0, 0.0, 0.0]),
},
{
"p": Magmom([0.0, 0.0, 0.0]),
"d": Magmom([0.0, 0.0, 0.0]),
"tot": Magmom([0.0, 0.0, 0.0]),
},
)
# test note: Magmom class uses np.allclose() when testing for equality
# so fine to use == operator here
assert outcar.magnetization == expected_mag, "Wrong vector magnetization read from Outcar for SOC calculation"
assert outcar.orbital_moment == expected_orbmom, "Wrong orbital moments read from Outcar for SOC calculation"

assert outcar.noncollinear is True

Expand Down