def _log_results_summary(amset_data, output_parameters): results_summary = [] doping = [d * (1 / bohr_to_cm)**3 for d in amset_data.doping] temps = amset_data.temperatures if output_parameters["calculate_mobility"] and not amset_data.is_metal: logger.info("Average conductivity (σ), Seebeck (S) and mobility (μ)" " results:") headers = ("conc [cm⁻³]", "temp [K]", "σ [S/m]", "S [µV/K]", "μ [cm²/Vs]") for c, t in np.ndindex(amset_data.fermi_levels.shape): results = ( doping[c], temps[t], tensor_average(amset_data.conductivity[c, t]), tensor_average(amset_data.seebeck[c, t]), tensor_average(amset_data.mobility["overall"][c, t]), ) results_summary.append(results) else: logger.info("Average conductivity (σ) and Seebeck (S) results:") headers = ("conc [cm⁻³]", "temp [K]", "σ [S/m]", "S [µV/K]") for c, t in np.ndindex(amset_data.fermi_levels.shape): results = ( doping[c], temps[t], tensor_average(amset_data.conductivity[c, t]), tensor_average(amset_data.seebeck[c, t]), ) results_summary.append(results) table = tabulate( results_summary, headers=headers, numalign="right", stralign="center", floatfmt=(".2e", ".1f", ".2e", ".2e", ".1f"), ) logger.info(table) if output_parameters["separate_mobility"] and not amset_data.is_metal: labels = amset_data.scattering_labels logger.info("Mobility breakdown by scattering mechanism, in cm²/Vs:") headers = ["conc [cm⁻³]", "temp [K]"] + labels results_summary = [] for c, t in np.ndindex(amset_data.fermi_levels.shape): results = [doping[c], temps[t]] results += [ tensor_average(amset_data.mobility[s][c, t]) for s in labels ] results_summary.append(results) table = tabulate( results_summary, headers=headers, numalign="right", stralign="center", floatfmt=[".2e", ".1f"] + [".2e"] * len(labels), ) logger.info(table)
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 test_tensor_average(tensor, expected): assert tensor_average(tensor) == expected