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
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)
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)
def test_get_vbm_energy(band_structures, system, vb_idx, expected): result = get_vbm_energy(band_structures[system].bands, vb_idx) assert result == expected