예제 #1
0
def _shift_energies(
    energies: Dict[Spin, np.ndarray],
    vb_idx: Dict[Spin, int],
    scissor: Optional[float] = None,
    bandgap: Optional[float] = None,
) -> Union[Dict[Spin, np.ndarray], Tuple[Dict[Spin, np.ndarray], float]]:
    """Shift the band energies based on the scissor or bandgap parameter.

    Args:
        energies: The band energies in Hartree, given for each Spin channel.
        vb_idx: The band index of the valence band maximum in the energies
            array, given for each Spin channel.
        scissor: The amount by which the band gap is scissored. Cannot
            be used in conjunction with the ``bandgap`` option. Has no
            effect for metallic systems.
        bandgap: Automatically adjust the band gap to this value. Cannot
            be used in conjunction with the ``scissor`` option. Has no
            effect for metallic systems.

    Returns:
        The energies, shifted according to ``scissor`` or ``bandgap``. If
        return_scissor is True, a tuple of (energies, scissor) is returned.
    """

    if scissor and bandgap:
        raise ValueError("scissor and bandgap cannot be set simultaneously")

    if bandgap:
        e_vbm = get_vbm_energy(energies, vb_idx)
        e_cbm = get_cbm_energy(energies, vb_idx)
        interp_bandgap = (e_cbm - e_vbm) * hartree_to_ev

        scissor = bandgap - interp_bandgap
        logger.info(
            f"bandgap set to {bandgap:.3f} eV, applying scissor of {scissor:.3f} eV"
        )

    if scissor:
        scissor *= ev_to_hartree
        for spin, spin_vb_idx in vb_idx.items():
            spin_cb_idx = spin_vb_idx + 1
            if spin_cb_idx != 0:
                # if spin_cb_idx == 0 there are no valence bands for this spin channel
                energies[spin][:spin_cb_idx] -= scissor / 2

            if spin_cb_idx != energies[spin].shape[0]:
                # if spin_cb_idx == nbands there are no conduction bands for this spin
                energies[spin][spin_cb_idx:] += scissor / 2

    return energies
예제 #2
0
    def calculate_fd_cutoffs(
        self,
        fd_tolerance: Optional[float] = 0.01,
        cutoff_pad: float = 0.0,
        max_moment: int = 2,
        mobility_rates_only: bool = False,
    ):
        energies = self.dos.energies
        vv = {
            s: v.transpose((0, 3, 1, 2))
            for s, v in self.velocities_product.items()
        }
        _, vvdos = self.tetrahedral_band_structure.get_density_of_states(
            energies, integrand=vv, sum_spins=True, use_cached_weights=True)
        vvdos = tensor_average(vvdos)
        # vvdos = np.array(self.dos.get_densities())

        # three fermi integrals govern transport properties:
        #   1. df/de controls conductivity and mobility
        #   2. (e-u) * df/de controls Seebeck
        #   3. (e-u)^2 df/de controls electronic thermal conductivity
        # take the absolute sum of the integrals across all doping and
        # temperatures. this gives us the energies that are important for
        # transport
        if fd_tolerance:

            def get_min_max_cutoff(cumsum):
                min_idx = np.where(cumsum < fd_tolerance / 2)[0].max()
                max_idx = np.where(cumsum > (1 - fd_tolerance / 2))[0].min()
                return energies[min_idx], energies[max_idx]

            min_cutoff = np.inf
            max_cutoff = -np.inf
            for n, t in np.ndindex(self.fermi_levels.shape):
                ef = self.fermi_levels[n, t]
                temp = self.temperatures[t]
                dfde = -dfdde(energies, ef, temp * boltzmann_au)

                for moment in range(max_moment + 1):
                    weight = np.abs((energies - ef)**moment * dfde)
                    weight_dos = weight * vvdos
                    weight_cumsum = np.cumsum(weight_dos)
                    weight_cumsum /= np.max(weight_cumsum)

                    cmin, cmax = get_min_max_cutoff(weight_cumsum)
                    min_cutoff = min(cmin, min_cutoff)
                    max_cutoff = max(cmax, max_cutoff)

                    # import matplotlib.pyplot as plt
                    # ax = plt.gca()
                    # plt.plot(energies / units.eV, weight / weight.max())
                    # plt.plot(energies / units.eV, vvdos / vvdos.max())
                    # plt.plot(energies / units.eV, weight_dos / weight_dos.max())
                    # plt.plot(energies / units.eV, weight_cumsum / weight_cumsum.max())
                    # ax.set(xlim=(4, 7.5))
                    # plt.show()

        else:
            min_cutoff = energies.min()
            max_cutoff = energies.max()

        if mobility_rates_only:
            vbm = get_vbm_energy(self.energies, self.vb_idx)
            cbm = get_cbm_energy(self.energies, self.vb_idx)
            mid_gap = (cbm + vbm) / 2
            if np.all(self.doping < 0):
                # only electron mobility so don't calculate valence band rates
                min_cutoff = max(min_cutoff, mid_gap)
            elif np.all(self.doping < 0):
                # only hole mobility so don't calculate conudction band rates
                max_cutoff = min(max_cutoff, mid_gap)

        min_cutoff -= cutoff_pad
        max_cutoff += cutoff_pad

        logger.info("Calculated Fermi–Dirac cut-offs:")
        log_list([
            "min: {:.3f} eV".format(min_cutoff * hartree_to_ev),
            "max: {:.3f} eV".format(max_cutoff * hartree_to_ev),
        ])
        self.fd_cutoffs = (min_cutoff, max_cutoff)
