Ejemplo n.º 1
0
    def __init__(self,
                 band_structure: BandStructure,
                 num_electrons: int,
                 interpolation_factor: float = 20,
                 soc: bool = False,
                 magmom: Optional[np.ndarray] = None,
                 mommat: Optional[np.ndarray] = None,
                 interpolate_projections: bool = False):
        self._band_structure = band_structure
        self._num_electrons = num_electrons
        self._soc = soc
        self._spins = self._band_structure.bands.keys()
        self._interpolate_projections = interpolate_projections
        self.interpolation_factor = interpolation_factor
        self._lattice_matrix = (band_structure.structure.lattice.matrix *
                                units.Angstrom)
        self._coefficients = {}
        self._projection_coefficients = defaultdict(dict)

        kpoints = np.array([k.frac_coords for k in band_structure.kpoints])
        atoms = AseAtomsAdaptor.get_atoms(band_structure.structure)

        logger.info("Getting band interpolation coefficients")

        t0 = time.perf_counter()
        self._equivalences = sphere.get_equivalences(
            atoms=atoms, nkpt=kpoints.shape[0] * interpolation_factor,
            magmom=magmom)

        # get the interpolation mesh used by BoltzTraP2
        self.interpolation_mesh = 2 * np.max(
            np.abs(np.vstack(self._equivalences)), axis=0) + 1

        for spin in self._spins:
            energies = band_structure.bands[spin] * units.eV
            data = DFTData(kpoints, energies, self._lattice_matrix,
                           mommat=mommat)
            self._coefficients[spin] = fite.fitde3D(data, self._equivalences)

        log_time_taken(t0)

        if self._interpolate_projections:
            logger.info("Getting projection interpolation coefficients")

            if not band_structure.projections:
                raise ValueError(
                    "interpolate_projections is True but band structure has no "
                    "projections")

            for spin in self._spins:
                for label, projection in _get_projections(
                        band_structure.projections[spin]):
                    data = DFTData(kpoints, projection, self._lattice_matrix,
                                   mommat=mommat)
                    self._projection_coefficients[spin][label] = fite.fitde3D(
                        data, self._equivalences)
            log_time_taken(t0)
Ejemplo n.º 2
0
def _interpolate_zero_rates(rates, kpoints, masks: Optional = None):
    # loop over all scattering types, doping, temps, and bands and interpolate
    # zero scattering rates based on the nearest k-point
    logger.info("Interpolating missing scattering rates")
    n_rates = sum([np.product(rates[spin].shape[:-1]) for spin in rates])
    pbar = tqdm(total=n_rates,
                ncols=output_width,
                desc="    ├── progress",
                bar_format='{l_bar}{bar}| {elapsed}<{remaining}{postfix}',
                file=sys.stdout)

    n_zero_rates = 0
    n_lots = 0
    t0 = time.perf_counter()
    k_idx = np.arange(len(kpoints))
    for spin in rates:
        for s, d, t, b in np.ndindex(rates[spin].shape[:-1]):

            if masks is not None:
                mask = np.invert(masks[spin][s, d, t, b])
            else:
                mask = [True] * len(rates[spin][s, d, t, b])

            non_zero_rates = rates[spin][s, d, t, b, mask] > 1e7
            # non_zero_rates = rates[spin][s, d, t, b, mask] != 0
            zero_rate_idx = k_idx[mask][~non_zero_rates]
            non_zero_rate_idx = k_idx[mask][non_zero_rates]

            if not np.any(non_zero_rates):
                # all scattering rates are zero so cannot interpolate
                # generally this means the scattering prefactor is zero. E.g.
                # for POP when studying non polar materials
                rates[spin][s, d, t, b, mask] += small_val

            elif np.sum(non_zero_rates) != np.sum(mask):
                n_lots += 1
                # interpolation seems to work best when all the kpoints are +ve
                # therefore add 0.5
                # Todo: Use cartesian coordinates (will be more robust to
                # oddly shaped cells)
                rates[spin][s, d, t, b, zero_rate_idx] = griddata(
                    points=kpoints[non_zero_rate_idx] + 0.5,
                    values=rates[spin][s, d, t, b, non_zero_rate_idx],
                    xi=kpoints[zero_rate_idx] + 0.5,
                    method='nearest')

            pbar.update()
    pbar.close()
    log_time_taken(t0)

    if n_zero_rates > 0:
        logger.warning("WARNING: N zero rates: {:.0f}".format(n_zero_rates /
                                                              n_lots))

    return rates
