def __init__( self, band_structure: BandStructure, num_electrons: int, interpolation_factor: float = defaults["interpolation_factor"], soc: bool = False, magmom: Optional[np.ndarray] = None, mommat: Optional[np.ndarray] = None, other_properties: Dict[Spin, Dict[str, np.ndarray]] = None, ): self._band_structure = band_structure self._num_electrons = num_electrons self._soc = soc self._spins = self._band_structure.bands.keys() self._other_properties = other_properties self.interpolation_factor = interpolation_factor self._lattice_matrix = (band_structure.structure.lattice.matrix.T * angstrom_to_bohr) self._coefficients = {} self._other_coefficients = defaultdict(dict) kpoints = np.array([k.frac_coords for k in band_structure.kpoints]) atoms = AseAtomsAdaptor.get_atoms(band_structure.structure) logger.info("Getting band interpolation coefficients") t0 = time.perf_counter() self._equivalences = sphere.get_equivalences(atoms=atoms, nkpt=kpoints.shape[0] * interpolation_factor, magmom=magmom) # get the interpolation mesh used by BoltzTraP2 self.interpolation_mesh = ( 2 * np.max(np.abs(np.vstack(self._equivalences)), axis=0) + 1) for spin in self._spins: energies = band_structure.bands[spin] * ev_to_hartree data = DFTData(kpoints, energies, self._lattice_matrix, mommat=mommat) self._coefficients[spin] = fite.fitde3D(data, self._equivalences) log_time_taken(t0) t0 = time.perf_counter() if self._other_properties: logger.info("Getting additional interpolation coefficients") for spin in self._spins: for label, prop in self._other_properties[spin].items(): data = DFTData(kpoints, prop, self._lattice_matrix, mommat=mommat) self._other_coefficients[spin][label] = fite.fitde3D( data, self._equivalences) log_time_taken(t0)
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_dos( self, estep: float = defaults["dos_estep"], progress_bar: bool = defaults["print_log"] ): """ Args: estep: The DOS energy step in eV, where smaller numbers give more accuracy but are more expensive. progress_bar: Show a progress bar for DOS calculation. """ emin = np.min([np.min(spin_eners) for spin_eners in self.energies.values()]) emax = np.max([np.max(spin_eners) for spin_eners in self.energies.values()]) epoints = int(round((emax - emin) / (estep * units.eV))) energies = np.linspace(emin, emax, epoints) dos_weight = 1 if self._soc or len(self.spins) == 2 else 2 logger.debug("DOS parameters:") log_list( [ "emin: {:.2f} eV".format(emin / units.eV), "emax: {:.2f} eV".format(emax / units.eV), "dos weight: {}".format(dos_weight), "n points: {}".format(epoints), ] ) logger.debug("Generating tetrahedral DOS:") t0 = time.perf_counter() emesh, dos = self.tetrahedral_band_structure.get_density_of_states( energies=energies, progress_bar=progress_bar ) log_time_taken(t0) num_electrons = self.num_electrons if self.is_metal else None self.dos = FermiDos( self.intrinsic_fermi_level, emesh, dos, self.structure, atomic_units=True, dos_weight=dos_weight, num_electrons=num_electrons, )
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 solve_boltzman_transport_equation( amset_data: AmsetData, calculate_mobility: bool = defaults["calculate_mobility"], separate_mobility: bool = defaults["separate_mobility"], progress_bar: bool = defaults["print_log"], ): has_doping = amset_data.doping is not None has_temps = amset_data.temperatures is not None has_rates = amset_data.scattering_rates is not None if not (has_doping and has_temps and has_rates): raise ValueError(_e_str) logger.info( "Calculating conductivity, Seebeck, and electronic thermal conductivity" ) t0 = time.perf_counter() sigma, seebeck, kappa = _calculate_transport_properties( amset_data, progress_bar=progress_bar) log_time_taken(t0) if not calculate_mobility: return sigma, seebeck, kappa, None if amset_data.is_metal: logger.info( "System is metallic, refusing to calculate carrier mobility") return sigma, seebeck, kappa, None n_scats = len(amset_data.scattering_labels) logger.info("Calculating overall mobility") t0 = time.perf_counter() overall = _calculate_mobility( amset_data, np.arange(n_scats), pbar_label="mobility" if progress_bar else None) mobility = {"overall": overall} log_time_taken(t0) if separate_mobility: logger.info("Calculating individual scattering rate mobilities") t0 = time.perf_counter() for rate_idx, name in enumerate(amset_data.scattering_labels): mobility[name] = _calculate_mobility( amset_data, rate_idx, pbar_label=name if progress_bar else None) log_time_taken(t0) return sigma, seebeck, kappa, mobility
def get_energies( self, kpoints: Union[np.ndarray, List], energy_cutoff: Optional[float] = None, scissor: float = None, bandgap: float = None, return_velocity: bool = False, return_curvature: bool = False, return_other_properties: bool = False, coords_are_cartesian: bool = False, atomic_units: bool = False, symprec: Optional[float] = defaults["symprec"], return_efermi: bool = False, return_vb_idx: bool = False, ) -> Union[Dict[Spin, np.ndarray], Tuple[Dict[Spin, np.ndarray], ...]]: """Gets the interpolated energies for multiple k-points in a band. Note, the accuracy of the interpolation is dependant on the ``interpolate_factor`` used to initialize the Interpolater. Args: kpoints: The k-point coordinates. energy_cutoff: The energy cut-off to determine which bands are included in the interpolation. If the energy of a band falls within the cut-off at any k-point it will be included. For metals the range is defined as the Fermi level ± energy_cutoff. For gapped materials, the energy range is from the VBM - energy_cutoff to the CBM + energy_cutoff. scissor: The amount by which the band gap is scissored. Cannot be used in conjunction with the ``bandgap`` option. Has no effect for metallic systems. bandgap: Automatically adjust the band gap to this value. Cannot be used in conjunction with the ``scissor`` option. Has no effect for metallic systems. return_velocity: Whether to return the band velocities. return_curvature: Whether to return the band curvature (inverse effective mass). return_other_properties: Whether to return the interpolated results for the ``other_properties`` data. coords_are_cartesian: Whether the kpoints are in cartesian or fractional coordinates. atomic_units: Return the energies, velocities, and effective_massses in atomic units. If False, energies will be in eV, velocities in cm/s, and curvature in units of 1 / electron rest mass (1/m0). symprec: Symmetry precision. If set, symmetry will be used to reduce the nummber of calculated k-points and velocities. return_efermi: Whether to return the Fermi level with the unit determined by ``atomic_units``. If the system is semiconducting the Fermi level will be given in the middle of the band gap. return_vb_idx: Whether to return the index of the highest valence band in the interpolated bands. Will be returned as a dictionary of ``{spin: vb_idx}``. Returns: The band energies as dictionary of:: {spin: energies} If ``return_velocity``, ``curvature`` or ``return_other_properties`` a tuple is returned, formatted as:: (energies, Optional[velocities], Optional[curvature], Optional[other_properties]) The velocities and effective masses are given as the 1x3 trace and full 3x3 tensor, respectively (along cartesian directions). The projections are summed for each orbital type (s, p, d) across all atoms, and are given as:: {spin: {orbital: projections}} """ if self._band_structure.is_metal() and (bandgap or scissor): raise ValueError("{} option set but system is metallic".format( "bandgap" if bandgap else "scissor")) # only calculate the energies for the bands within the energy cutoff lattice = self._band_structure.structure.lattice kpoints = np.asarray(kpoints) nkpoints = len(kpoints) if coords_are_cartesian: kpoints = lattice.reciprocal_lattice.get_fractional_coords(kpoints) if symprec: logger.info("Reducing # k-points using symmetry") ( kpoints, weights, ir_kpoints_idx, ir_to_full_idx, _, rot_mapping, ) = get_symmetry_equivalent_kpoints( self._band_structure.structure, kpoints, symprec=symprec, return_inverse=True, time_reversal_symmetry=not self._soc, ) k_info = [ "# original k-points: {}".format(nkpoints), "# reduced k-points {}".format(len(kpoints)), ] log_list(k_info) ibands = get_ibands(energy_cutoff, self._band_structure) new_vb_idx = get_vb_idx(energy_cutoff, self._band_structure) energies = {} velocities = {} curvature = {} other_properties = defaultdict(dict) for spin in self._spins: spin_ibands = ibands[spin] min_b = spin_ibands.min() + 1 max_b = spin_ibands.max() + 1 info = "Interpolating {} bands {}-{}".format( spin_name[spin], min_b, max_b) logger.info(info) t0 = time.perf_counter() fitted = fite.getBands( kpoints, self._equivalences, self._lattice_matrix, self._coefficients[spin][spin_ibands], curvature=return_curvature, ) log_time_taken(t0) energies[spin] = fitted[0] velocities[spin] = fitted[1] if return_curvature: # make curvature have the shape ((nbands, nkpoints, 3, 3) curvature[spin] = fitted[2] curvature[spin] = curvature[spin].transpose((2, 3, 0, 1)) if return_other_properties: logger.info("Interpolating {} properties".format( spin_name[spin])) t0 = time.perf_counter() for label, coeffs in self._other_coefficients[spin].items(): other_properties[spin][label], _ = fite.getBands( kpoints, self._equivalences, self._lattice_matrix, coeffs[spin_ibands], curvature=False, ) log_time_taken(t0) if not atomic_units: energies[spin] = energies[spin] * hartree_to_ev velocities[spin] = _convert_velocities(velocities[spin], lattice.matrix) if symprec: energies, velocities, curvature, other_properties = symmetrize_results( energies, velocities, curvature, other_properties, ir_to_full_idx, rot_mapping, self._band_structure.structure.lattice.reciprocal_lattice. matrix, ) if not self._band_structure.is_metal(): energies = _shift_energies(energies, new_vb_idx, scissor=scissor, bandgap=bandgap) to_return = [energies] if return_velocity: to_return.append(velocities) if return_curvature: to_return.append(curvature) if return_other_properties: to_return.append(other_properties) if return_efermi: if self._band_structure.is_metal(): efermi = self._band_structure.efermi if atomic_units: efermi *= ev_to_hartree else: # if semiconducting, set Fermi level to middle of gap efermi = _get_efermi(energies, new_vb_idx) to_return.append(efermi) if return_vb_idx: to_return.append(new_vb_idx) if len(to_return) == 1: return to_return[0] else: return tuple(to_return)
def get_amset_data( self, energy_cutoff: Optional[float] = None, scissor: float = None, bandgap: float = None, symprec: float = defaults["symprec"], nworkers: int = defaults["nworkers"], ) -> AmsetData: """Gets an AmsetData object using the interpolated bands. Note, the interpolation mesh is determined using by ``interpolate_factor`` option in the ``Inteprolater`` constructor. This method is much faster than the ``get_energies`` function but doesn't provide as much flexibility. The degree of parallelization is controlled by the ``nworkers`` option. Args: energy_cutoff: The energy cut-off to determine which bands are included in the interpolation. If the energy of a band falls within the cut-off at any k-point it will be included. For metals the range is defined as the Fermi level ± energy_cutoff. For gapped materials, the energy range is from the VBM - energy_cutoff to the CBM + energy_cutoff. scissor: The amount by which the band gap is scissored. Cannot be used in conjunction with the ``bandgap`` option. Has no effect for metallic systems. bandgap: Automatically adjust the band gap to this value. Cannot be used in conjunction with the ``scissor`` option. Has no effect for metallic systems. symprec: The symmetry tolerance used when determining the symmetry inequivalent k-points on which to interpolate. nworkers: The number of processors used to perform the interpolation. If set to ``-1``, the number of workers will be set to the number of CPU cores. Returns: The electronic structure (including energies, velocities, density of states and k-point information) as an AmsetData object. """ is_metal = self._band_structure.is_metal() if is_metal and (bandgap or scissor): raise ValueError("{} option set but system is metallic".format( "bandgap" if bandgap else "scissor")) nworkers = multiprocessing.cpu_count() if nworkers == -1 else nworkers logger.info("Interpolation parameters:") iinfo = [ "k-point mesh: {}".format("x".join( map(str, self.interpolation_mesh))), "energy cutoff: {} eV".format(energy_cutoff), ] log_list(iinfo) ibands = get_ibands(energy_cutoff, self._band_structure) new_vb_idx = get_vb_idx(energy_cutoff, self._band_structure) energies = {} vvelocities = {} velocities = {} forgotten_electrons = 0 for spin in self._spins: spin_ibands = ibands[spin] min_b = spin_ibands.min() + 1 max_b = spin_ibands.max() + 1 info = "Interpolating {} bands {}-{}".format( spin_name[spin], min_b, max_b) logger.info(info) # these are bands beneath the Fermi level that are dropped forgotten_electrons += min_b - 1 t0 = time.perf_counter() energies[spin], vvelocities[spin], _, velocities[ spin] = get_bands_fft( self._equivalences, self._coefficients[spin][spin_ibands], self._lattice_matrix, return_effective_mass=False, nworkers=nworkers, ) log_time_taken(t0) if not self._soc and len(self._spins) == 1: forgotten_electrons *= 2 nelectrons = self._num_electrons - forgotten_electrons if is_metal: efermi = self._band_structure.efermi * ev_to_hartree else: energies = _shift_energies(energies, new_vb_idx, scissor=scissor, bandgap=bandgap) # if material is semiconducting, set Fermi level to middle of gap efermi = _get_efermi(energies, new_vb_idx) # get the actual k-points used in the BoltzTraP2 interpolation # unfortunately, BoltzTraP2 doesn't expose this information so we # have to get it ourselves ( ir_kpts, _, full_kpts, ir_kpts_idx, ir_to_full_idx, tetrahedra, *ir_tetrahedra_info, ) = get_kpoints_tetrahedral( self.interpolation_mesh, self._band_structure.structure, symprec=symprec, time_reversal_symmetry=not self._soc, ) energies, vvelocities, velocities = sort_amset_results( full_kpts, energies, vvelocities, velocities) atomic_structure = get_atomic_structure(self._band_structure.structure) return AmsetData( atomic_structure, energies, vvelocities, velocities, self.interpolation_mesh, full_kpts, ir_kpts, ir_kpts_idx, ir_to_full_idx, tetrahedra, ir_tetrahedra_info, efermi, nelectrons, is_metal, self._soc, vb_idx=new_vb_idx, )
def from_data( cls, energies: Dict[Spin, np.ndarray], kpoints: np.ndarray, tetrahedra: np.ndarray, structure: Structure, ir_kpoints_idx: np.ndarray, ir_kpoint_mapping: np.ndarray, ir_tetrahedra_idx: Optional[np.ndarray] = None, ir_tetrahedra_to_full_idx: Optional[np.ndarray] = None, ir_tetrahedra_weights: Optional[np.ndarray] = None, ): logger.info("Initializing tetrahedron band structure") t0 = time.perf_counter() tparams = (ir_tetrahedra_idx, ir_tetrahedra_to_full_idx, ir_tetrahedra_weights) if len(set([x is None for x in tparams])) != 1: raise ValueError( "Either all or none of ir_tetrahedra_idx, ir_tetrahedra_to_full_idx and" " ir_tetrahedra_weights should be set.") if ir_tetrahedra_idx is None: ir_tetrahedra_idx = np.arange(len(kpoints)) ir_tetrahedra_to_full_idx = np.ones_like(ir_tetrahedra_idx) ir_tetrahedra_weights = np.ones_like(ir_tetrahedra_idx) ir_tetrahedra_to_full_idx = ir_tetrahedra_to_full_idx ir_kpoints_idx = ir_kpoints_idx ir_kpoint_mapping = ir_kpoint_mapping _, ir_kpoint_weights = np.unique(ir_kpoint_mapping, return_counts=True) # need to keep track of full tetrahedra to recover full k-point indices # when calculating scattering rates (i.e., k-k' is symmetry inequivalent). full_tetrahedra, _ = process_tetrahedra(tetrahedra, energies) # store irreducible tetrahedra and use energies to calculate diffs and min/maxes ir_tetrahedra, ir_tetrahedra_energies = process_tetrahedra( tetrahedra[ir_tetrahedra_idx], energies) # the remaining properties are given for each irreducible tetrahedra (e21, e31, e41, e32, e42, e43) = get_tetrahedra_energy_diffs(ir_tetrahedra_energies) ( max_tetrahedra_energies, min_tetrahedra_energies, ) = get_max_min_tetrahedra_energies(ir_tetrahedra_energies) cross_section_weights = get_tetrahedra_cross_section_weights( structure.lattice.reciprocal_lattice.matrix, kpoints, ir_tetrahedra, e21, e31, e41, ) tetrahedron_volume = 1 / len(tetrahedra) log_time_taken(t0) return cls( energies, kpoints, ir_kpoints_idx, ir_kpoint_mapping, ir_kpoint_weights, full_tetrahedra, ir_tetrahedra, ir_tetrahedra_energies, ir_tetrahedra_idx, ir_tetrahedra_to_full_idx, ir_tetrahedra_weights, e21, e31, e41, e32, e42, e43, max_tetrahedra_energies, min_tetrahedra_energies, cross_section_weights, tetrahedron_volume, )
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 __init__( self, energies: Dict[Spin, np.ndarray], kpoints: np.ndarray, tetrahedra: np.ndarray, structure: Structure, ir_kpoints_idx: np.ndarray, ir_kpoint_mapping: np.ndarray, ir_tetrahedra_idx: Optional[np.ndarray] = None, ir_tetrahedra_to_full_idx: Optional[np.ndarray] = None, ir_tetrahedra_weights: Optional[np.ndarray] = None, ): logger.info("Initializing tetrahedron band structure") t0 = time.perf_counter() tparams = (ir_tetrahedra_idx, ir_tetrahedra_to_full_idx, ir_tetrahedra_weights) if len(set([x is None for x in tparams])) != 1: raise ValueError( "Either all or none of ir_tetrahedra_idx, ir_tetrahedra_to_full_idx and" " ir_tetrahedra_weights should be set." ) if ir_tetrahedra_idx is None: ir_tetrahedra_idx = np.arange(len(kpoints)) ir_tetrahedra_to_full_idx = np.ones_like(ir_tetrahedra_idx) ir_tetrahedra_weights = np.ones_like(ir_tetrahedra_idx) self.energies = energies self.kpoints = kpoints self.ir_tetrahedra_idx = ir_tetrahedra_idx self.ir_tetrahedra_to_full_idx = ir_tetrahedra_to_full_idx self.ir_tetrahedra_weights = ir_tetrahedra_weights self.ir_kpoints_idx = ir_kpoints_idx self.ir_kpoint_mapping = ir_kpoint_mapping _, self.ir_kpoint_weights = np.unique(ir_kpoint_mapping, return_counts=True) # need to keep track of full tetrahedra to recover full k-point indices # when calculating scattering rates (i.e., k-k' is symmetry inequivalent). self.tetrahedra, _ = process_tetrahedra(tetrahedra, self.energies) # store irreducible tetrahedra and use energies to calculate diffs and min/maxes self.ir_tetrahedra, self.ir_tetrahedra_energies = process_tetrahedra( tetrahedra[self.ir_tetrahedra_idx], self.energies ) # the remaining properties are given for each irreducible tetrahedra ( self.e21, self.e31, self.e41, self.e32, self.e42, self.e43, ) = get_tetrahedra_energy_diffs(self.ir_tetrahedra_energies) ( self.max_tetrahedra_energies, self.min_tetrahedra_energies, ) = get_max_min_tetrahedra_energies(self.ir_tetrahedra_energies) self.cross_section_weights = get_tetrahedra_cross_section_weights( structure.lattice.reciprocal_lattice.matrix, self.kpoints, self.ir_tetrahedra, self.e21, self.e31, self.e41, ) self._tetrahedron_volume = 1 / len(tetrahedra) self.grouped_ir_to_full = groupby( np.arange(len(ir_tetrahedra_to_full_idx)), ir_tetrahedra_to_full_idx ) self._ir_weights_shape = { s: (len(self.energies[s]), len(ir_kpoints_idx)) for s in self.energies } self._weights_cache = {} self._weights_mask_cache = {} self._energies_cache = {} self._tetrahedra_connections = defaultdict(set) for tet in tetrahedra: self._tetrahedra_connections[tet[0]].update(tet) self._tetrahedra_connections[tet[1]].update(tet) self._tetrahedra_connections[tet[2]].update(tet) self._tetrahedra_connections[tet[3]].update(tet) log_time_taken(t0)