def from_band_structure( cls, band_structure: BandStructure, energy_cutoff=defaults["energy_cutoff"], symprec=defaults["symprec"], ): kpoints = np.array([k.frac_coords for k in band_structure.kpoints]) efermi = band_structure.efermi structure = band_structure.structure full_kpoints, ir_to_full_idx, rot_mapping = expand_kpoints( structure, kpoints, symprec=symprec ) ibands = get_ibands(energy_cutoff, band_structure) vb_idx = get_vb_idx(energy_cutoff, band_structure) # energies = { # s: e[ibands[s], ir_to_full_idx] for s, e in band_structure.bands.items() # } energies = {s: e[ibands[s]] for s, e in band_structure.bands.items()} energies = {s: e[:, ir_to_full_idx] for s, e in energies.items()} projections = {s: p[ibands[s]] for s, p in band_structure.projections.items()} band_centers = get_band_centers(full_kpoints, energies, vb_idx, efermi) return cls( structure, kpoints, projections, band_centers, kpoint_symmetry_mapping=(full_kpoints, ir_to_full_idx, rot_mapping), )
def from_band_structure( cls, band_structure: BandStructure, energy_cutoff=defaults["energy_cutoff"], symprec=defaults["symprec"], ): kpoints = np.array([k.frac_coords for k in band_structure.kpoints]) efermi = band_structure.efermi structure = band_structure.structure full_kpoints, _, _, _, _, ir_to_full_idx = expand_kpoints( structure, kpoints, symprec=symprec, return_mapping=True, time_reversal=True) ibands = get_ibands(energy_cutoff, band_structure) vb_idx = get_vb_idx(energy_cutoff, band_structure) energies = {s: e[ibands[s]] for s, e in band_structure.bands.items()} energies = {s: e[:, ir_to_full_idx] for s, e in energies.items()} projections = { s: p[ibands[s]] for s, p in band_structure.projections.items() } band_centers = get_band_centers(full_kpoints, energies, vb_idx, efermi) rotation_mask = get_rotation_mask(projections) full_projections = {} for spin, spin_projections in projections.items(): nbands = spin_projections.shape[0] nkpoints = len(full_kpoints) spin_projections = spin_projections[:, ir_to_full_idx].reshape( (nbands, nkpoints, -1), order="F") spin_projections /= np.linalg.norm(spin_projections, axis=2)[..., None] spin_projections[np.isnan(spin_projections)] = 0 full_projections[spin] = spin_projections return cls.from_data( full_kpoints, full_projections, rotation_mask=rotation_mask, band_centers=band_centers, )
def get_energies( self, kpoints: Union[np.ndarray, List], energy_cutoff: Optional[float] = None, scissor: float = None, bandgap: float = None, return_velocity: bool = False, return_curvature: bool = False, return_other_properties: bool = False, coords_are_cartesian: bool = False, atomic_units: bool = False, symprec: Optional[float] = defaults["symprec"], return_efermi: bool = False, return_vb_idx: bool = False, ) -> Union[Dict[Spin, np.ndarray], Tuple[Dict[Spin, np.ndarray], ...]]: """Gets the interpolated energies for multiple k-points in a band. Note, the accuracy of the interpolation is dependant on the ``interpolate_factor`` used to initialize the Interpolater. Args: kpoints: The k-point coordinates. 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. return_velocity: Whether to return the band velocities. return_curvature: Whether to return the band curvature (inverse effective mass). return_other_properties: Whether to return the interpolated results for the ``other_properties`` data. coords_are_cartesian: Whether the kpoints are in cartesian or fractional coordinates. atomic_units: Return the energies, velocities, and effective_massses in atomic units. If False, energies will be in eV, velocities in cm/s, and curvature in units of 1 / electron rest mass (1/m0). symprec: Symmetry precision. If set, symmetry will be used to reduce the nummber of calculated k-points and velocities. return_efermi: Whether to return the Fermi level with the unit determined by ``atomic_units``. If the system is semiconducting the Fermi level will be given in the middle of the band gap. return_vb_idx: Whether to return the index of the highest valence band in the interpolated bands. Will be returned as a dictionary of ``{spin: vb_idx}``. Returns: The band energies as dictionary of:: {spin: energies} If ``return_velocity``, ``curvature`` or ``return_other_properties`` a tuple is returned, formatted as:: (energies, Optional[velocities], Optional[curvature], Optional[other_properties]) The velocities and effective masses are given as the 1x3 trace and full 3x3 tensor, respectively (along cartesian directions). The projections are summed for each orbital type (s, p, d) across all atoms, and are given as:: {spin: {orbital: projections}} """ if self._band_structure.is_metal() and (bandgap or scissor): raise ValueError("{} option set but system is metallic".format( "bandgap" if bandgap else "scissor")) # only calculate the energies for the bands within the energy cutoff lattice = self._band_structure.structure.lattice kpoints = np.asarray(kpoints) nkpoints = len(kpoints) if coords_are_cartesian: kpoints = lattice.reciprocal_lattice.get_fractional_coords(kpoints) if symprec: logger.info("Reducing # k-points using symmetry") ( kpoints, weights, ir_kpoints_idx, ir_to_full_idx, _, rot_mapping, ) = get_symmetry_equivalent_kpoints( self._band_structure.structure, kpoints, symprec=symprec, return_inverse=True, time_reversal_symmetry=not self._soc, ) k_info = [ "# original k-points: {}".format(nkpoints), "# reduced k-points {}".format(len(kpoints)), ] log_list(k_info) ibands = get_ibands(energy_cutoff, self._band_structure) new_vb_idx = get_vb_idx(energy_cutoff, self._band_structure) energies = {} velocities = {} curvature = {} other_properties = defaultdict(dict) for spin in self._spins: spin_ibands = ibands[spin] min_b = spin_ibands.min() + 1 max_b = spin_ibands.max() + 1 info = "Interpolating {} bands {}-{}".format( spin_name[spin], min_b, max_b) logger.info(info) t0 = time.perf_counter() fitted = fite.getBands( kpoints, self._equivalences, self._lattice_matrix, self._coefficients[spin][spin_ibands], curvature=return_curvature, ) log_time_taken(t0) energies[spin] = fitted[0] velocities[spin] = fitted[1] if return_curvature: # make curvature have the shape ((nbands, nkpoints, 3, 3) curvature[spin] = fitted[2] curvature[spin] = curvature[spin].transpose((2, 3, 0, 1)) if return_other_properties: logger.info("Interpolating {} properties".format( spin_name[spin])) t0 = time.perf_counter() for label, coeffs in self._other_coefficients[spin].items(): other_properties[spin][label], _ = fite.getBands( kpoints, self._equivalences, self._lattice_matrix, coeffs[spin_ibands], curvature=False, ) log_time_taken(t0) if not atomic_units: energies[spin] = energies[spin] * hartree_to_ev velocities[spin] = _convert_velocities(velocities[spin], lattice.matrix) if symprec: energies, velocities, curvature, other_properties = symmetrize_results( energies, velocities, curvature, other_properties, ir_to_full_idx, rot_mapping, self._band_structure.structure.lattice.reciprocal_lattice. matrix, ) if not self._band_structure.is_metal(): energies = _shift_energies(energies, new_vb_idx, scissor=scissor, bandgap=bandgap) to_return = [energies] if return_velocity: to_return.append(velocities) if return_curvature: to_return.append(curvature) if return_other_properties: to_return.append(other_properties) if return_efermi: if self._band_structure.is_metal(): efermi = self._band_structure.efermi if atomic_units: efermi *= ev_to_hartree else: # if semiconducting, set Fermi level to middle of gap efermi = _get_efermi(energies, new_vb_idx) to_return.append(efermi) if return_vb_idx: to_return.append(new_vb_idx) if len(to_return) == 1: return to_return[0] else: return tuple(to_return)
def get_amset_data( self, energy_cutoff: Optional[float] = None, scissor: float = None, bandgap: float = None, symprec: float = defaults["symprec"], nworkers: int = defaults["nworkers"], ) -> AmsetData: """Gets an AmsetData object using the interpolated bands. Note, the interpolation mesh is determined using by ``interpolate_factor`` option in the ``Inteprolater`` constructor. This method is much faster than the ``get_energies`` function but doesn't provide as much flexibility. The degree of parallelization is controlled by the ``nworkers`` option. Args: 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. symprec: The symmetry tolerance used when determining the symmetry inequivalent k-points on which to interpolate. nworkers: The number of processors used to perform the interpolation. If set to ``-1``, the number of workers will be set to the number of CPU cores. Returns: The electronic structure (including energies, velocities, density of states and k-point information) as an AmsetData object. """ is_metal = self._band_structure.is_metal() if is_metal and (bandgap or scissor): raise ValueError("{} option set but system is metallic".format( "bandgap" if bandgap else "scissor")) nworkers = multiprocessing.cpu_count() if nworkers == -1 else nworkers logger.info("Interpolation parameters:") iinfo = [ "k-point mesh: {}".format("x".join( map(str, self.interpolation_mesh))), "energy cutoff: {} eV".format(energy_cutoff), ] log_list(iinfo) ibands = get_ibands(energy_cutoff, self._band_structure) new_vb_idx = get_vb_idx(energy_cutoff, self._band_structure) energies = {} vvelocities = {} velocities = {} forgotten_electrons = 0 for spin in self._spins: spin_ibands = ibands[spin] min_b = spin_ibands.min() + 1 max_b = spin_ibands.max() + 1 info = "Interpolating {} bands {}-{}".format( spin_name[spin], min_b, max_b) logger.info(info) # these are bands beneath the Fermi level that are dropped forgotten_electrons += min_b - 1 t0 = time.perf_counter() energies[spin], vvelocities[spin], _, velocities[ spin] = get_bands_fft( self._equivalences, self._coefficients[spin][spin_ibands], self._lattice_matrix, return_effective_mass=False, nworkers=nworkers, ) log_time_taken(t0) if not self._soc and len(self._spins) == 1: forgotten_electrons *= 2 nelectrons = self._num_electrons - forgotten_electrons if is_metal: efermi = self._band_structure.efermi * ev_to_hartree else: energies = _shift_energies(energies, new_vb_idx, scissor=scissor, bandgap=bandgap) # if material is semiconducting, set Fermi level to middle of gap efermi = _get_efermi(energies, new_vb_idx) # get the actual k-points used in the BoltzTraP2 interpolation # unfortunately, BoltzTraP2 doesn't expose this information so we # have to get it ourselves ( ir_kpts, _, full_kpts, ir_kpts_idx, ir_to_full_idx, tetrahedra, *ir_tetrahedra_info, ) = get_kpoints_tetrahedral( self.interpolation_mesh, self._band_structure.structure, symprec=symprec, time_reversal_symmetry=not self._soc, ) energies, vvelocities, velocities = sort_amset_results( full_kpts, energies, vvelocities, velocities) atomic_structure = get_atomic_structure(self._band_structure.structure) return AmsetData( atomic_structure, energies, vvelocities, velocities, self.interpolation_mesh, full_kpts, ir_kpts, ir_kpts_idx, ir_to_full_idx, tetrahedra, ir_tetrahedra_info, efermi, nelectrons, is_metal, self._soc, vb_idx=new_vb_idx, )
def test_get_vb_idx_tricky(band_structures, system, energy_cutoff, expected): result = get_vb_idx(energy_cutoff, band_structures[system]) assert result == expected