Ejemplo n.º 3
0
    def compute_volumes(self):
        logger.info("Calculating k-point Voronoi diagram:")
        logger.debug("  ├── num k-points near extra points: {}".format(
            len(self._voronoi_points)))
        t0 = time.perf_counter()

        # after some testing it seems like sorting the points before calculating
        # the Voronoi diagram can speed things up by > 1000x when there are many
        # points
        sorted_idx = np.argsort(self._voronoi_points, axis=0)[:, 1]

        # voro = Voronoi(self._voronoi_points[sorted_idx], qhull_options="Qbb Qc Qz")
        voro = Voronoi(self._voronoi_points[sorted_idx],
                       qhull_options="Qbb Qc Qz")

        # need to unsort regions to get correct points
        inv_sorted_idx = np.argsort(sorted_idx)
        regions = voro.point_region[inv_sorted_idx][self._volume_points_idx]
        indices = np.array(voro.regions)[regions]
        vertices = [voro.vertices[i] for i in indices]

        log_time_taken(t0)

        volumes = self._final_volumes.copy()

        # divide volumes by reciprocal lattice volume to get the fractional volume
        volumes[self._volume_in_final_idx] = self._get_voronoi_volumes(
            indices, vertices)[self._expand_ir] / self._volume

        zero_vols = volumes == 0
        if zero_vols.any():
            logger.warning("{} volumes are zero".format(np.sum(zero_vols)))

        inf_vols: np.ndarray = volumes == np.inf
        if inf_vols.any():
            logger.warning("{} volumes are infinite".format(inf_vols.sum()))

        sum_volumes = volumes.sum()
        vol_diff = sum_volumes - 1

        if abs(vol_diff) > 1e-7:
            logger.warning("Sum of weights does not equal 1 (diff = {:.3f} "
                           "%)... renormalising weights".format(vol_diff *
                                                                100))
            volumes /= sum_volumes

        return volumes
Ejemplo n.º 4
0
    def _get_voronoi_volumes(self, indices, vertices) -> np.ndarray:
        logger.info("Calculating k-point weights:")

        voronoi_info = tqdm(
            list(zip(indices, vertices)),
            total=len(indices),
            ncols=output_width,
            desc="    ├── progress",
            file=sys.stdout,
            bar_format='{l_bar}{bar}| {elapsed}<{remaining}{postfix}')

        t0 = time.perf_counter()
        volumes = Parallel(n_jobs=self._nworkers,
                           prefer="processes")(delayed(_get_volume)(idx, verts)
                                               for idx, verts in voronoi_info)
        log_time_taken(t0)
        return np.array(volumes)
Ejemplo n.º 5
0
    def compute_volumes(self):
        logger.info("Calculating k-point Voronoi diagram:")
        logger.debug("  ├── num k-points near extra points: {}".format(
            len(self._voronoi_points)))
        t0 = time.perf_counter()

        # after some testing it seems like sorting the points before calculating
        # the Voronoi diagram can speed things up by > 1000x when there are many
        # points
        sorted_idx = np.argsort(self._voronoi_points, axis=0)[:, 1]

        # add the QJ option to qhull, necessary to slightly jiggle the points
        voro = Voronoi(self._voronoi_points[sorted_idx],
                       qhull_options="Qbb Qc Qz QJ")

        # need to unsort regions to get correct points
        inv_sorted_idx = np.argsort(sorted_idx)
        regions = voro.point_region[inv_sorted_idx][self._volume_points_idx]
        indices = np.array(voro.regions)[regions]
        vertices = [voro.vertices[i] for i in indices]

        log_time_taken(t0)

        volumes = self._final_volumes.copy()
        volumes[self._volume_in_final_idx] = self._get_voronoi_volumes(
            indices, vertices)

        zero_vols = volumes == 0
        if any(zero_vols):
            logger.warning("{} volumes are zero".format(np.sum(zero_vols)))

        inf_vols: np.ndarray = volumes == np.inf
        if any(inf_vols):
            logger.warning("{} volumes are infinite".format(np.sum(inf_vols)))

        sum_volumes = volumes.sum()
        vol_diff = abs(sum_volumes - 1)
        if vol_diff > 0.01:
            logger.warning(
                "Sum of weights does not equal 1 (diff = {:.1f})... "
                "renormalising weights".format(vol_diff * 100))
            volumes = volumes / sum_volumes

        return volumes