예제 #3
0
    def get_dos(
        self,
        kpoint_mesh: Union[float, int, List[int]],
        energy_cutoff: Optional[float] = None,
        scissor: Optional[float] = None,
        bandgap: Optional[float] = None,
        estep: float = defaults["dos_estep"],
        symprec: float = defaults["symprec"],
        atomic_units: bool = False,
    ) -> Union[Dos, FermiDos]:
        """Calculates the density of states using the interpolated bands.

        Args:
            kpoint_mesh: The k-point mesh as a 1x3 array. E.g.,``[6, 6, 6]``.
                Alternatively, if a single value is provided this will be
                treated as a reciprocal density and the k-point mesh dimensions
                generated automatically.
            energy_cutoff: The energy cut-off to determine which bands are
                included in the interpolation. If the energy of a band falls
                within the cut-off at any k-point it will be included. For
                metals the range is defined as the Fermi level ± energy_cutoff.
                For gapped materials, the energy range is from the VBM -
                energy_cutoff to the CBM + energy_cutoff.
            scissor: The amount by which the band gap is scissored. Cannot
                be used in conjunction with the ``bandgap`` option. Has no
                effect for metallic systems.
            bandgap: Automatically adjust the band gap to this value. Cannot
                be used in conjunction with the ``scissor`` option. Has no
                effect for metallic systems.
            estep: The energy step, where smaller numbers give more
                accuracy but are more expensive.
            symprec: The symmetry tolerance used when determining the symmetry
                inequivalent k-points on which to interpolate.
            atomic_units: Whether to return the DOS in atomic units. If False, the
                unit of energy will be eV.

        Returns:
            The density of states.
        """
        if isinstance(kpoint_mesh, numeric_types):
            logger.info(f"DOS k-point length cutoff: {kpoint_mesh}")
        else:
            str_mesh = "x".join(map(str, kpoint_mesh))
            logger.info(f"DOS k-point mesh: {str_mesh}")

        structure = self._band_structure.structure
        tri = not self._soc
        (
            ir_kpts,
            _,
            full_kpts,
            ir_kpts_idx,
            ir_to_full_idx,
            tetrahedra,
            *ir_tetrahedra_info,
        ) = get_kpoints_tetrahedral(
            kpoint_mesh, structure, symprec=symprec, time_reversal_symmetry=tri
        )

        energies, efermi, vb_idx = self.get_energies(
            ir_kpts,
            scissor=scissor,
            bandgap=bandgap,
            energy_cutoff=energy_cutoff,
            atomic_units=atomic_units,
            return_efermi=True,
            return_vb_idx=True,
        )

        if not self._band_structure.is_metal():
            # if not a metal, set the Fermi level to the top of the valence band.
            efermi = get_vbm_energy(energies, vb_idx)

        full_energies = {s: e[:, ir_to_full_idx] for s, e in energies.items()}
        tetrahedral_band_structure = TetrahedralBandStructure.from_data(
            full_energies,
            full_kpts,
            tetrahedra,
            structure,
            ir_kpts_idx,
            ir_to_full_idx,
            *ir_tetrahedra_info,
        )

        emin = np.min([np.min(spin_eners) for spin_eners in energies.values()])
        emax = np.max([np.max(spin_eners) for spin_eners in energies.values()])
        epoints = int(round((emax - emin) / estep))
        energies = np.linspace(emin, emax, epoints)

        _, dos = tetrahedral_band_structure.get_density_of_states(energies)

        return FermiDos(efermi, energies, dos, structure, atomic_units=atomic_units)
예제 #4
0
def test_get_vbm_energy(band_structures, system, vb_idx, expected):
    result = get_vbm_energy(band_structures[system].bands, vb_idx)
    assert result == expected