Example #1
0
def fermiintegrals(epsilon, dos, sigma, mur, Tr, dosweight=2.0, cdos=None):
    """Compute the moments of the FD distribution over the band structure.

    Args:
        epsilon: array of energies at which the DOS is available
        dos: density of states
        sigma: transport DOS
        mur: array of chemical potential values
        Tr: array of temperature values
        dosweight: maximum occupancy of an electron mode
        cdos: "curvature DOS" if available

    Returns:
        Five numpy arrays, namely:
        1. An (nT, nmu) array with the electron counts for each temperature and
           each chemical potential.
        2. An (nT, nmu, 3, 3) with the integrals of the 3 x 3 transport DOS
           over the band structure taking the occupancies into account.
        3. An (nT, nmu, 3, 3) with the first moment of the 3 x 3 transport DOS
           over the band structure taking the occupancies into account.
        4. An (nT, nmu, 3, 3) with the second moment of the 3 x 3 transport DOS
           over the band structure taking the occupancies into account.
        5. If the cdos argument is provided, an (nT, nmu, 3, 3, 3) with the
           integrals of the 3 x 3 x 3 "curvature DOS" over the band structure
           taking the occupancies into account.
        where nT and nmu are the sizes of Tr and mur, respectively.
    """
    kBTr = np.array(Tr) * BOLTZMANN
    nT = len(Tr)
    nmu = len(mur)
    N = np.empty((nT, nmu))
    L0 = np.empty((nT, nmu, 3, 3))
    L1 = np.empty((nT, nmu, 3, 3))
    L2 = np.empty((nT, nmu, 3, 3))
    if cdos is not None:
        L11 = np.empty((nT, nmu, 3, 3, 3))
    else:
        L11 = None
    de = epsilon[1] - epsilon[0]
    for iT, kBT in enumerate(kBTr):
        for imu, mu in enumerate(mur):
            N[iT, imu] = -(dosweight * dos * fd(epsilon, mu, kBT)).sum() * de
            int0 = -dosweight * dfdde(epsilon, mu, kBT)
            intn = int0 * sigma
            L0[iT, imu] = intn.sum(axis=2) * de
            intn *= epsilon - mu
            L1[iT, imu] = -intn.sum(axis=2) * de
            intn *= epsilon - mu
            L2[iT, imu] = intn.sum(axis=2) * de
            if cdos is not None:
                cint = int0 * cdos
                L11[iT, imu] = -cint.sum(axis=3) * de
    return N, L0, L1, L2, L11
Example #2
0
    def _get_conductivity(self, n_idx, t_idx):
        velocities = self._get_group_velocity()
        lifetimes = 1 / self._get_scattering_rates(n_idx, t_idx)

        energies = self._get_energies()
        _, weights = np.unique(self.ir_to_full_kpoint_mapping,
                               return_counts=True)

        ef = self.fermi_levels[n_idx, t_idx]
        temp = self.temperatures[t_idx]

        dfde = -dfdde(energies, ef, temp * boltzmann_au)
        nkpoints = len(self.kpoints)

        integrand = velocities**2 * lifetimes * dfde * weights[
            None, :] / nkpoints
        conductivity = np.sum(integrand)

        return integrand / conductivity