Ejemplo n.º 6
0
    def solve_bte(self, amset_data: AmsetData):
        if not all([amset_data.doping is not None,
                    amset_data.temperatures is not None,
                    amset_data.scattering_rates is not None]):
            raise ValueError("Electronic structure must contain dopings "
                             "temperatures and scattering rates")

        logger.info("Calculating conductivity, Seebeck, and electronic thermal "
                    "conductivity tensors.")
        t0 = time.perf_counter()
        sigma, seebeck, kappa = _calculate_transport_properties(amset_data)
        log_time_taken(t0)

        if not self.calculate_mobility:
            return sigma, seebeck, kappa, None

        if amset_data.is_metal:
            logger.info("System is metallic, refusing to calculate carrier "
                        "mobility")
            return sigma, seebeck, kappa, None

        logger.info("Calculating overall mobility")
        t0 = time.perf_counter()
        mobility = {"overall": _calculate_mobility(
            amset_data, list(range(len(amset_data.scattering_labels))))}
        log_time_taken(t0)

        if self.separate_scattering_mobilities:
            logger.info("Calculating individual scattering rate mobilities")
            t0 = time.perf_counter()
            for rate_idx, name in enumerate(amset_data.scattering_labels):
                mobility[name] = _calculate_mobility(amset_data, rate_idx)
            log_time_taken(t0)

        return sigma, seebeck, kappa, mobility
Ejemplo n.º 7
0
def _interpolate_zero_rates(rates, kpoints):
    # loop over all scattering types, doping, temps, and bands and interpolate
    # zero scattering rates based on the nearest k-point
    logger.info("Interpolating missing scattering rates")
    n_rates = sum([np.product(rates[spin].shape[:-1]) for spin in rates])
    pbar = tqdm(total=n_rates, ncols=output_width, desc="    ├── progress",
                bar_format='{l_bar}{bar}| {elapsed}<{remaining}{postfix}',
                file=sys.stdout)

    n_zero_rates = 0
    n_lots = 0
    t0 = time.perf_counter()
    for spin in rates:
        for s, d, t, b in np.ndindex(rates[spin].shape[:-1]):
            non_zero_rates = rates[spin][s, d, t, b] != 0

            if not any(non_zero_rates):
                # all scattering rates are zero so cannot interpolate
                # generally this means the scattering prefactor is zero. E.g.
                # for POP when studying non polar materials
                rates[spin][s, d, t, b] += small_val

            elif np.sum(non_zero_rates) != len(rates[spin][s, d, t, b]):
                n_zero_rates += sum(non_zero_rates == False)
                n_lots += 1
                rates[spin][s, d, t, b, ~non_zero_rates] = griddata(
                    points=kpoints[non_zero_rates],
                    values=rates[spin][s, d, t, b, non_zero_rates],
                    xi=kpoints[~non_zero_rates], method='nearest')

            pbar.update()
    pbar.close()
    log_time_taken(t0)

    if n_zero_rates > 0:
        print("WARNING: N zero rates: {:.0f}".format(n_zero_rates/n_lots))

    return rates
