Source code for vaspparser.vasp.procar

# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
# Distributed under the terms of "New BSD License", see the LICENSE file.

from collections import OrderedDict
from typing import Optional

import numpy as np

from vaspparser.dft.waves.electronic import ElectronicStructure

__author__ = "Sudarsan Surendralal"
__copyright__ = (
    "Copyright 2021, Max-Planck-Institut für Eisenforschung GmbH - "
    "Computational Materials Design (CM) Department"
)
__version__ = "1.0"
__maintainer__ = "Sudarsan Surendralal"
__email__ = "surendralal@mpie.de"
__status__ = "production"
__date__ = "Sep 1, 2017"


[docs] class Procar: """ This module contains routines to parse VASP PROCAR files. """
[docs] def __init__(self): self._is_spin_polarized = False self.dos_dict = OrderedDict()
[docs] def from_file(self, filename: str) -> ElectronicStructure: """ Parse a VASP PROCAR file and return the electronic structure. The PROCAR file is written when ``LORBIT = 10`` or ``LORBIT = 11`` is set in the INCAR. It contains the k-point coordinates and weights, band eigenvalues and occupancies, and the projection of the wave functions onto spherical harmonics centered on each ion (orbital- and atom-resolved DOS weights). Args: filename (str): Path to the PROCAR file Returns: ElectronicStructure: Populated electronic structure object with k-points, bands, eigenvalues, occupancies, and atom/orbital-resolved DOS matrices """ with open(filename, errors="ignore") as f: es_obj = ElectronicStructure() lines = f.readlines() details_trigger = "# of k-points:" details_ready = False kpoint_trigger = "k-point" band_trigger = "band" num_atoms = 0 for i, line in enumerate(lines): line = line.strip() if details_trigger in line: num_kpts, num_bands, num_atoms = self._get_details(line) details_ready = True if details_ready: if kpoint_trigger in line.split(): kpt, weight = self._get_kpoint_details(line) es_obj.add_kpoint(value=kpt, weight=weight) if band_trigger in line.split(): eigenvalue, occupancy = self._get_band_details(line) es_obj.kpoints[-1].add_band( eigenvalue=eigenvalue, occupancy=occupancy, ) band_obj = es_obj.kpoints[0].bands[0][-1] ( band_obj.resolved_dos_matrix, band_obj.orbital_resolved_dos, band_obj.atom_resolved_dos, ) = self._get_dos_matrix(lines[i + 2 : i + num_atoms + 4]) return es_obj
@staticmethod def _check_if_spin_polarized(line: str) -> Optional[bool]: """ Check whether a PROCAR line indicates a spin-polarized calculation. Args: line (str): A line from the PROCAR file Returns: bool or None: True if the line contains a spin-component marker, else None """ if "spin component" in line.lower(): return True return None @staticmethod def _get_details(line: str): """ Parse the header line of a PROCAR file to get the number of k-points, bands and atoms. Args: line (str): The header line containing ``# of k-points: N # of bands: M # of ions: K`` Returns: tuple: (num_kpts, num_bands, num_atoms) as integers """ lst = line.split() num_kpts = int(lst[3]) num_bands = int(lst[7]) num_atoms = int(lst[11]) return num_kpts, num_bands, num_atoms @staticmethod def _get_kpoint_details(line: str): """ Parse a k-point header line from the PROCAR file. Args: line (str): A line of the form ``k-point N : kx ky kz weight= w`` Returns: tuple: list: k-point coordinates [kx, ky, kz] in reciprocal coordinates float: weight of the k-point """ line = line.replace("-", " -") lst = line.split() kpt = [float(lst[i]) for i in range(4, 7)] weight = float(lst[9]) return kpt, weight @staticmethod def _get_band_details(line: str): """ Parse a band header line from the PROCAR file. Args: line (str): A line of the form ``band N # energy E # occ. O`` Returns: tuple: float: eigenvalue (eV) float: occupancy """ lst = line.split() eigval = float(lst[4]) occ = float(lst[7]) return eigval, occ @staticmethod def _get_dos_matrix(lines: list[str]): """ Parse the atom-orbital projection block for a single band from the PROCAR file. Each band block in the PROCAR lists the wavefunction projection onto spherical harmonics for each ion, followed by a ``tot`` line with orbital sums and an overall total. This method extracts the per-atom per-orbital matrix, the orbital-resolved totals, and the atom-resolved totals. Args: lines (list): Lines of the projection block for a single band, including the header and the ``tot`` summary line Returns: tuple: numpy.ndarray: Shape (n_atoms, n_orbitals) - per-atom, per-orbital projections numpy.ndarray: Shape (n_orbitals,) - sum over atoms for each orbital numpy.ndarray: Shape (n_atoms,) - sum over orbitals for each atom """ num_orbitals = len((lines[0].strip()).split()) - 2 num_atoms = len(lines) - 2 dos_matrix = np.zeros((num_atoms, num_orbitals)) orbital_resolved_dos: list[float] = [] atom_resolved_dos: list[float] = [] count = 0 for i, line in enumerate(lines): line = line.strip() lst = line.split() if i not in [0, len(lines) - 1]: dos_matrix[count, :] = np.array( [float(val) for val in lst[1 : len(lst) - 1]] ) count += 1 atom_resolved_dos.append(float(lst[-1])) if i == len(lines) - 1: orbital_resolved_dos = [float(val) for val in lst[1 : len(lst) - 1]] return dos_matrix, np.array(orbital_resolved_dos), np.array(atom_resolved_dos)