From e6a332757e242a2d15b4ca85fa616d3ee77c5364 Mon Sep 17 00:00:00 2001 From: Omar Ashour Date: Wed, 8 Nov 2023 17:49:34 -0800 Subject: [PATCH 1/4] Parse orbital moments in OUTCAR --- src/pymatgen/io/vasp/outputs.py | 99 +++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 6 deletions(-) diff --git a/src/pymatgen/io/vasp/outputs.py b/src/pymatgen/io/vasp/outputs.py index f954554d0f6..015b7c2a975 100644 --- a/src/pymatgen/io/vasp/outputs.py +++ b/src/pymatgen/io/vasp/outputs.py @@ -1927,6 +1927,9 @@ def __init__(self, filename: PathLike) -> None: mag_x = [] mag_y = [] mag_z = [] + orbmom_x = [] + orbmom_y = [] + orbmom_z = [] header = [] run_stats: dict[str, float | None] = {} total_mag = nelect = efermi = e_fr_energy = e_wo_entrp = e0 = None @@ -1988,9 +1991,12 @@ def __init__(self, filename: PathLike) -> None: read_mag_x = False read_mag_y = False # for SOC calculations only read_mag_z = False + read_orbmom_x = False # For SOC calculations with LORBMOM=.TRUE. + read_orbmom_y = False + read_orbmom_z = False 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 read_orbmom_x or read_orbmom_y or read_orbmom_z: if clean.startswith("# of ion"): header = re.split(r"\s{2,}", clean.strip()) header.pop(0) @@ -2005,29 +2011,102 @@ 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 read_orbmom_x: + orbmom_x.append(dict(zip(header, tokens, strict=True))) + elif read_orbmom_y: + orbmom_y.append(dict(zip(header, tokens, strict=True))) + elif read_orbmom_z: + orbmom_z.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_x = False + read_orbmom_y = False + read_orbmom_z = False if clean == "total charge": charge = [] read_charge = True - read_mag_x, read_mag_y, read_mag_z = False, False, False + read_mag_x, read_mag_y, read_mag_z, read_orbmom_x, read_orbmom_y, read_orbmom_z = ( + False, + False, + False, + 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_charge, read_mag_y, read_mag_z, read_orbmom_x, read_orbmom_y, read_orbmom_z = ( + False, + False, + False, + 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_charge, read_mag_x, read_mag_z, read_orbmom_x, read_orbmom_y, read_orbmom_z = ( + False, + False, + False, + 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, read_orbmom_x, read_orbmom_y, read_orbmom_z = ( + False, + False, + False, + False, + False, + False, + ) + elif clean == "orbital moment (x)": + orbmom_x = [] + read_orbmom_x = True + read_charge, read_mag_x, read_mag_y, read_mag_z, read_orbmom_y, read_orbmom_z = ( + False, + False, + False, + False, + False, + False, + ) + elif clean == "orbital moment (y)": + orbmom_y = [] + read_orbmom_y = True + read_charge, read_mag_x, read_mag_y, read_mag_z, read_orbmom_x, read_orbmom_z = ( + False, + False, + False, + False, + False, + False, + ) + elif clean == "orbital moment (z)": + orbmom_z = [] + read_orbmom_z = True + read_charge, read_mag_x, read_mag_y, read_mag_z, read_orbmom_x, read_orbmom_y = ( + False, + False, + False, + False, + False, + False, + ) elif re.search("electrostatic", clean): - read_charge, read_mag_x, read_mag_y, read_mag_z = ( + read_charge, read_mag_x, read_mag_y, read_mag_z, read_orbmom_x, read_orbmom_y, read_orbmom_z = ( + False, + False, + False, False, False, False, @@ -2042,6 +2121,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.) + orbmom = [] + if orbmom_x and orbmom_y and orbmom_z: + for idx in range(len(orbmom_x)): + orbmom.append( + {key: Magmom([orbmom_x[idx][key], orbmom_y[idx][key], orbmom_z[idx][key]]) for key in orbmom_x[0]} + ) # Data from beginning of OUTCAR run_stats["cores"] = None @@ -2061,6 +2147,7 @@ def __init__(self, filename: PathLike) -> None: self.run_stats = run_stats self.magnetization = tuple(mag) + self.orbital_moment = tuple(orbmom) self.charge = tuple(charge) self.efermi = efermi self.nelect = nelect From a7a71a71a3b59abccc9f215ba27d85618031251c Mon Sep 17 00:00:00 2001 From: Omar Ashour Date: Wed, 8 Nov 2023 17:49:55 -0800 Subject: [PATCH 2/4] Add test for orbital moments --- tests/io/vasp/test_outputs.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/io/vasp/test_outputs.py b/tests/io/vasp/test_outputs.py index dd6decacab2..08f59ceb170 100644 --- a/tests/io/vasp/test_outputs.py +++ b/tests/io/vasp/test_outputs.py @@ -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 From 877062e472b1a9c7b5abbc05608cc02eaf300232 Mon Sep 17 00:00:00 2001 From: Omar Ashour Date: Wed, 8 Nov 2023 17:51:38 -0800 Subject: [PATCH 3/4] Update OUTCAR docstring --- src/pymatgen/io/vasp/outputs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pymatgen/io/vasp/outputs.py b/src/pymatgen/io/vasp/outputs.py index 015b7c2a975..bd7f8658e52 100644 --- a/src/pymatgen/io/vasp/outputs.py +++ b/src/pymatgen/io/vasp/outputs.py @@ -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]], ...] From 6d1aa07cfd265782a6941cd6c9af8110ff8fcea4 Mon Sep 17 00:00:00 2001 From: Omar Ashour Date: Sat, 21 Sep 2024 17:00:46 -0700 Subject: [PATCH 4/4] Clean up orbital moment parser --- src/pymatgen/io/vasp/outputs.py | 91 ++++++++++++--------------------- 1 file changed, 33 insertions(+), 58 deletions(-) diff --git a/src/pymatgen/io/vasp/outputs.py b/src/pymatgen/io/vasp/outputs.py index bd7f8658e52..627561825f2 100644 --- a/src/pymatgen/io/vasp/outputs.py +++ b/src/pymatgen/io/vasp/outputs.py @@ -1929,9 +1929,7 @@ def __init__(self, filename: PathLike) -> None: mag_x = [] mag_y = [] mag_z = [] - orbmom_x = [] - orbmom_y = [] - orbmom_z = [] + orbmom = [[], [], []] header = [] run_stats: dict[str, float | None] = {} total_mag = nelect = efermi = e_fr_energy = e_wo_entrp = e0 = None @@ -1993,12 +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_x = False # For SOC calculations with LORBMOM=.TRUE. - read_orbmom_y = False - read_orbmom_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 or read_orbmom_x or read_orbmom_y or read_orbmom_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) @@ -2013,27 +2009,20 @@ 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 read_orbmom_x: - orbmom_x.append(dict(zip(header, tokens, strict=True))) - elif read_orbmom_y: - orbmom_y.append(dict(zip(header, tokens, strict=True))) - elif read_orbmom_z: - orbmom_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_x = False - read_orbmom_y = False - read_orbmom_z = False + read_orbmom = [False, False, False] if clean == "total charge": charge = [] read_charge = True - read_mag_x, read_mag_y, read_mag_z, read_orbmom_x, read_orbmom_y, read_orbmom_z = ( - False, - False, - False, + read_orbmom = [False, False, False] + read_mag_x, read_mag_y, read_mag_z = ( False, False, False, @@ -2041,10 +2030,8 @@ def __init__(self, filename: PathLike) -> None: elif clean == "magnetization (x)": mag_x = [] read_mag_x = True - read_charge, read_mag_y, read_mag_z, read_orbmom_x, read_orbmom_y, read_orbmom_z = ( - False, - False, - False, + read_orbmom = [False, False, False] + read_charge, read_mag_y, read_mag_z = ( False, False, False, @@ -2052,10 +2039,8 @@ def __init__(self, filename: PathLike) -> None: elif clean == "magnetization (y)": mag_y = [] read_mag_y = True - read_charge, read_mag_x, read_mag_z, read_orbmom_x, read_orbmom_y, read_orbmom_z = ( - False, - False, - False, + read_orbmom = [False, False, False] + read_charge, read_mag_x, read_mag_z = ( False, False, False, @@ -2063,57 +2048,47 @@ def __init__(self, filename: PathLike) -> None: elif clean == "magnetization (z)": mag_z = [] read_mag_z = True - read_charge, read_mag_x, read_mag_y, read_orbmom_x, read_orbmom_y, read_orbmom_z = ( - False, - False, - False, + read_charge, read_mag_x, read_mag_y = ( False, False, False, ) + read_orbmom = [False, False, False] elif clean == "orbital moment (x)": - orbmom_x = [] - read_orbmom_x = True - read_charge, read_mag_x, read_mag_y, read_mag_z, read_orbmom_y, read_orbmom_z = ( - False, - False, + 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)": - orbmom_y = [] - read_orbmom_y = True - read_charge, read_mag_x, read_mag_y, read_mag_z, read_orbmom_x, read_orbmom_z = ( - False, - False, + 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)": - orbmom_z = [] - read_orbmom_z = True - read_charge, read_mag_x, read_mag_y, read_mag_z, read_orbmom_x, read_orbmom_y = ( - False, - False, + 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, read_orbmom_x, read_orbmom_y, read_orbmom_z = ( - False, - False, - False, + 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: @@ -2124,12 +2099,12 @@ def __init__(self, filename: PathLike) -> None: else: mag = mag_x # merge x, y and z components of orbmoms if present (SOC calculation with LORBMOM=.TRUE.) - orbmom = [] - if orbmom_x and orbmom_y and orbmom_z: - for idx in range(len(orbmom_x)): - orbmom.append( - {key: Magmom([orbmom_x[idx][key], orbmom_y[idx][key], orbmom_z[idx][key]]) for key in orbmom_x[0]} - ) + 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 @@ -2149,7 +2124,7 @@ def __init__(self, filename: PathLike) -> None: self.run_stats = run_stats self.magnetization = tuple(mag) - self.orbital_moment = tuple(orbmom) + self.orbital_moment = tuple(orbital_moment) self.charge = tuple(charge) self.efermi = efermi self.nelect = nelect