Source code for rhodent.pdos

from __future__ import annotations

from typing import Generator

import numpy as np
from numpy.typing import NDArray

from gpaw import GPAW
from gpaw.mpi import world, SerialCommunicator

from .utils import gauss_ij, Logger
from .voronoi import VoronoiWeights
from .typing import GPAWCalculator


[docs] class PDOSCalculator: """ PDOS calculator Parameters ---------- voronoi Voronoi weights object energies Array of energies (in eV) for which the broadened PDOS is computed sigma Gaussian broadening width in eV gpw_file Filename of GPAW ground state file zerofermi Eigenvalues relative to Fermi level if ``True``, else relative to vacuum """ def __init__(self, voronoi: VoronoiWeights, energies: list[float] | NDArray[np.float64], sigma: float, gpw_file: str | None = None, zerofermi: bool = False): self._voronoi = voronoi self._energies = np.array(energies) self._sigma = sigma self._zerofermi = zerofermi self._gpw_file = gpw_file try: self._calc = voronoi.voronoi_lcao_gen.calc # type: ignore except AttributeError: # Reading weights from file (either in LCAO basis or KS basis). # No GPAW calculator present self._calc = None assert gpw_file is not None @property def calc(self) -> GPAWCalculator: """ GPAW Calculator object """ if self._calc is None: calc = GPAW(self._gpw_file, communicator=SerialCommunicator(), txt='/dev/null') self._calc = calc return self._calc @property def voronoi(self) -> VoronoiWeights: """ Voronoi weights object """ return self._voronoi @property def log(self) -> Logger: """ Logger """ return self.voronoi.log @property def zero(self) -> float: if self._zerofermi: return self.calc.get_fermi_level() else: return 0 @property def eig_n(self) -> NDArray[np.float64]: return self.calc.get_eigenvalues() - self.zero @property def energies(self) -> NDArray[np.float64]: """ Energy grid in eV """ return self._energies @property def sigma(self) -> float: """ Gaussian broadening width in eV. """ return self._sigma
[docs] def icalculate(self) -> Generator[dict[str, NDArray[np.float64] | None], None, None]: r"""Read eigenvalues and wave functions from ground state and calculate broadened PDOS. The PDOS is projected on each group of atoms in :attr:`atom_projections`. Calculates Voronoi weights from ground state file using LCAO basis function overlaps and PAW corrections, .. math:: W_{nn'} = \left<\psi_n|\hat{w}|\psi_{n'}\right> = \int w(\vec{r}) \psi_n^*(\vec{r}) \psi_{n'}(\vec{r}) d\vec{r} where the operator :math:`\hat{w} = w(\vec{r})` is 1 in the Voronoi region of the atomic projections and 0 outside. Parameters ---------- voronoi Voronoi weights calculator/reader sigma Gaussian broadening width in eV Yields ------- Once per atom group in :attr:`atom_projections` a dictionary with keys * ``weight_n`` - Array of dimensions ``(Nn)`` of projections. ``None`` on non-root ranks. * ``pdos_e`` - Broadened PDOS. ``None`` on non-root ranks. """ if world.rank == 0: # Construct gaussians gauss_en = gauss_ij(self.energies, self.eig_n, self.sigma) self.log('Computed gaussians', flush=True) for i, weight_nn in enumerate(self.voronoi): if world.rank == 0: assert weight_nn is not None weight_n = weight_nn.diagonal() pdos_e = gauss_en @ weight_n self.log(f'Computed PDOS for projection {self.voronoi.atom_projections[i]}', flush=True) yield dict(weight_n=weight_n, pdos_e=pdos_e) else: yield dict(weight_n=None, pdos_e=None)