Ejemplo n.º 8
0
    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_projections: bool = False,
        return_vel_outer_prod: bool = False,
        coords_are_cartesian: bool = False,
        atomic_units: bool = False,
        skip_coefficients: Optional[float] = None,
        symprec: Optional[float] = None,
        return_kpoint_mapping: bool = False,
        return_efermi: bool = False,
        return_vb_idx: bool = False,
        return_scissor: 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_projections: Whether to return the interpolated projections.
            return_vel_outer_prod: Whether to return the outer product of
                velocity, as used by BoltzTraP2 to calculate transport
                properties.
            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_kpoint_mapping: If `True`, the kpoint symmetry mapping information
                will be returned. If ``symprec`` is None then all sites will be
                considered symmetry inequivalent.
            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}``.
            return_scissor: Whether to return the determined scissor value, given in
                Hartree.

        Returns:
            The band energies as dictionary of::

                {spin: energies}

            If ``return_velocity``, ``curvature`` or
            ``return_projections`` a tuple is returned, formatted as::

                (energies, Optional[velocities], Optional[curvature],
                 Optional[projections])

            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"))

        if not self._interpolate_projections and return_projections:
            raise ValueError(
                "Band structure projections needed to obtain full "
                "electronic structure. Reinitialise the "
                "interpolater with interpolate_projections=True")

        n_equivalences = len(self._equivalences)
        if not skip_coefficients or skip_coefficients > 1:
            skip = n_equivalences
        else:
            skip = int(skip_coefficients * n_equivalences)

        # only calculate the energies for the bands within the energy cutoff
        min_e, max_e = _get_energy_cutoffs(energy_cutoff, self._band_structure)
        lattice = self._band_structure.structure.lattice

        if coords_are_cartesian:
            kpoints = lattice.reciprocal_lattice.get_fractional_coords(kpoints)

        nkpoints = len(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,
            )
            similarity_matrix = np.array([
                similarity_transformation(lattice.reciprocal_lattice.matrix, r)
                for r in rot_mapping
            ])
            # similarity_matrix = rot_mapping

            inv_similarity_matrix = np.array(
                [np.linalg.inv(s) for s in similarity_matrix])

            log_list([
                "# original k-points: {}".format(nkpoints),
                "# reduced k-points {}".format(len(kpoints)),
            ])
        else:
            kpoints = np.asarray(kpoints)
            nkpoints = kpoints.shape[0]
            ir_kpoints_idx = np.arange(nkpoints)
            ir_to_full_idx = np.arange(nkpoints)
            weights = np.full(nkpoints, 1 / nkpoints)

        energies = {}
        velocities = {}
        curvature = {}
        projections = defaultdict(dict)
        new_vb_idx = {}
        for spin in self._spins:
            bands = self._band_structure.bands[spin]
            ibands = np.any((bands > min_e) & (bands < max_e), axis=1)

            logger.info("Interpolating {} bands {}-{}".format(
                spin_name[spin],
                np.where(ibands)[0].min() + 1,
                np.where(ibands)[0].max() + 1,
            ))

            t0 = time.perf_counter()
            fitted = fite.getBands(
                kpoints,
                self._equivalences[:skip],
                self._lattice_matrix,
                self._coefficients[spin][ibands, :skip],
                curvature=return_curvature,
            )
            log_time_taken(t0)

            energies[spin] = fitted[0]
            velocities[spin] = fitted[1]

            if symprec:
                energies[spin] = energies[spin][:, ir_to_full_idx]

                # apply rotation matrices to the velocities at the symmetry
                # reduced k-points, to get the velocities for the full
                # original mesh (this is just the dot product of the velocity
                # and appropriate rotation matrix. The weird ordering of the
                # indices is because the velocities has the shape
                # (3, nbands, nkpoints)
                # velocities[spin] = np.einsum(
                #     "kij,jkl->lij",
                #     velocities[spin][:, :, ir_to_full_idx],
                #     similarity_matrix,
                # )
                velocities[spin] = np.einsum(
                    "jkl,kij->lij",
                    similarity_matrix,
                    velocities[spin][:, :, ir_to_full_idx],
                )

            if not self._band_structure.is_metal():
                vb_idx = max(
                    self._band_structure.get_vbm()["band_index"][spin])

                # need to know the index of the valence band after discounting
                # bands during the interpolation. As ibands is just a list of
                # True/False, we can count the number of Trues included up to
                # and including the VBM to get the new number of valence bands
                new_vb_idx[spin] = sum(ibands[:vb_idx + 1]) - 1

            if return_vel_outer_prod:
                # calculate the outer produce of velocities with itself
                # this code is adapted from BoltzTraP2.fite
                iu0 = np.triu_indices(3)
                il1 = np.tril_indices(3, -1)
                iu1 = np.triu_indices(3, 1)

                velocities[spin] = velocities[spin].transpose((1, 0, 2))
                vvband = np.zeros((len(velocities[spin]), 3, 3, nkpoints))
                vvband[:, iu0[0], iu0[1]] = (velocities[spin][:, iu0[0]] *
                                             velocities[spin][:, iu0[1]])
                vvband[:, il1[0], il1[1]] = vvband[:, iu1[0], iu1[1]]
                velocities[spin] = vvband

            if return_curvature:
                curvature[spin] = fitted[2]

                # make curvature have the shape ((nbands, nkpoints, 3, 3)
                curvature[spin] = curvature[spin].transpose((2, 3, 0, 1))

                if symprec:
                    curvature[spin] = curvature[spin][:, ir_to_full_idx, ...]
                    new_curvature = np.empty(curvature[spin].shape)
                    for b_idx, k_idx in np.ndindex(curvature[spin].shape[:2]):
                        new_curvature[b_idx, k_idx] = np.dot(
                            inv_similarity_matrix[k_idx],
                            np.dot(curvature[spin][b_idx, k_idx],
                                   similarity_matrix[k_idx]),
                        )
                    curvature[spin] = new_curvature

            if not atomic_units:
                energies[spin] = energies[spin] / units.eV
                velocities[spin] = _convert_velocities(velocities[spin],
                                                       lattice.matrix)

                if return_curvature:
                    curvature[spin] = _convert_curvature(curvature[spin])

            if return_projections:
                logger.info("Interpolating {} projections".format(
                    spin_name[spin]))

                t0 = time.perf_counter()
                for label, proj_coeffs in self._projection_coefficients[
                        spin].items():
                    projections[spin][label] = fite.getBands(
                        kpoints,
                        self._equivalences[:skip],
                        self._lattice_matrix,
                        proj_coeffs[ibands, :skip],
                        curvature=False,
                    )[0]

                    if symprec:
                        projections[spin][label] = projections[spin][
                            label][:, ir_to_full_idx]

                log_time_taken(t0)

        if not self._band_structure.is_metal():
            energies, scissor = _shift_energies(
                energies,
                new_vb_idx,
                scissor=scissor,
                bandgap=bandgap,
                return_scissor=True,
            )
        else:
            scissor = 0

        if not (return_velocity or return_curvature or return_projections
                or return_kpoint_mapping or return_efermi or return_vb_idx
                or return_scissor):
            return energies

        to_return = [energies]

        if return_velocity:
            to_return.append(velocities)

        if return_curvature:
            to_return.append(curvature)

        if return_projections:
            to_return.append(projections)

        if symprec and return_kpoint_mapping:
            to_return.append({
                "weights": weights,
                "ir_kpoints_idx": ir_kpoints_idx,
                "ir_to_full_idx": ir_to_full_idx,
            })

        if return_efermi:
            if self._band_structure.is_metal():
                efermi = self._band_structure.efermi
                if atomic_units:
                    efermi *= units.eV
            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 return_scissor:
            to_return.append(scissor)

        return tuple(to_return)
