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