def __init__(self, BztInterpolator, temp_r=np.arange(100, 1400, 100), doping=10. ** np.arange(16, 23), npts_mu=4000, CRTA=1e-14, margin=None): self.CRTA = CRTA self.temp_r = temp_r self.doping = doping self.dosweight = BztInterpolator.data.dosweight lattvec = BztInterpolator.data.get_lattvec() self.epsilon, self.dos, self.vvdos, self.cdos = BL.BTPDOS(BztInterpolator.eband, BztInterpolator.vvband, npts=npts_mu, cband=BztInterpolator.cband) if margin is None: margin = 9. * units.BOLTZMANN * temp_r.max() mur_indices = np.logical_and(self.epsilon > self.epsilon.min() + margin, self.epsilon < self.epsilon.max() - margin) self.mu_r = self.epsilon[mur_indices] N, L0, L1, L2, Lm11 = BL.fermiintegrals( self.epsilon, self.dos, self.vvdos, mur=self.mu_r, Tr=temp_r, dosweight=self.dosweight, cdos=self.cdos) self.efermi = BztInterpolator.data.fermi / units.eV self.mu_r_eV = self.mu_r / units.eV - self.efermi self.nelect = BztInterpolator.data.nelect self.volume = BztInterpolator.data.get_volume() # Compute the Onsager coefficients from those Fermi integrals self.Conductivity_mu, self.Seebeck_mu, self.Kappa_mu, Hall_mu = BL.calc_Onsager_coefficients(L0, L1, L2, self.mu_r, temp_r, self.volume, Lm11=Lm11) # Common properties rescaling self.Conductivity_mu *= CRTA # S / m self.Seebeck_mu *= 1e6 # microvolt / K self.Kappa_mu *= CRTA # W / (m K) self.Hall_carrier_conc_trace_mu = units.Coulomb * 1e-6 / (np.abs(Hall_mu[:, :, 0, 1, 2] + Hall_mu[:, :, 2, 0, 1] + Hall_mu[:, :, 1, 2, 0]) / 3) self.Carrier_conc_mu = (N + self.nelect) / (self.volume / (units.Meter / 100.) ** 3) # Derived properties cond_eff_mass = np.zeros((len(self.temp_r), len(self.mu_r), 3, 3)) for t in range(len(self.temp_r)): for i in range(len(self.mu_r)): try: cond_eff_mass[t, i] = np.linalg.inv(self.Conductivity_mu[t, i]) * self.Carrier_conc_mu[ t, i] * units.qe_SI ** 2 / units.me_SI * 1e6 except np.linalg.LinAlgError: pass self.Effective_mass_mu = cond_eff_mass * CRTA self.Power_Factor_mu = (self.Seebeck_mu @ self.Seebeck_mu) @ self.Conductivity_mu self.Power_Factor_mu *= 1e-9 # milliWatt / m / K**2
def _calculate_transport_properties(amset_data): kmask = amset_data.transport_mask energies = np.vstack([amset_data.energies[spin] for spin in amset_data.spins]) vv = np.vstack([amset_data.velocities_product[spin] for spin in amset_data.spins]) # mask data to remove extra k-points that are not part of the regular # k-point mesh (necessary as BoltzTraP2 doesn't support custom k-point # weights. energies = energies[:, kmask] vv = vv[..., kmask] n_t_size = (len(amset_data.doping), len(amset_data.temperatures)) sigma = np.zeros(n_t_size + (3, 3)) seebeck = np.zeros(n_t_size + (3, 3)) kappa = np.zeros(n_t_size + (3, 3)) # solve sigma, seebeck, kappa and hall using information from all bands for n, t in np.ndindex(n_t_size): sum_rates = [np.sum(amset_data.scattering_rates[s][:, n, t], axis=0) for s in amset_data.spins] lifetimes = 1 / np.vstack(sum_rates) lifetimes = lifetimes[:, kmask] # print(lifetimes.min()) # Nones are required as BoltzTraP2 expects the Fermi and temp as arrays fermi = amset_data.fermi_levels[n, t][None] * units.eV temp = amset_data.temperatures[t][None] # obtain the Fermi integrals epsilon, dos, vvdos, cdos = BTPDOS( energies, vv, scattering_model=lifetimes, npts=len(amset_data.dos.energies)) # epsilon, dos, vvdos, cdos = AMSETDOS( # energies, vv, scattering_model=lifetimes, # npts=len(amset_data.dos.energies), # kpoint_weights=amset_data.kpoint_weights) _, l0, l1, l2, lm11 = fermiintegrals( epsilon, dos, vvdos, mur=fermi, Tr=temp, dosweight=amset_data.dos_weight) volume = (amset_data.structure.lattice.volume * units.Angstrom ** 3) # Compute the Onsager coefficients from Fermi integrals # Don't store the Hall coefficient as we don't have the curvature # information. # TODO: Fix Hall coefficient sigma[n, t], seebeck[n, t], kappa[n, t], _ = \ calc_Onsager_coefficients(l0, l1, l2, fermi, temp, volume) # convert seebeck to µV/K seebeck *= 1e6 return sigma, seebeck, kappa
def _calculate_transport_properties(amset_data, progress_bar: bool = defaults["print_log"] ): n_t_size = (len(amset_data.doping), len(amset_data.temperatures)) sigma = np.zeros(n_t_size + (3, 3)) seebeck = np.zeros(n_t_size + (3, 3)) kappa = np.zeros(n_t_size + (3, 3)) volume = amset_data.structure.volume epsilon, dos = amset_data.tetrahedral_band_structure.get_density_of_states( amset_data.dos.energies, sum_spins=True, use_cached_weights=True) iterable = list(np.ndindex(n_t_size)) if progress_bar: pbar = get_progress_bar(iterable=iterable, desc="transport") else: pbar = iterable # solve sigma, seebeck, kappa and hall using information from all bands for n, t in pbar: lifetimes = { s: 1 / np.sum(amset_data.scattering_rates[s][:, n, t], axis=0) for s in amset_data.spins } # Nones are required as BoltzTraP2 expects the Fermi and temp as arrays fermi = amset_data.fermi_levels[n, t][None] temp = amset_data.temperatures[t][None] # obtain the Fermi integrals vvdos = get_transport_dos( amset_data.tetrahedral_band_structure, amset_data.velocities_product, lifetimes, amset_data.dos.energies, ) _, l0, l1, l2, lm11 = fermiintegrals( epsilon, dos, vvdos, mur=fermi, Tr=temp, dosweight=amset_data.dos.dos_weight) # Compute the Onsager coefficients from Fermi integrals # Don't store the Hall coefficient as we don't have the curvature # information. sigma[n, t], seebeck[n, t], kappa[n, t], _ = calc_Onsager_coefficients( l0, l1, l2, fermi, temp, volume) # convert seebeck to µV/K seebeck *= 1e6 return sigma, seebeck, kappa
def _calculate_transport_properties(amset_data): energies = np.vstack( [amset_data.energies[spin] for spin in amset_data.spins]) vv = np.vstack( [amset_data.velocities_product[spin] for spin in amset_data.spins]) # curvature = np.vstack([amset_data.curvature[spin] for spin in amset_data.spins]) n_t_size = (len(amset_data.doping), len(amset_data.temperatures)) sigma = np.zeros(n_t_size + (3, 3)) seebeck = np.zeros(n_t_size + (3, 3)) kappa = np.zeros(n_t_size + (3, 3)) hall = np.zeros(n_t_size + (3, 3)) # solve sigma, seebeck, kappa and hall using information from all bands for n, t in np.ndindex(n_t_size): sum_rates = [ np.sum(amset_data.scattering_rates[s][:, n, t], axis=0) for s in amset_data.spins ] lifetimes = 1 / np.vstack(sum_rates) # Nones are required as BoltzTraP2 expects the Fermi and temp as arrays fermi = amset_data.fermi_levels[n, t][None] temp = amset_data.temperatures[t][None] # obtain the Fermi integrals epsilon, dos, vvdos, cdos = get_transport_dos( energies, vv, scattering_model=lifetimes, npts=len(amset_data.dos.energies), kpoint_weights=amset_data.kpoint_weights) _, l0, l1, l2, lm11 = fermiintegrals( epsilon, dos, vvdos, cdos=cdos, mur=fermi, Tr=temp, dosweight=amset_data.dos.dos_weight) volume = (amset_data.structure.lattice.volume * units.Angstrom**3) # Compute the Onsager coefficients from Fermi integrals # Don't store the Hall coefficient as we don't have the curvature # information. sigma[n, t], seebeck[n, t], kappa[n, t], _ = \ calc_Onsager_coefficients(l0, l1, l2, fermi, temp, volume) # convert seebeck to µV/K seebeck *= 1e6 return sigma, seebeck, kappa
def _calculate_mobility( amset_data: AmsetData, rate_idx: Union[int, List[int], np.ndarray], pbar_label: str = "mobility", ): if isinstance(rate_idx, int): rate_idx = [rate_idx] volume = amset_data.structure.volume mobility = np.zeros(amset_data.fermi_levels.shape + (3, 3)) epsilon, dos = amset_data.tetrahedral_band_structure.get_density_of_states( amset_data.dos.energies, sum_spins=True, use_cached_weights=True) pbar = get_progress_bar(iterable=list( np.ndindex(amset_data.fermi_levels.shape)), desc=pbar_label) for n, t in pbar: br = { s: np.arange(len(amset_data.energies[s])) for s in amset_data.spins } cb_idx = {s: amset_data.vb_idx[s] + 1 for s in amset_data.spins} if amset_data.doping[n] < 0: band_idx = {s: br[s][cb_idx[s]:] for s in amset_data.spins} else: band_idx = {s: br[s][:cb_idx[s]] for s in amset_data.spins} lifetimes = { s: 1 / np.sum(amset_data.scattering_rates[s][rate_idx, n, t], axis=0) for s in amset_data.spins } # Nones are required as BoltzTraP2 expects the Fermi and temp as arrays fermi = amset_data.fermi_levels[n, t][None] temp = amset_data.temperatures[t][None] # obtain the Fermi integrals for the temperature and doping vvdos = get_transport_dos( amset_data.tetrahedral_band_structure, amset_data.velocities_product, lifetimes, amset_data.dos.energies, band_idx=band_idx, ) c, l0, l1, l2, lm11 = fermiintegrals( epsilon, dos, vvdos, mur=fermi, Tr=temp, dosweight=amset_data.dos.dos_weight) # Compute the Onsager coefficients from Fermi integrals sigma, _, _, _ = calc_Onsager_coefficients(l0, l1, l2, fermi, temp, volume) if amset_data.doping[n] < 0: carrier_conc = amset_data.electron_conc[n, t] else: carrier_conc = amset_data.hole_conc[n, t] # don't use c as we don't use the correct DOS each time # c = -c[0, ...] / (volume / (Meter / 100.)**3) # convert mobility to cm^2/V.s uc = 0.01 / (e * carrier_conc * (1 / bohr_to_cm)**3) mobility[n, t] = sigma[0, ...] * uc return mobility
def compute_properties_doping(self, doping, temp_r=None): """ Calculate all the properties w.r.t. the doping levels in input. Args: doping: numpy array specifying the doping levels When executed, it add the following variable at the BztTransportProperties object: Conductivity_doping, Seebeck_doping, Kappa_doping, Power_Factor_doping, cond_Effective_mass_doping are dictionaries with 'n' and 'p' keys and arrays of dim (len(temp_r),len(doping),3,3) as values. Carriers_conc_doping: carriers concentration for each doping level and T. mu_doping_eV: the chemical potential corrispondent to each doping level. """ if temp_r is None: temp_r = self.temp_r ( self.Conductivity_doping, self.Seebeck_doping, self.Kappa_doping, self.Carriers_conc_doping, ) = ({}, {}, {}, {}) self.Power_Factor_doping, self.Effective_mass_doping = {}, {} mu_doping = {} doping_carriers = [ dop * (self.volume / (units.Meter / 100.0)**3) for dop in doping ] for dop_type in ["n", "p"]: sbk = np.zeros((len(temp_r), len(doping), 3, 3)) cond = np.zeros((len(temp_r), len(doping), 3, 3)) kappa = np.zeros((len(temp_r), len(doping), 3, 3)) hall = np.zeros((len(temp_r), len(doping), 3, 3, 3)) dc = np.zeros((len(temp_r), len(doping))) if dop_type == "p": doping_carriers = [-dop for dop in doping_carriers] mu_doping[dop_type] = np.zeros((len(temp_r), len(doping))) for t, temp in enumerate(temp_r): for i, dop_car in enumerate(doping_carriers): mu_doping[dop_type][t, i] = BL.solve_for_mu( self.epsilon, self.dos, self.nelect + dop_car, temp, self.dosweight, True, False, ) # mu_doping[dop_type][t, i] = self.find_mu_doping( # self.epsilon, self.dos, self.nelect + dop_car, temp, # self.dosweight) N, L0, L1, L2, Lm11 = BL.fermiintegrals( self.epsilon, self.dos, self.vvdos, mur=mu_doping[dop_type][t], Tr=np.array([temp]), dosweight=self.dosweight, ) cond[t], sbk[t], kappa[t], hall[ t] = BL.calc_Onsager_coefficients( L0, L1, L2, mu_doping[dop_type][t], np.array([temp]), self.volume, Lm11, ) dc[t] = self.nelect + N self.Conductivity_doping[dop_type] = cond * self.CRTA # S / m self.Seebeck_doping[dop_type] = sbk * 1e6 # microVolt / K self.Kappa_doping[dop_type] = kappa * self.CRTA # W / (m K) # self.Hall_doping[dop_type] = hall self.Carriers_conc_doping[dop_type] = dc / ( self.volume / (units.Meter / 100.0)**3) self.Power_Factor_doping[dop_type] = ( sbk @ sbk) @ cond * self.CRTA * 1e3 cond_eff_mass = np.zeros((len(temp_r), len(doping), 3, 3)) for t in range(len(temp_r)): for i, dop in enumerate(doping): try: cond_eff_mass[t, i] = np.linalg.inv( cond[t, i]) * dop * units.qe_SI**2 / units.me_SI * 1e6 except np.linalg.LinAlgError: pass self.Effective_mass_doping[dop_type] = cond_eff_mass self.doping = doping self.mu_doping = mu_doping self.mu_doping_eV = { k: v / units.eV - self.efermi for k, v in mu_doping.items() } self.contain_props_doping = True
def __init__( self, BztInterpolator, temp_r=np.arange(100, 1400, 100), doping=None, npts_mu=4000, CRTA=1e-14, margin=None, save_bztTranspProps=False, load_bztTranspProps=False, fname="bztTranspProps.json.gz", ): """ Args: BztInterpolator: a BztInterpolator previously generated temp_r: numpy array of temperatures at which to calculate transport properties doping: doping levels at which to calculate transport properties. If provided, transport properties w.r.t. these doping levels are also computed. See compute_properties_doping() method for details. npts_mu: number of energy points at which to calculate transport properties CRTA: constant value of the relaxation time save_bztTranspProps: Default False. If True all computed transport properties will be stored in fname file. load_bztTranspProps: Default False. If True all computed transport properties will be loaded from fname file. fname: File path where to save/load transport properties. Upon creation, it contains properties tensors w.r.t. the chemical potential of size (len(temp_r),npts_mu,3,3): Conductivity_mu (S/m), Seebeck_mu (microV/K), Kappa_mu (W/(m*K)), Power_Factor_mu (milliW/K m); cond_Effective_mass_mu (m_e) calculated as Ref. Also: Carrier_conc_mu: carrier concentration of size (len(temp_r),npts_mu) Hall_carrier_conc_trace_mu: trace of Hall carrier concentration of size (len(temp_r),npts_mu) mu_r_eV: array of energies in eV and with E_fermi at 0.0 where all the properties are calculated. Example: bztTransp = BztTransportProperties(bztInterp,temp_r = np.arange(100,1400,100)) """ self.dosweight = BztInterpolator.data.dosweight self.volume = BztInterpolator.data.get_volume() self.nelect = BztInterpolator.data.nelect self.efermi = BztInterpolator.data.fermi / units.eV if margin is None: margin = 9.0 * units.BOLTZMANN * temp_r.max() if load_bztTranspProps: self.load(fname) else: self.CRTA = CRTA self.temp_r = temp_r self.doping = doping self.epsilon, self.dos, self.vvdos, self.cdos = BL.BTPDOS( BztInterpolator.eband, BztInterpolator.vvband, npts=npts_mu, cband=BztInterpolator.cband, ) mur_indices = np.logical_and( self.epsilon > self.epsilon.min() + margin, self.epsilon < self.epsilon.max() - margin, ) self.mu_r = self.epsilon[mur_indices] self.mu_r_eV = self.mu_r / units.eV - self.efermi N, L0, L1, L2, Lm11 = BL.fermiintegrals( self.epsilon, self.dos, self.vvdos, mur=self.mu_r, Tr=temp_r, dosweight=self.dosweight, cdos=self.cdos, ) # Compute the Onsager coefficients from those Fermi integrals ( self.Conductivity_mu, self.Seebeck_mu, self.Kappa_mu, Hall_mu, ) = BL.calc_Onsager_coefficients(L0, L1, L2, self.mu_r, temp_r, self.volume, Lm11=Lm11) # Common properties rescaling self.Conductivity_mu *= CRTA # S / m self.Seebeck_mu *= 1e6 # microvolt / K self.Kappa_mu *= CRTA # W / (m K) self.Hall_carrier_conc_trace_mu = ( units.Coulomb * 1e-6 / (np.abs(Hall_mu[:, :, 0, 1, 2] + Hall_mu[:, :, 2, 0, 1] + Hall_mu[:, :, 1, 2, 0]) / 3)) self.Carrier_conc_mu = (N + self.nelect) / (self.volume / (units.Meter / 100.0)**3) # Derived properties cond_eff_mass = np.zeros((len(self.temp_r), len(self.mu_r), 3, 3)) for t in range(len(self.temp_r)): for i in range(len(self.mu_r)): try: cond_eff_mass[t, i] = ( np.linalg.inv(self.Conductivity_mu[t, i]) * self.Carrier_conc_mu[t, i] * units.qe_SI**2 / units.me_SI * 1e6) except np.linalg.LinAlgError: pass self.Effective_mass_mu = cond_eff_mass * CRTA self.Power_Factor_mu = ( self.Seebeck_mu @ self.Seebeck_mu) @ self.Conductivity_mu self.Power_Factor_mu *= 1e-9 # milliWatt / m / K**2 # self.props_as_dict() self.contain_props_doping = False if isinstance(doping, np.ndarray): self.compute_properties_doping(doping, temp_r) if save_bztTranspProps: self.save(fname)
def _calculate_mobility(amset_data: AmsetData, rate_idx: Union[int, List[int], np.ndarray]): if isinstance(rate_idx, int): rate_idx = [rate_idx] n_t_size = (len(amset_data.doping), len(amset_data.temperatures)) all_rates = amset_data.scattering_rates all_vv = amset_data.velocities_product all_energies = amset_data.energies kmask = amset_data.transport_mask mobility = np.zeros(n_t_size + (3, 3)) for n, t in np.ndindex(n_t_size): energies = [] vv = [] rates = [] for spin in amset_data.spins: cb_idx = amset_data.vb_idx[spin] + 1 if amset_data.doping[n] > 0: # electrons energies.append(all_energies[spin][cb_idx:]) vv.append(all_vv[spin][cb_idx:]) rates.append(np.sum(all_rates[spin][rate_idx, n, t, cb_idx:], axis=0)) else: # holes energies.append(all_energies[spin][:cb_idx]) vv.append(all_vv[spin][:cb_idx]) rates.append(np.sum(all_rates[spin][rate_idx, n, t, :cb_idx], axis=0)) energies = np.vstack(energies) vv = np.vstack(vv) lifetimes = 1 / np.vstack(rates) # mask data to remove extra k-points that are not part of the regular # k-point mesh (necessary as BoltzTraP2 doesn't support custom k-point # weights. energies = energies[:, kmask] vv = vv[..., kmask] lifetimes = lifetimes[:, kmask] # Nones are required as BoltzTraP2 expects the Fermi and temp as arrays fermi = amset_data.fermi_levels[n, t][None] * units.eV temp = amset_data.temperatures[t][None] # obtain the Fermi integrals for the temperature and doping epsilon, dos, vvdos, cdos = BTPDOS( energies, vv, scattering_model=lifetimes, npts=len(amset_data.dos.energies)) # epsilon, dos, vvdos, cdos = AMSETDOS( # energies, vv, scattering_model=lifetimes, # npts=len(amset_data.dos.energies), # kpoint_weights=amset_data.kpoint_weights) _, l0, l1, l2, lm11 = fermiintegrals( epsilon, dos, vvdos, mur=fermi, Tr=temp, dosweight=amset_data.dos_weight) # Compute the Onsager coefficients from Fermi integrals volume = (amset_data.structure.lattice.volume * units.Angstrom ** 3) sigma, _, _, _ = calc_Onsager_coefficients( l0, l1, l2, fermi, temp, volume) if amset_data.doping[n] > 0: carrier_conc = amset_data.electron_conc[n, t] else: carrier_conc = amset_data.hole_conc[n, t] # convert mobility to cm^2/V.s mobility[n, t] = sigma[0, ...] * 0.01 / (e * carrier_conc) return mobility
def boltztrap(dirname, bt2file, title, T): print(("\n\nWorking in %s for %s at %i K" % (dirname, title, T))) # If a ready-made file with the interpolation results is available, use it # Otherwise, create the file. if not os.path.exists(bt2file): # Load the input data = BTP.DFTData(dirname) # Select the interesting bands nemin, nemax = data.bandana(emin=data.fermi - .2, emax=data.fermi + .2) # Set up a k point grid with roughly five times the density of the input equivalences = sphere.get_equivalences(data.atoms, len(data.kpoints) * 5) # Perform the interpolation coeffs = fite.fitde3D(data, equivalences) # Save the result serialization.save_calculation( bt2file, data, equivalences, coeffs, serialization.gen_bt2_metadata(data, data.mommat is not None)) # Load the interpolation results print("Load the interpolation results") data, equivalences, coeffs, metadata = serialization.load_calculation( bt2file) # Reconstruct the bands print("Reconstruct the bands") lattvec = data.get_lattvec() eband, vvband, cband = fite.getBTPbands(equivalences, coeffs, lattvec) # Obtain the Fermi integrals for different chemical potentials at # room temperature. TEMP = np.array([T]) epsilon, dos, vvdos, cdos = BL.BTPDOS(eband, vvband, npts=4000) margin = 9. * units.BOLTZMANN * TEMP.max() mur_indices = np.logical_and(epsilon > epsilon.min() + margin, epsilon < epsilon.max() - margin) mur = epsilon[mur_indices] N, L0, L1, L2, Lm11 = BL.fermiintegrals(epsilon, dos, vvdos, mur=mur, Tr=TEMP, dosweight=data.dosweight) # Compute the Onsager coefficients from those Fermi integrals print("Compute the Onsager coefficients") UCvol = data.get_volume() sigma, seebeck, kappa, Hall = BL.calc_Onsager_coefficients( L0, L1, L2, mur, TEMP, UCvol) fermi = BL.solve_for_mu(epsilon, dos, data.nelect, T, data.dosweight) savedata[title + '-%s' % T] = { "sigma": sigma, "seebeck": seebeck, "kappa": kappa, "Hall": Hall, "mu": (mur - fermi) / BL.eV, "temp": T, "n": N[0] + data.nelect }
refine=True) # The flow from this point is again similar to that in parse_integrate, # with the exception that the chemical potentials are different for # each temperature. N = np.empty_like(Tr) L0, L1, L2 = (np.empty((Tr.shape[0], 3, 3)) for ii in range(3)) Lm11 = np.empty((Tr.shape[0], 3, 3, 3)) # Obtain the Fermi integrals required to get the Onsager coefficients for iT, T in enumerate(Tr): (N[iT], L0[iT], L1[iT], L2[iT], Lm11[iT]) = BL.fermiintegrals(epsilon, dos, vvdos, mur=np.array([mur[iT]]), Tr=np.array([T]), dosweight=data.dosweight, cdos=cdos) #N += data.nelect # incorrect because of missing states due to ecut and efcut # ??? N -= N[0, round(N.shape[1] / 2)] # zero doping at low T in the middle of the band gap volume = data.get_volume() # Translate those into Onsager coefficients sigma, seebeck, kappa = (np.empty((Tr.shape[0], 3, 3)) for ii in range(3)) Hall = np.empty((Tr.shape[0], 3, 3, 3)) for iT, T in enumerate(Tr): (sigma[iT], seebeck[iT], kappa[iT], Hall[iT]) = BL.calc_Onsager_coefficients(np.array([[L0[iT]]]), np.array([[L1[iT]]]), np.array([[L2[iT]]]), np.array([mur[iT]]),
# The flow from this point is again similar to that in parse_integrate, # with the exception that the chemical potentials are different for # each temperature. N = np.empty_like(Tr) L0, L1, L2 = (np.empty((Tr.shape[0], 3, 3)) for ii in range(3)) Lm11 = np.empty((Tr.shape[0], 3, 3, 3)) # Obtain the Fermi integrals required to get the Onsager coefficients for iT, T in enumerate(Tr): (N[iT], L0[iT], L1[iT], L2[iT], Lm11[iT]) = BL.fermiintegrals(epsilon, dos, vvdos, mur=np.array([mur[iT]]), Tr=np.array([T]), dosweight=data.dosweight, cdos=cdos, scattering_model='epa', scattering_file=fepa) #N += data.nelect # incorrect because of missing states due to ecut and efcut # ??? N -= N[0, round(N.shape[1] / 2)] # zero doping at low T in the middle of the band gap volume = data.get_volume() # Translate those into Onsager coefficients sigma, seebeck, kappa = (np.empty((Tr.shape[0], 3, 3)) for ii in range(3)) Hall = np.empty((Tr.shape[0], 3, 3, 3)) for iT, T in enumerate(Tr): (sigma[iT], seebeck[iT], kappa[iT], Hall[iT]) = BL.calc_Onsager_coefficients(np.array([[L0[iT]]]), np.array([[L1[iT]]]), np.array([[L2[iT]]]),
def compute_properties_doping(self, doping, temp_r=None): """ Calculate all the properties w.r.t. the doping levels in input. Args: doping: numpy array specifing the doping levels When executed, it add the following variable at the BztTransportProperties object: Conductivity_doping, Seebeck_doping, Kappa_doping, Power_Factor_doping, cond_Effective_mass_doping are dictionaries with 'n' and 'p' keys and arrays of dim (len(temp_r),len(doping),3,3) as values doping_carriers: number of carriers for each doping level mu_doping_eV: the chemical potential corrispondent to each doping level """ if temp_r is None: temp_r = self.temp_r self.Conductivity_doping, self.Seebeck_doping, self.Kappa_doping = {}, {}, {} # self.Hall_doping = {} self.Power_Factor_doping, self.Effective_mass_doping = {}, {} mu_doping = {} doping_carriers = [dop * (self.volume / (units.Meter / 100.) ** 3) for dop in doping] for dop_type in ['n', 'p']: sbk = np.zeros((len(temp_r), len(doping), 3, 3)) cond = np.zeros((len(temp_r), len(doping), 3, 3)) kappa = np.zeros((len(temp_r), len(doping), 3, 3)) hall = np.zeros((len(temp_r), len(doping), 3, 3, 3)) if dop_type == 'p': doping_carriers = [-dop for dop in doping_carriers] mu_doping[dop_type] = np.zeros((len(temp_r), len(doping))) for t, temp in enumerate(temp_r): for i, dop_car in enumerate(doping_carriers): mu_doping[dop_type][t, i] = self.find_mu_doping(self.epsilon, self.dos, self.nelect + dop_car, temp, self.dosweight) N, L0, L1, L2, Lm11 = BL.fermiintegrals(self.epsilon, self.dos, self.vvdos, mur=mu_doping[dop_type][t], Tr=np.array([temp]), dosweight=self.dosweight) cond[t], sbk[t], kappa[t], hall[t] = BL.calc_Onsager_coefficients(L0, L1, L2, mu_doping[dop_type][t], np.array([temp]), self.volume, Lm11) self.Conductivity_doping[dop_type] = cond * self.CRTA # S / m self.Seebeck_doping[dop_type] = sbk * 1e6 # microVolt / K self.Kappa_doping[dop_type] = kappa * self.CRTA # W / (m K) # self.Hall_doping[dop_type] = hall self.Power_Factor_doping[dop_type] = (sbk @ sbk) @ cond * self.CRTA * 1e3 cond_eff_mass = np.zeros((len(temp_r), len(doping), 3, 3)) for t in range(len(temp_r)): for i, dop in enumerate(doping): try: cond_eff_mass[t, i] = np.linalg.inv(cond[t, i]) * dop * units.qe_SI ** 2 / units.me_SI * 1e6 except np.linalg.LinAlgError: pass self.Effective_mass_doping[dop_type] = cond_eff_mass self.doping_carriers = doping_carriers self.doping = doping self.mu_doping = mu_doping self.mu_doping_eV = {k: v / units.eV - self.efermi for k, v in mu_doping.items()}
def __init__(self, BztInterpolator, temp_r=np.arange(100, 1400, 100), doping=10.**np.arange(16, 23), npts_mu=4000, CRTA=1e-14, margin=None): """ Args: BztInterpolator: a BztInterpolator previously generated temp_r: numpy array of temperatures at which to calculate trasport properties doping: doping levels at which to calculate trasport properties npts_mu: number of energy points at which to calculate trasport properties CRTA: constant value of the relaxation time Upon creation, it contains properties tensors w.r.t. the chemical potential of size (len(temp_r),npts_mu,3,3): Conductivity_mu (S/m), Seebeck_mu (microV/K), Kappa_mu (W/(m*K)), Power_Factor_mu (milliW/K); cond_Effective_mass_mu (m_e) calculated as Ref. Also: Carrier_conc_mu: carrier concentration of size (len(temp_r),npts_mu) Hall_carrier_conc_trace_mu: trace of Hall carrier concentration of size (len(temp_r),npts_mu) mu_r_eV: array of energies in eV and with E_fermi at 0.0 where all the properties are calculated. Example: bztTransp = BztTransportProperties(bztInterp,temp_r = np.arange(100,1400,100)) """ self.CRTA = CRTA self.temp_r = temp_r self.doping = doping self.dosweight = BztInterpolator.data.dosweight self.epsilon, self.dos, self.vvdos, self.cdos = BL.BTPDOS( BztInterpolator.eband, BztInterpolator.vvband, npts=npts_mu, cband=BztInterpolator.cband) if margin is None: margin = 9. * units.BOLTZMANN * temp_r.max() mur_indices = np.logical_and( self.epsilon > self.epsilon.min() + margin, self.epsilon < self.epsilon.max() - margin) self.mu_r = self.epsilon[mur_indices] N, L0, L1, L2, Lm11 = BL.fermiintegrals(self.epsilon, self.dos, self.vvdos, mur=self.mu_r, Tr=temp_r, dosweight=self.dosweight, cdos=self.cdos) self.efermi = BztInterpolator.data.fermi / units.eV self.mu_r_eV = self.mu_r / units.eV - self.efermi self.nelect = BztInterpolator.data.nelect self.volume = BztInterpolator.data.get_volume() # Compute the Onsager coefficients from those Fermi integrals self.Conductivity_mu, self.Seebeck_mu, self.Kappa_mu, Hall_mu = BL.calc_Onsager_coefficients( L0, L1, L2, self.mu_r, temp_r, self.volume, Lm11=Lm11) # Common properties rescaling self.Conductivity_mu *= CRTA # S / m self.Seebeck_mu *= 1e6 # microvolt / K self.Kappa_mu *= CRTA # W / (m K) self.Hall_carrier_conc_trace_mu = units.Coulomb * 1e-6 / ( np.abs(Hall_mu[:, :, 0, 1, 2] + Hall_mu[:, :, 2, 0, 1] + Hall_mu[:, :, 1, 2, 0]) / 3) self.Carrier_conc_mu = (N + self.nelect) / (self.volume / (units.Meter / 100.)**3) # Derived properties cond_eff_mass = np.zeros((len(self.temp_r), len(self.mu_r), 3, 3)) for t in range(len(self.temp_r)): for i in range(len(self.mu_r)): try: cond_eff_mass[t, i] = np.linalg.inv( self.Conductivity_mu[t, i]) * self.Carrier_conc_mu[ t, i] * units.qe_SI**2 / units.me_SI * 1e6 except np.linalg.LinAlgError: pass self.Effective_mass_mu = cond_eff_mass * CRTA self.Power_Factor_mu = ( self.Seebeck_mu @ self.Seebeck_mu) @ self.Conductivity_mu self.Power_Factor_mu *= 1e-9 # milliWatt / m / K**2
# Reconstruct the bands lattvec = data.get_lattvec() eband, vvband, cband = fite.getBTPbands(equivalences, coeffs, lattvec) # Obtain the Fermi integrals for different chemical potentials at # room temperature. TEMP = np.array([300.]) epsilon, dos, vvdos, cdos = BL.BTPDOS(eband, vvband, npts=4000) margin = 9. * units.BOLTZMANN * TEMP.max() mur_indices = np.logical_and(epsilon > epsilon.min() + margin, epsilon < epsilon.max() - margin) mur = epsilon[mur_indices] N, L0, L1, L2, Lm11 = BL.fermiintegrals(epsilon, dos, vvdos, mur=mur, Tr=TEMP, dosweight=data.dosweight) # Compute the Onsager coefficients from those Fermi integrals UCvol = data.get_volume() sigma, seebeck, kappa, Hall = BL.calc_Onsager_coefficients( L0, L1, L2, mur, TEMP, UCvol) fermi = BL.solve_for_mu(epsilon, dos, data.nelect, 300, data.dosweight) # Plot the results fig1, ax1 = pl.subplots(1, figsize=(6, 3)) ax1.set_xlim([-1, 1]) ax1.set_ylim([-300, 300]) ax1.plot((mur - fermi) / BL.eV,
def _calculate_mobility(amset_data: AmsetData, rate_idx: Union[int, List[int], np.ndarray]): if isinstance(rate_idx, int): rate_idx = [rate_idx] n_t_size = (len(amset_data.doping), len(amset_data.temperatures)) all_rates = amset_data.scattering_rates all_vv = amset_data.velocities_product all_energies = amset_data.energies # all_curvature = amset_data.curvature mobility = np.zeros(n_t_size + (3, 3)) for n, t in np.ndindex(n_t_size): energies = [] vv = [] # curvature = [] rates = [] for spin in amset_data.spins: cb_idx = amset_data.vb_idx[spin] + 1 if amset_data.doping[n] > 0: # electrons energies.append(all_energies[spin][cb_idx:]) vv.append(all_vv[spin][cb_idx:]) # curvature.append(all_curvature[spin][cb_idx:]) rates.append( np.sum(all_rates[spin][rate_idx, n, t, cb_idx:], axis=0)) else: # holes energies.append(all_energies[spin][:cb_idx]) vv.append(all_vv[spin][:cb_idx]) # curvature.append(all_curvature[spin][:cb_idx]) rates.append( np.sum(all_rates[spin][rate_idx, n, t, :cb_idx], axis=0)) energies = np.vstack(energies) vv = np.vstack(vv) # curvature = np.vstack(curvature) lifetimes = 1 / np.vstack(rates) # Nones are required as BoltzTraP2 expects the Fermi and temp as arrays fermi = amset_data.fermi_levels[n, t][None] temp = amset_data.temperatures[t][None] # obtain the Fermi integrals for the temperature and doping epsilon, dos, vvdos, cdos = get_transport_dos( energies, vv, scattering_model=lifetimes, npts=len(amset_data.dos.energies), kpoint_weights=amset_data.kpoint_weights) c, l0, l1, l2, lm11 = fermiintegrals( epsilon, dos, vvdos, cdos=cdos, mur=fermi, Tr=temp, dosweight=amset_data.dos.dos_weight) c = ((-c[0, ...] - amset_data.dos.nelect) / (amset_data.structure.volume / (units.Meter / 100.)**3)) # Compute the Onsager coefficients from Fermi integrals volume = (amset_data.structure.lattice.volume * units.Angstrom**3) sigma, _, _, _ = calc_Onsager_coefficients(l0, l1, l2, fermi, temp, volume) if amset_data.doping[n] > 0: carrier_conc = amset_data.electron_conc[n, t] else: carrier_conc = amset_data.hole_conc[n, t] print(carrier_conc) print(c) # convert mobility to cm^2/V.s mobility[n, t] = sigma[0, ...] * 0.01 / (e * carrier_conc) return mobility