Ejemplo n.º 9
0
    def get_amset_data(
        self,
        energy_cutoff: Optional[float] = None,
        scissor: float = None,
        bandgap: float = None,
        symprec: float = 0.01,
        nworkers: int = -1,
    ) -> 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"))

        if not self._interpolate_projections:
            raise ValueError(
                "Band structure projections needed to obtain full "
                "electronic structure. Reinitialise the "
                "interpolater with interpolate_projections=True")

        nworkers = multiprocessing.cpu_count() if nworkers == -1 else nworkers

        str_kmesh = "x".join(map(str, self.interpolation_mesh))
        logger.info("Interpolation parameters:")
        log_list([
            "k-point mesh: {}".format(str_kmesh),
            "energy cutoff: {} eV".format(energy_cutoff),
        ])

        # only calculate the energies for the bands within the energy cutoff
        min_e, max_e = _get_energy_cutoffs(energy_cutoff, self._band_structure)

        energies = {}
        vvelocities = {}
        curvature = {}
        projections = defaultdict(dict)
        new_vb_idx = {}
        for spin in self._spins:
            bands = self._band_structure.bands[spin]
            ibands = np.any((bands > min_e) & (bands < max_e), axis=1)

            logger.info("Interpolating {} bands {}-{}".format(
                spin_name[spin],
                np.where(ibands)[0].min() + 1,
                np.where(ibands)[0].max() + 1,
            ))

            t0 = time.perf_counter()
            energies[spin], vvelocities[spin], curvature[
                spin] = fite.getBTPbands(
                    self._equivalences,
                    self._coefficients[spin][ibands],
                    self._lattice_matrix,
                    curvature=True,
                    nworkers=nworkers,
                )
            log_time_taken(t0)

            if not is_metal:
                # Need the largest VB index. Sometimes the index of the band
                # containing the VBM is not the largest index of all VBs, so
                # find all bands with energies less than the VBM and count that
                # number
                vbm_energy = self._band_structure.get_vbm()["energy"]
                vb_idx = np.any(bands <= vbm_energy, axis=1).sum() - 1

                # need to know the index of the valence band after discounting
                # bands during the interpolation (i.e., if energy_cutoff is
                # set). As ibands is just a list of True/False, we can count the
                # number of Trues up to and including the VBM to get the new
                # number of valence bands
                new_vb_idx[spin] = sum(ibands[:vb_idx + 1]) - 1

            logger.info("Interpolating {} projections".format(spin_name[spin]))
            t0 = time.perf_counter()

            for label, proj_coeffs in self._projection_coefficients[
                    spin].items():
                projections[spin][label] = fite.getBTPbands(
                    self._equivalences,
                    proj_coeffs[ibands],
                    self._lattice_matrix,
                    nworkers=nworkers,
                )[0]

            log_time_taken(t0)

        if is_metal:
            efermi = self._band_structure.efermi * units.eV
            scissor = 0.0
        else:
            energies, scissor = _shift_energies(
                energies,
                new_vb_idx,
                scissor=scissor,
                bandgap=bandgap,
                return_scissor=True,
            )

            # 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 = get_kpoints(
            self.interpolation_mesh,
            self._band_structure.structure,
            symprec=symprec,
            return_full_kpoints=True,
        )

        return AmsetData(
            self._band_structure.structure,
            energies,
            vvelocities,
            projections,
            self.interpolation_mesh,
            full_kpts,
            ir_kpts,
            ir_kpts_idx,
            ir_to_full_idx,
            efermi,
            is_metal,
            self._soc,
            vb_idx=new_vb_idx,
            scissor=scissor,
            # curvature=curvature,
        )
