# -*- coding: utf-8 -*-
"""This module defines the ``Dos`` class."""
from attr import field
from nptyping import NDArray
from pathlib import Path
from nanotools.base import Base, Quantity
from nanotools.utils import to_quantity, ureg
import attr
import h5py
import numpy as np
# TODO: efermi type
# custom converters
def pdos_conv(pdos):
if pdos is None:
return None
if isinstance(pdos, list):
pdos = np.array(pdos)
if isinstance(pdos, str):
path = Path(pdos).absolute()
if not path.exists():
Warning(f"File {pdos} not found.")
return None
f = h5py.File(pdos, "r")
if "total" in f["dos"]["pdos"].keys():
pdos = f["dos"]["pdos"]["total"][0:].T
elif "spin-up" in f["dos"]["pdos"].keys():
pdosu = f["dos"]["pdos"]["spin-up"][0:].T
pdosd = f["dos"]["pdos"]["spin-down"][0:].T
pdos = np.stack((pdosu, pdosd), axis=2)
pdos /= ureg.hartree
return pdos.to("1 / eV")
[docs]
@attr.s(auto_detect=True, eq=False)
class Dos(Base):
"""``Dos`` class.
The ``Dos`` class stores density of states data. It is typically empty before a calculation.
It gets overwritten during a DOS calculation.
Attributes:
dos:
Density of states.
Examples::
dos = system.dos.dos
efermi:
``efermi`` is the Fermi energy.
Examples::
ef = system.dos.efermi
energy:
Energies at which the DOS is computed.
Examples::
e = system.dos.energy
interval:
Interval on which the DOS is calculated. The interval is relative to the Fermi energy and is sampled uniformly.
Examples::
dos.interval = [-5.,5.]
pdos_return:
If ``pdos_return`` is True, the partial DOS is calculated and saved in the HDF5 file.
Examples::
dos.pdos_return = True
orbA:
Atomic index of each partial DOS. This is useful to further analyze pDOS results.
Examples::
orbA = system.dos.orbA
orbL:
Orbital angular momentum of each partial DOS. This is useful to further analyze pDOS results.
Examples::
orbL = system.dos.orbL
orbM:
z-component of the orbital angular momentum of each partial DOS. This is useful to further analyze pDOS results.
Examples::
orbM = system.dos.orbM
pdos:
Parital density of states. Each column is the pDOS of one atomic orbital.
Examples::
pdos = system.dos.pdos
resolution:
``resolution`` gives the sampling density in the energy interval.
Examples::
dos.resolution = 0.01
transmission:
"""
dos: Quantity = field(
default=None,
converter=attr.converters.optional(lambda x: to_quantity(x, "1 / eV")),
validator=attr.validators.optional(
attr.validators.instance_of(Quantity),
),
)
efermi: Quantity = field(
default=None,
converter=attr.converters.optional(lambda x: to_quantity(x, "eV")),
validator=attr.validators.optional(
attr.validators.instance_of(Quantity),
),
)
energy: Quantity = field(
default=None,
converter=attr.converters.optional(lambda x: to_quantity(x, "eV", shape=(-1))),
validator=attr.validators.optional(
attr.validators.instance_of(Quantity),
),
)
interval: Quantity = field(
factory=lambda: [-10.0, 10.0] * ureg.eV,
converter=attr.converters.optional(lambda x: to_quantity(x, "eV")),
validator=attr.validators.optional(
attr.validators.instance_of(Quantity),
),
)
pdos_return: bool = field(
default=False,
validator=attr.validators.instance_of(bool),
)
orbA: NDArray = field(
default=None,
converter=attr.converters.optional(np.array),
validator=attr.validators.optional(
attr.validators.instance_of(NDArray),
),
)
orbL: NDArray = field(
default=None,
converter=attr.converters.optional(np.array),
validator=attr.validators.optional(
attr.validators.instance_of(NDArray),
),
)
orbM: NDArray = field(
default=None,
converter=attr.converters.optional(np.array),
validator=attr.validators.optional(
attr.validators.instance_of(NDArray),
),
)
pdos: Quantity = field(
default=None,
converter=pdos_conv,
validator=attr.validators.optional(
attr.validators.instance_of(Quantity),
),
)
resolution: Quantity = field(
default=0.025 * ureg.eV,
converter=attr.converters.optional(lambda x: to_quantity(x, "eV")),
validator=attr.validators.optional(
attr.validators.instance_of(Quantity),
),
)
transmission: NDArray = field(
default=None,
converter=attr.converters.optional(np.array),
validator=attr.validators.optional(
attr.validators.instance_of(NDArray),
),
)
def __attrs_post_init__(self):
if self.energy is None:
n = (self.interval[1] - self.interval[0]) / self.resolution
n = int(np.around(n))
self.energy = np.linspace(self.interval[0], self.interval[1], num=n + 1)
else:
self.set_energy(self.energy)
if isinstance(self.dos, Quantity):
self.dos = np.reshape(self.dos, (self.energy.size, -1), order="F")
def set_energy(self, energy):
self.energy = to_quantity(energy, "eV", shape=(-1), allow_none=False)
if len(self.energy) <= 1:
self.resolution = 0.0 * ureg.eV
else:
self.resolution = (self.energy[-1] - self.energy[0]) / (
len(self.energy) - 1
)
self.interval = Quantity.from_list([self.energy[0], self.energy[-1]])
def __eq__(self, other):
if other.__class__ is not self.__class__:
return NotImplemented
valid = True
for at in (
"dos",
"efermi",
"energy",
"interval",
"orbA",
"orbL",
"orbM",
"pdos",
"resolution",
):
if getattr(self, at) is None:
valid = valid and getattr(self, at) == getattr(other, at)
else:
valid = valid and np.allclose(getattr(self, at), getattr(other, at))
return valid and (
self.dos.u,
self.efermi.u,
self.energy.u,
self.interval.u,
self.pdos_return,
self.pdos.u,
self.resolution.u,
) == (
other.dos.u,
other.efermi.u,
other.energy.u,
other.interval.u,
other.pdos_return,
other.pdos.u,
other.resolution.u,
)