Source code for vaspparser.dft.bader

# 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.

import os
import subprocess
from typing import Optional, Tuple

import numpy as np
from ase.atoms import Atoms

from vaspparser.vasp.volumetric_data import VaspVolumetricData

__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__ = "May 1, 2021"


[docs] class Bader: """ Module to apply the Bader charge partitioning scheme to finished DFT jobs. This module is interfaced with the `Bader code`_ from the Henkelman group. .. _Bader code: http://theory.cm.utexas.edu/henkelman/code/bader """
[docs] def __init__(self, structure: Atoms, working_directory: str) -> None: """ Initialize the Bader module. Args: structure (ase.atoms.Atoms): The atomistic structure of the finished DFT calculation working_directory (str): Path to the directory containing the VASP output files (must contain AECCAR0 and AECCAR2 for total and valence charge densities) """ self._working_directory = working_directory self._structure = structure
def _create_cube_files(self) -> None: """ Create CUBE format files of the valence and total charge densities for use by the Bader program. Reads AECCAR0 (core charge) and AECCAR2 (valence charge) from the working directory, sums them to obtain the total charge density, and writes both the valence charge (``valence_charge.CUBE``) and total charge (``total_charge.CUBE``) to disk. """ cd_val, cd_total = get_valence_and_total_charge_density( working_directory=self._working_directory ) cd_val.write_cube_file( filename=os.path.join(self._working_directory, "valence_charge.CUBE") ) cd_total.write_cube_file( filename=os.path.join(self._working_directory, "total_charge.CUBE") )
[docs] def compute_bader_charges( self, extra_arguments: Optional[str] = None ) -> Tuple[np.ndarray, np.ndarray]: """ Run Bader analysis on the output from the DFT job Args: extra_arguments (str): Extra arguments to the Bader program Returns: tuple: Charges and volumes as numpy arrays """ self._create_cube_files() error_code = call_bader( foldername=self._working_directory, extra_arguments=extra_arguments ) if error_code > 0: self._remove_cube_files() raise ValueError("Invoking Bader charge analysis failed!") self._remove_cube_files() return self._parse_charge_vol()
def _remove_cube_files(self) -> None: """ Delete created CUBE files """ os.remove(os.path.join(self._working_directory, "valence_charge.CUBE")) os.remove(os.path.join(self._working_directory, "total_charge.CUBE")) def _parse_charge_vol(self) -> Tuple[np.ndarray, np.ndarray]: """ Parse Bader charges and volumes Returns: tuple: charges and volumes """ filename = os.path.join(self._working_directory, "ACF.dat") return parse_charge_vol_file(structure=self._structure, filename=filename)
[docs] def call_bader(foldername: str, extra_arguments: Optional[str] = None) -> int: """ Call the Bader program inside a given folder Args: foldername (str): Folder path extra_arguments (str): Extra arguments to the Bader program Returns: int: Result from the subprocess call (>0 if an error occurs) """ if extra_arguments is None: extra_arguments = "" cmd = f"bader valence_charge.CUBE -ref total_charge.CUBE {extra_arguments}" return subprocess.call(cmd, shell=True, cwd=foldername)
[docs] def parse_charge_vol_file( structure: Atoms, filename: str = "ACF.dat" ) -> Tuple[np.ndarray, np.ndarray]: """ Parse charges and volumes from the output file Args: structure (pyiron_atomistics.atomistics.structure.atoms.Atoms): The snapshot to be analyzed filename (str): Filename of the output file Returns: tuple: charges and volumes """ with open(filename, errors="ignore") as f: lines = f.readlines() charges = np.genfromtxt(lines[2:], max_rows=len(structure))[:, 4] volumes = np.genfromtxt(lines[2:], max_rows=len(structure))[:, 6] return charges, volumes
[docs] def get_valence_and_total_charge_density( working_directory: str, ) -> Tuple[VaspVolumetricData, VaspVolumetricData]: """ Read the valence and total all-electron charge densities from AECCAR files. VASP writes the core charge density to AECCAR0 and the valence charge density to AECCAR2 when ``LAECHG = .TRUE.`` is set in the INCAR. The total all-electron charge density is the sum of both. These files are required for the Bader charge partitioning scheme (Henkelman group). Args: working_directory (str): Path to the directory containing AECCAR0 and AECCAR2 Returns: tuple: VaspVolumetricData: Valence charge density (from AECCAR2) VaspVolumetricData: Total all-electron charge density (AECCAR0 + AECCAR2) """ cd_core = VaspVolumetricData() cd_total = VaspVolumetricData() cd_val = VaspVolumetricData() if os.path.isfile(working_directory + "/AECCAR0"): cd_core.from_file(working_directory + "/AECCAR0") cd_val.from_file(working_directory + "/AECCAR2") cd_val.atoms = cd_val.atoms cd_total.total_data = cd_core.total_data + cd_val.total_data cd_total.atoms = cd_val.atoms return cd_val, cd_total