def _interpolate_zero_rates( rates, kpoints, masks: Optional = None, progress_bar: bool = defaults["print_log"], ): # 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]) if progress_bar: pbar = get_progress_bar(total=n_rates, desc="progress") else: pbar = None 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): # electronic_structure 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", ) # rates[spin][s, d, t, b, zero_rate_idx] = 1e15 if pbar is not None: pbar.update() if pbar is not None: pbar.close() log_time_taken(t0) return rates
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 desymmetrize_deformation_potentials(deformation_potentials, structure, rotations, op_mapping, kp_mapping, pbar=True): logger.info("Desymmetrizing deformation potentials") t0 = time.perf_counter() rlat = structure.lattice.reciprocal_lattice.matrix sims = np.array([similarity_transformation(rlat, r) for r in rotations]) inv_sims = np.array([np.linalg.inv(s) for s in sims]) sims = sims[op_mapping] inv_sims = inv_sims[op_mapping] all_deformation_potentials = {} for spin, spin_deformation_potentials in deformation_potentials.items(): all_deformation_potentials[spin] = np.zeros( (len(spin_deformation_potentials), len(sims), 3, 3)) state_idxs = list( np.ndindex((len(spin_deformation_potentials), len(sims)))) if pbar: state_idxs = get_progress_bar(state_idxs, desc="progress") for b_idx, k_idx in state_idxs: map_idx = kp_mapping[k_idx] sim = sims[k_idx] inv_sim = inv_sims[k_idx] inner = np.dot(sim, spin_deformation_potentials[b_idx, map_idx]) rot_deform = np.abs(np.dot(inner, inv_sim)) # inner = np.dot(spin_deformation_potentials[b_idx, map_idx], inv_sim) # rot_deform = np.abs(np.dot(sim, inner)) # inner = np.dot(spin_deformation_potentials[b_idx, map_idx], sim) # rot_deform = np.abs(np.dot(inv_sim, inner)) all_deformation_potentials[spin][b_idx, k_idx] = rot_deform log_time_taken(t0) return all_deformation_potentials
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 calculate_band_rates(self, spin: Spin, b_idx: int): vol = self.amset_data.structure.lattice.reciprocal_lattice.volume conversion = vol / (4 * np.pi**2) kpoints_idx = self.amset_data.ir_kpoints_idx nkpoints = len(kpoints_idx) band_energies = self.amset_data.energies[spin][b_idx, kpoints_idx] mask = band_energies < self.scattering_energy_cutoffs[0] mask |= band_energies > self.scattering_energy_cutoffs[1] fill_mask = mask[self.amset_data.ir_to_full_kpoint_mapping] n = np.sum(~fill_mask) logger.info( " ├── # k-points within Fermi–Dirac cut-offs: {}".format(n)) k_idx_in_cutoff = kpoints_idx[~mask] ir_idx_in_cutoff = np.arange(nkpoints)[~mask] iterable = list(zip(k_idx_in_cutoff, ir_idx_in_cutoff)) to_stack = [] if len(self.basic_scatterers) > 0: basic_rates = np.array([ m.rates[spin][:, :, b_idx, kpoints_idx] for m in self.basic_scatterers ]) to_stack.append(basic_rates) if len(self.elastic_scatterers) > 0: elastic_prefactors = conversion * np.array( [m.prefactor(spin, b_idx) for m in self.elastic_scatterers]) elastic_rates = np.zeros(elastic_prefactors.shape + (nkpoints, )) if len(k_idx_in_cutoff) > 0: if self.progress_bar: pbar = get_progress_bar(iterable, desc="elastic") else: pbar = iterable for k_idx, ir_idx in pbar: elastic_rates[..., ir_idx] = self.calculate_rate( spin, b_idx, k_idx) elastic_rates *= elastic_prefactors[..., None] to_stack.append(elastic_rates) if len(self.inelastic_scatterers) > 0: inelastic_prefactors = conversion * np.array( [m.prefactor(spin, b_idx) for m in self.inelastic_scatterers]) inelastic_rates = np.zeros(inelastic_prefactors.shape + (nkpoints, )) f_pop = self.settings["pop_frequency"] energy_diff = f_pop * 1e12 * 2 * np.pi * hbar * ev_to_hartree if len(k_idx_in_cutoff) > 0: if self.progress_bar: pbar = get_progress_bar(iterable, desc="inelastic") else: pbar = iterable inelastic_rates[:, :, :, ir_idx_in_cutoff] = 0 for k_idx, ir_idx in pbar: for ediff in [energy_diff, -energy_diff]: inelastic_rates[:, :, :, ir_idx] += self.calculate_rate( spin, b_idx, k_idx, energy_diff=ediff) inelastic_rates *= inelastic_prefactors[..., None] to_stack.append(inelastic_rates) all_band_rates = np.vstack(to_stack) return all_band_rates[ ..., self.amset_data.ir_to_full_kpoint_mapping], fill_mask
def get_spin_density_of_states( self, spin, energies, integrand=None, band_idx=None, use_cached_weights=False, progress_bar=False, ): # integrand should have the shape (nbands, n_ir_kpts, 3, 3) # the integrand should have been summed at all equivalent k-points # TODO: add support for variable shaped integrands if integrand is None: dos = np.zeros_like(energies) else: dos = np.zeros((len(energies), 3, 3)) if use_cached_weights: if self._weights_cache is None: raise ValueError("No integrand have been cached") all_weights = self._weights_cache[spin] all_weights_mask = self._weights_mask_cache[spin] energies = self._energies_cache[spin] else: all_weights = [] all_weights_mask = [] nbands = len(self.energies[spin]) kpoint_multiplicity = np.tile(self.ir_kpoint_weights, (nbands, 1)) if band_idx is not None and integrand is not None: integrand = integrand[band_idx] if band_idx is not None and integrand is None: kpoint_multiplicity = kpoint_multiplicity[band_idx] energies_iter = list(enumerate(energies)) if progress_bar: energies_iter = get_progress_bar(iterable=energies_iter, desc="DOS") for i, energy in energies_iter: if use_cached_weights: weights = all_weights[i] weights_mask = all_weights_mask[i] else: weights = self.get_energy_dependent_integration_weights( spin, energy) weights_mask = weights != 0 all_weights.append(weights) all_weights_mask.append(weights_mask) if band_idx is not None: weights = weights[band_idx] weights_mask = weights_mask[band_idx] if integrand is None: dos[i] = np.sum(weights[weights_mask] * kpoint_multiplicity[weights_mask]) else: # don't need to include the k-point multiplicity as this is included by # pre-summing the integrand at symmetry equivalent points dos[i] = np.sum(weights[weights_mask, None, None] * integrand[weights_mask], axis=0) if not use_cached_weights: self._weights_cache[spin] = np.array(all_weights) self._weights_mask_cache[spin] = np.array(all_weights_mask) self._energies_cache[spin] = energies return energies, np.asarray(dos)
def initialize_workers(self): if self._basic_only: return logger.info( f"Forking {self.nworkers} processes to calculate scattering") t0 = time.perf_counter() if isinstance(self.amset_data.overlap_calculator, ProjectionOverlapCalculator): overlap_type = "projection" else: overlap_type = "wavefunction" if self._coeffs is None: coeffs_buffer = None coeffs_mapping_buffer = None else: coeffs_buffer, self._coeffs = create_shared_dict_array( self._coeffs, return_shared_data=True) coeffs_mapping_buffer, self._coeffs_mapping = create_shared_dict_array( self._coeffs_mapping, return_shared_data=True) amset_data_min = _AmsetDataMin.from_amset_data(self.amset_data) amset_data_min_reference = amset_data_min.to_reference() # deformation potential is a large tensor that should be put into shared memory elastic_scatterers = [ s.to_reference() if isinstance( s, AcousticDeformationPotentialScattering) else s for s in self.elastic_scatterers ] ctx = multiprocessing.get_context("spawn") self.in_queue = ctx.Queue() self.out_queue = ctx.Queue() args = ( self.amset_data.tetrahedral_band_structure.to_reference(), overlap_type, self.amset_data.overlap_calculator.to_reference(), self.amset_data.mrta_calculator.to_reference(), elastic_scatterers, self.inelastic_scatterers, amset_data_min_reference, coeffs_buffer, coeffs_mapping_buffer, self.in_queue, self.out_queue, ) self.workers = [] for _ in range(self.nworkers): self.workers.append( ctx.Process(target=scattering_worker, args=args)) iterable = self.workers if self.progress_bar: iterable = get_progress_bar(self.workers, desc="workers") for w in iterable: w.start() log_time_taken(t0) return self.workers
def desymmetrize_coefficients( coeffs, gpoints, kpoints, structure, rotations, translations, is_tr, op_mapping, kp_mapping, pbar=True, ): logger.info("Desymmetrizing wavefunction coefficients") t0 = time.perf_counter() ncl = is_ncl(coeffs) rots = rotations[op_mapping] taus = translations[op_mapping] trs = is_tr[op_mapping] su2s = None if ncl: # get cartesian rotation matrix r_cart = [ similarity_transformation(structure.lattice.matrix.T, r.T) for r in rotations ] # calculate SU(2) su2_no_dagger = np.array([rotation_matrix_to_su2(r) for r in r_cart]) # calculate SU(2)^{dagger} su2 = np.conjugate(su2_no_dagger).transpose((0, 2, 1)) su2s = su2[op_mapping] g_mesh = (np.abs(gpoints).max(axis=0) + 3) * 2 g1, g2, g3 = (gpoints + g_mesh / 2).astype(int).T # indices of g-points to keep all_rot_coeffs = {} for spin, spin_coeffs in coeffs.items(): coeff_shape = (len(spin_coeffs), len(rots)) + tuple(g_mesh) if ncl: coeff_shape += (2,) rot_coeffs = np.zeros(coeff_shape, dtype=complex) state_idxs = list(range(len(rots))) if pbar: state_idxs = get_progress_bar(state_idxs, desc="progress") for k_idx in state_idxs: map_idx = kp_mapping[k_idx] rot = rots[k_idx] tau = taus[k_idx] tr = trs[k_idx] kpoint = kpoints[map_idx] rot_kpoint = np.dot(rot, kpoint) kdiff = np.around(rot_kpoint) rot_kpoint -= kdiff edges = np.around(rot_kpoint, 5) == -0.5 rot_kpoint += edges kdiff -= edges rot_gpoints = np.dot(rot, gpoints.T).T rot_gpoints = np.around(rot_gpoints).astype(int) rot_gpoints += kdiff.astype(int) if tr: tau = -tau factor = np.exp(-1j * 2 * np.pi * np.dot(rot_gpoints + rot_kpoint, tau)) rg1, rg2, rg3 = (rot_gpoints + g_mesh / 2).astype(int).T if ncl: # perform rotation in spin space su2 = su2s[k_idx] rc = np.zeros_like(spin_coeffs[:, map_idx]) rc[:, :, 0] = ( su2[0, 0] * spin_coeffs[:, map_idx, :, 0] + su2[0, 1] * spin_coeffs[:, map_idx, :, 1] ) rc[:, :, 1] = ( su2[1, 0] * spin_coeffs[:, map_idx, :, 0] + su2[1, 1] * spin_coeffs[:, map_idx, :, 1] ) rot_coeffs[:, k_idx, rg1, rg2, rg3] = factor[None, :, None] * rc else: rot_coeffs[:, k_idx, rg1, rg2, rg3] = spin_coeffs[:, map_idx] * factor if tr and not ncl: rot_coeffs[:, k_idx] = np.conjugate(rot_coeffs[:, k_idx]) all_rot_coeffs[spin] = rot_coeffs[:, :, g1, g2, g3] log_time_taken(t0) return all_rot_coeffs
def test_get_progress_bar(iterable, total, error): with error: pbar = get_progress_bar(iterable=iterable, total=total) pbar.close()
def calculate_band_rates(self, spin: Spin, b_idx: int): conversion = self.amset_data.structure.lattice.reciprocal_lattice.volume kpoints_idx = self.amset_data.ir_kpoints_idx nkpoints = len(kpoints_idx) buf = 0.01 * 5 * units.eV band_energies = self.amset_data.energies[spin][b_idx, kpoints_idx] mask = (band_energies < self.scattering_energy_cutoffs[0] - buf) | ( band_energies > self.scattering_energy_cutoffs[1] + buf) fill_mask = mask[self.amset_data.ir_to_full_kpoint_mapping] n = np.sum(~fill_mask) logger.debug( " ├── # k-points within Fermi–Dirac cut-offs: {}".format(n)) # get k-point indexes of k-points within FD cutoffs (faster than np.where) k_idx_in_cutoff = np.arange(nkpoints)[~mask] to_stack = [] if len(self.basic_scatterers) > 0: basic_rates = np.array([ m.rates[spin][:, :, b_idx, kpoints_idx] for m in self.basic_scatterers ]) to_stack.append(basic_rates) if len(self.elastic_scatterers) > 0: elastic_prefactors = conversion * np.array( [m.prefactor(spin, b_idx) for m in self.elastic_scatterers]) elastic_rates = np.zeros(elastic_prefactors.shape + (nkpoints, )) if len(k_idx_in_cutoff) > 0: pbar = get_progress_bar(k_idx_in_cutoff, desc="elastic") for k_idx in pbar: elastic_rates[..., k_idx] = self.calculate_rate( spin, b_idx, k_idx) elastic_rates *= elastic_prefactors[..., None] to_stack.append(elastic_rates) if len(self.inelastic_scatterers) > 0: inelastic_prefactors = conversion * np.array( [m.prefactor(spin, b_idx) for m in self.inelastic_scatterers]) inelastic_rates = np.zeros(inelastic_prefactors.shape + (nkpoints, )) f_pop = self.settings["pop_frequency"] energy_diff = f_pop * 1e12 * 2 * np.pi * hbar * units.eV if len(k_idx_in_cutoff) > 0: pbar = get_progress_bar(k_idx_in_cutoff, desc="inelastic") inelastic_rates[:, :, :, k_idx_in_cutoff] = 0 for k_idx in pbar: for ediff in [energy_diff, -energy_diff]: inelastic_rates[:, :, :, k_idx] += self.calculate_rate( spin, b_idx, k_idx, energy_diff=ediff) inelastic_rates *= inelastic_prefactors[..., None] to_stack.append(inelastic_rates) all_band_rates = np.vstack(to_stack) return all_band_rates[ ..., self.amset_data.ir_to_full_kpoint_mapping], fill_mask