diff --git a/src/pymatgen/io/vasp/outputs.py b/src/pymatgen/io/vasp/outputs.py index f954554d0f6..627561825f2 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]], ...] @@ -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 @@ -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) @@ -2005,27 +2009,78 @@ 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, @@ -2033,6 +2088,7 @@ def __init__(self, filename: PathLike) -> None: 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: @@ -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 @@ -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 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