Ejemplo n.º 10
0
    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_effective_mass: bool = False,
                     return_projections: bool = False,
                     return_vel_outer_prod: bool = True,
                     coords_are_cartesian: bool = False,
                     atomic_units: bool = False,
                     skip_coefficients: Optional[float] = None,
                     ) -> 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_effective_mass: Whether to return the band effective masses.
            return_projections: Whether to return the interpolated projections.
            return_vel_outer_prod: Whether to return the outer product of
                velocity, as used by BoltzTraP2 to calculate transport
                properties.
            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 effective masses in units of electron rest mass, m0.

        Returns:
            The band energies as dictionary of::

                {spin: energies}

            If ``return_velocity``, ``return_effective_mass`` or
            ``return_projections`` a tuple is returned, formatted as::

                (energies, Optional[velocities], Optional[effective_masses],
                 Optional[projections])

            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"))

        if not self._interpolate_projections and return_projections:
            raise ValueError("Band structure projections needed to obtain full "
                             "electronic structure. Reinitialise the "
                             "interpolater with interpolate_projections=True")

        n_equivalences = len(self._equivalences)
        if not skip_coefficients or skip_coefficients > 1:
            skip = n_equivalences
        else:
            skip = int(skip_coefficients * n_equivalences)

        # only calculate the energies for the bands within the energy cutoff
        if energy_cutoff and self._band_structure.is_metal():
            min_e = self._band_structure.efermi - energy_cutoff
            max_e = self._band_structure.efermi + energy_cutoff
        elif energy_cutoff:
            min_e = self._band_structure.get_vbm()['energy'] - energy_cutoff
            max_e = self._band_structure.get_cbm()['energy'] + energy_cutoff
        else:
            min_e = min([self._band_structure.bands[spin].min()
                         for spin in self._spins])
            max_e = max([self._band_structure.bands[spin].max()
                         for spin in self._spins])

        if coords_are_cartesian:
            kpoints = self._band_structure.structure.lattice. \
                reciprocal_lattice.get_fractional_coords(kpoints)

        kpoints = np.asarray(kpoints)

        energies = {}
        velocities = {}
        effective_masses = {}
        projections = defaultdict(dict)
        for spin in self._spins:
            ibands = np.any((self._band_structure.bands[spin] > min_e) &
                            (self._band_structure.bands[spin] < max_e), axis=1)

            logger.info("Interpolating {} bands {}-{}".format(
                spin_name[spin], np.where(ibands)[0].min() + 1,
                np.where(ibands)[0].max() + 1))

            t0 = time.perf_counter()
            fitted = fite.getBands(
                kpoints, self._equivalences[:skip], self._lattice_matrix,
                self._coefficients[spin][ibands, :skip],
                curvature=return_effective_mass)
            log_time_taken(t0)

            energies[spin] = fitted[0]
            velocities[spin] = fitted[1]

            if not self._band_structure.is_metal():
                vb_idx = max(self._band_structure.get_vbm()["band_index"][spin])

                # need to know the index of the valence band after discounting
                # bands during the interpolation. As ibands is just a list of
                # True/False, we can count the number of Trues included up to
                # and including the VBM to get the new number of valence bands
                new_vb_idx = sum(ibands[: vb_idx + 1]) - 1
                energies[spin] = _shift_energies(
                    energies[spin], new_vb_idx, scissor=scissor,
                    bandgap=bandgap)

            if return_vel_outer_prod:
                # calculate the outer produce of velocities with itself
                # this code is adapted from BoltzTraP2.fite
                iu0 = np.triu_indices(3)
                il1 = np.tril_indices(3, -1)
                iu1 = np.triu_indices(3, 1)
                velocities[spin] = velocities[spin].transpose((1, 0, 2))
                vvband = np.zeros((len(velocities[spin]), 3, 3, len(kpoints)))
                vvband[:, iu0[0], iu0[1]] = (velocities[spin][:, iu0[0]] *
                                             velocities[spin][:, iu0[1]])
                vvband[:, il1[0], il1[1]] = vvband[:, iu1[0], iu1[1]]
                velocities[spin] = vvband

            if return_effective_mass:
                effective_masses[spin] = fitted[2]

            if not atomic_units:
                energies[spin] = energies[spin] / units.eV
                velocities[spin] = _convert_velocities(
                    velocities[spin],
                    self._band_structure.structure.lattice.matrix)

                if return_effective_mass:
                    effective_masses[spin] = _convert_effective_masses(
                        effective_masses[spin])

            if return_projections:
                logger.info("Interpolating {} projections".format(
                    spin_name[spin]))

                t0 = time.perf_counter()
                for label, proj_coeffs in self._projection_coefficients[
                        spin].items():
                    projections[spin][label] = fite.getBands(
                        kpoints, self._equivalences[:skip],
                        self._lattice_matrix, proj_coeffs[ibands, :skip],
                        curvature=False)[0]
                log_time_taken(t0)

        if not (return_velocity or return_effective_mass or
                return_projections):
            return energies

        to_return = [energies]

        if return_velocity:
            to_return.append(velocities)

        if return_effective_mass:
            to_return.append(effective_masses)

        if return_projections:
            to_return.append(projections)

        return tuple(to_return)