Example #3
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)
Example #4
0
    def plot_rates_to_axis(
        self,
        ax,
        doping_idx,
        temperature_idx,
        plot_type="rate",
        separate_rates=True,
        plot_total_rate: bool = False,
        plot_fd_tols: bool = True,
        show_legend: bool = True,
        legend_kwargs=None,
        ymin=None,
        ymax=None,
        xmin=None,
        xmax=None,
        normalize_energy=0,
        scaling_factor=1,
        total_color=None,
        show_dfde=False,
    ):
        rates = self.plot_rates[:, doping_idx, temperature_idx]
        if separate_rates:
            sort_idx = np.argsort(self.scattering_labels)
            labels = self.scattering_labels[sort_idx].tolist()
            rates = rates[sort_idx]
        else:
            rates = np.sum(rates, axis=0)[None, ...]
            labels = ["rates"]

        if legend_kwargs is None:
            legend_kwargs = {}

        labels = deepcopy(labels)
        if plot_total_rate:
            # add total rates column
            rates = np.concatenate((rates, np.sum(rates, axis=0)[None, ...]))
            labels += ["total"]

        min_fd = self.fd_cutoffs[0]
        max_fd = self.fd_cutoffs[1]
        if not xmin:
            min_e = min_fd
        else:
            min_e = xmin + normalize_energy

        if not xmax:
            max_e = max_fd
        else:
            max_e = xmax + normalize_energy

        energies = self.plot_energies.ravel()
        energy_mask = (energies > min_e) & (energies < max_e)
        energies = energies[energy_mask]

        dfde_occ = -dfdde(
            energies,
            self.fermi_levels[doping_idx, temperature_idx],
            boltzmann_ev * self.temperatures[temperature_idx],
        )

        plt_rates = {}
        for label, rate in zip(labels, rates):
            rate = rate.ravel()[energy_mask]
            if plot_type == "lifetime":
                rate = 1 / rate
            elif plot_type == "v2tau":
                rate = self.plot_norm_velocities.ravel()[energy_mask] ** 2 / rate
            elif plot_type == "v2taudfde":
                rate = (
                    self.plot_norm_velocities.ravel()[energy_mask] ** 2
                    * dfde_occ
                    / rate
                )
            elif plot_type == "rate":
                pass
            else:
                raise ValueError(f"Unknown plot_type: {plot_type}")

            rate *= scaling_factor
            plt_rates[label] = rate

        rates_in_cutoffs = np.array(list(plt_rates.values()))
        ylim = get_lim(
            rates_in_cutoffs[rates_in_cutoffs > 0], ymin, ymax, True, self.pad
        )

        # convert energies to eV and normalise
        norm_energies = energies - normalize_energy
        norm_min_fd = min_fd - normalize_energy
        norm_max_fd = max_fd - normalize_energy
        xlim = (min_e - normalize_energy, max_e - normalize_energy)

        if total_color is None:
            total_color = base_total_color

        colors = matplotlib.rcParams["axes.prop_cycle"].by_key()["color"]
        for i, (label, rate) in enumerate(plt_rates.items()):
            if label == "total":
                c = total_color
            else:
                c = colors[i]

            legend_c = c
            if show_dfde:
                c = _get_lightened_colors(c, dfde_occ / np.max(dfde_occ))
                # sort on lightness to put darker points on top of lighter ones
                sort_idx = np.argsort(dfde_occ)
                norm_energies_sort = norm_energies[sort_idx]
                rate = rate[sort_idx]
                c = c[sort_idx]
            else:
                norm_energies_sort = norm_energies

            ax.scatter(norm_energies_sort, rate, c=c, rasterized=True)
            # hack to plot get the correct label if using dfde occupation
            ax.scatter(-10000000, 0, c=legend_c, label=label)

        if plot_fd_tols:
            ax.plot((norm_min_fd, norm_min_fd), ylim, c="gray", ls="--", alpha=0.5)
            ax.plot(
                (norm_max_fd, norm_max_fd),
                ylim,
                c="gray",
                ls="--",
                alpha=0.5,
                label="FD cutoffs",
            )

        scaling_string = f"{scaling_factor:g} " if scaling_factor != 1 else ""
        data_label = _fmt_data[plot_type][self.label_key]
        data_unit = _fmt_data[plot_type]["unit"]
        ylabel = f"{data_label} ({scaling_string}{data_unit})"
        xlabel = _fmt_data["energy"][self.label_key]

        ax.set(xlabel=xlabel, ylabel=ylabel, ylim=ylim, xlim=xlim)
        ax.semilogy()

        if show_legend:
            ax.legend(**legend_kwargs)