def reciprocal_lattice_match(band_structure: BandStructure, symprec=defaults["symprec"], tol=ktol): """Test whether reciprocal lattice and k-point mesh are consistent. I.e., do all the reciprocal space group operations result preserve the k-point mesh. """ round_dp = int(np.log10(1 / tol)) kpoints = get_kpoints_from_bandstructure(band_structure) kpoints = expand_kpoints(band_structure.structure, kpoints, symprec=symprec, verbose=False) rotations, _, _ = get_reciprocal_point_group_operations( band_structure.structure, symprec=symprec) kpoints = kpoints_to_first_bz(kpoints.round(round_dp)) kpoints = sort_kpoints(kpoints) for rotation in rotations: rot_kpoints = np.dot(rotation, kpoints.T).T rot_kpoints = kpoints_to_first_bz(rot_kpoints).round(round_dp) rot_kpoints = sort_kpoints(rot_kpoints) kpoints_match = np.max(np.linalg.norm(rot_kpoints - kpoints, axis=1)) < 0.01 if not kpoints_match: return False return True
def get_symmetrized_strain_mapping( bulk_structure, strain_mapping, symprec=defaults["symprec"], symprec_deformation=defaults["symprec"] / 100, ): # get symmetry operations of the bulk structure frac_ops = get_symmops(bulk_structure, symprec=symprec) for strain, calc in strain_mapping.items(): # expand band structure to cover full brillouin zone, otherwise rotation won't # include all necessary points # default symprec is lower otherwise the strain will not be noticed calc["bandstructure"] = expand_bandstructure( calc["bandstructure"], symprec=symprec_deformation) for strain, calc in strain_mapping.items(): k_old = get_kpoints_from_bandstructure(calc["bandstructure"], sort=True) k_old = kpoints_to_first_bz(k_old) for frac_op in frac_ops: # apply cartesian transformation matrix from the right side # hence the transpose r_cart = similarity_transformation(bulk_structure.lattice.matrix.T, frac_op.rotation_matrix.T) tstrain = strain.rotate(r_cart) independent = tstrain.get_deformation_matrix().is_independent( _mapping_tol) if independent and tstrain not in strain_mapping: rband = rotate_bandstructure(calc["bandstructure"], frac_op) k_new = get_kpoints_from_bandstructure(rband, sort=True) k_new = kpoints_to_first_bz(k_new) # check whether k-points match; if no match found this indicates # that the real and reciprocal lattice have different symmetries kpoints_match = np.max(np.linalg.norm(k_old - k_new, axis=1)) < 0.001 if kpoints_match: tcalc = { "reference": calc["reference"], "bandstructure": rband } strain_mapping[tstrain] = tcalc return strain_mapping
def _grid_kpoints(kpoints): # k-points has to cover the full BZ kpoints = kpoints_to_first_bz(kpoints) mesh_dim = get_mesh_from_kpoint_numbers(kpoints) if np.product(mesh_dim) != len(kpoints): raise ValueError("K-points do not cover full Brillouin zone.") kpoints = np.around(kpoints, 5) # get the indices to sort the k-points on the Z, then Y, then X columns sort_idx = np.lexsort((kpoints[:, 2], kpoints[:, 1], kpoints[:, 0])) # put the kpoints into a 3D grid so that they can be indexed as # kpoints[ikx][iky][ikz] = [kx, ky, kz] grid_kpoints = kpoints[sort_idx].reshape(mesh_dim + (3,)) # Expand the k-point mesh to account for periodic boundary conditions grid_kpoints = np.pad( grid_kpoints, ((1, 1), (1, 1), (1, 1), (0, 0)), mode="wrap" ) grid_kpoints[0, :, :] -= [1, 0, 0] grid_kpoints[:, 0, :] -= [0, 1, 0] grid_kpoints[:, :, 0] -= [0, 0, 1] grid_kpoints[-1, :, :] += [1, 0, 0] grid_kpoints[:, -1, :] += [0, 1, 0] grid_kpoints[:, :, -1] += [0, 0, 1] return grid_kpoints, mesh_dim, sort_idx
def __init__(self, structure, kpoints, coefficients): logger.info("Initializing wavefunction overlap calculator") self.structure = structure # k-points has to cover the full BZ kpoints = kpoints_to_first_bz(kpoints) mesh_dim = get_mesh_dim_from_kpoints(kpoints, tol=1e-4) round_dp = int(np.log10(1 / 1e-6)) kpoints = np.round(kpoints, round_dp) # get the indices to sort the k-points on the Z, then Y, then X columns sort_idx = np.lexsort((kpoints[:, 2], kpoints[:, 1], kpoints[:, 0])) # put the kpoints into a 3D grid so that they can be indexed as # kpoints[ikx][iky][ikz] = [kx, ky, kz] grid_kpoints = kpoints[sort_idx].reshape(mesh_dim + (3, )) x = grid_kpoints[:, 0, 0, 0] y = grid_kpoints[0, :, 0, 1] z = grid_kpoints[0, 0, :, 2] self.nbands = {s: c.shape[0] for s, c in coefficients.items()} # TODO: Expand the k-point mesh to account for periodic boundary conditions self.interpolators = {} for spin, spin_coefficients in coefficients.items(): nbands = spin_coefficients.shape[0] ncoefficients = spin_coefficients.shape[-1] # sort the coefficients then reshape them into the grid. The coefficients # can now be indexed as coefficients[iband][ikx][iky][ikz] sorted_coefficients = spin_coefficients[:, sort_idx] grid_shape = (nbands, ) + mesh_dim + (ncoefficients, ) grid_coefficients = sorted_coefficients.reshape(grid_shape) if nbands == 1: # this can cause a bug in RegularGridInterpolator. Have to fake # having at least two bands nbands = 2 grid_coefficients = np.tile(grid_coefficients, (2, 1, 1, 1, 1)) if eval_linear: grid = UCGrid( (0, nbands - 1, nbands), (x[0], x[-1], len(x)), (y[0], y[-1], len(y)), (z[0], z[-1], len(z)), ) self.interpolators[spin] = (grid, grid_coefficients) else: interp_range = (np.arange(nbands), x, y, z) self.interpolators[spin] = RegularGridInterpolator( interp_range, grid_coefficients, bounds_error=False, fill_value=None)
def __init__(self, kpoints, kpoint_mesh, velocities): logger.info("Initializing momentum relaxation time factor calculator") # k-points has to cover the full BZ kpoints = kpoints_to_first_bz(kpoints) kpoint_mesh = tuple(kpoint_mesh) round_dp = int(np.log10(1 / 1e-6)) kpoints = np.round(kpoints, round_dp) # get the indices to sort the k-points on the Z, then Y, then X columns sort_idx = np.lexsort((kpoints[:, 2], kpoints[:, 1], kpoints[:, 0])) # put the kpoints into a 3D grid so that they can be indexed as # kpoints[ikx][iky][ikz] = [kx, ky, kz] grid_kpoints = kpoints[sort_idx].reshape(kpoint_mesh + (3, )) x = grid_kpoints[:, 0, 0, 0] y = grid_kpoints[0, :, 0, 1] z = grid_kpoints[0, 0, :, 2] # TODO: Expand the k-point mesh to account for periodic boundary conditions self.interpolators = {} for spin, spin_velocities in velocities.items(): nbands = spin_velocities.shape[0] # sort the coefficients then reshape them into the grid. The coefficients # can now be indexed as coefficients[iband][ikx][iky][ikz] sorted_velocities = spin_velocities[:, sort_idx] grid_shape = (nbands, ) + kpoint_mesh + (3, ) grid_velocities = sorted_velocities.reshape(grid_shape) if nbands == 1: # this can cause a bug in RegularGridInterpolator. Have to fake # having at least two bands nbands = 2 grid_velocities = np.tile(grid_velocities, (2, 1, 1, 1, 1)) if eval_linear: grid = UCGrid( (0, nbands - 1, nbands), (x[0], x[-1], len(x)), (y[0], y[-1], len(y)), (z[0], z[-1], len(z)), ) self.interpolators[spin] = (grid, grid_velocities) else: logger.warning( "Install the 'interpolation' package for improved performance: " "https://pypi.org/project/interpolation") interp_range = (np.arange(nbands), x, y, z) self.interpolators[spin] = RegularGridInterpolator( interp_range, grid_velocities, bounds_error=False, fill_value=None)
def rotate_bandstructure(bandstructure: BandStructure, frac_symop: SymmOp): """Won't rotate projections...""" kpoints = get_kpoints_from_bandstructure(bandstructure) recip_rot = frac_symop.rotation_matrix.T rot_kpoints = np.dot(recip_rot, kpoints.T).T # map to first BZ, use VASP zone boundary convention rot_kpoints = kpoints_to_first_bz(rot_kpoints, negative_zone_boundary=False) # rotate structure structure = bandstructure.structure.copy() structure.apply_operation(frac_symop, fractional=True) return BandStructure( rot_kpoints, bandstructure.bands, structure.lattice.reciprocal_lattice, bandstructure.efermi, structure=structure, )
def calculate_rate(self, spin, b_idx, k_idx, energy_diff=None): rlat = self.amset_data.structure.lattice.reciprocal_lattice.matrix energy = self.amset_data.energies[spin][b_idx, k_idx] velocity = self.amset_data.velocities[spin][b_idx, k_idx] if energy_diff: energy += energy_diff tbs = self.amset_data.tetrahedral_band_structure ( tet_dos, tet_mask, cs_weights, tet_contributions, ) = tbs.get_tetrahedra_density_of_states( spin, energy, return_contributions=True, symmetry_reduce=False, # band_idx=b_idx, # turn this on to disable interband scattering ) if len(tet_dos) == 0: return 0 # next, get k-point indices and band_indices property_mask, band_kpoint_mask, band_mask, kpoint_mask = tbs.get_masks( spin, tet_mask) k = self.amset_data.kpoints[k_idx] k_primes = self.amset_data.kpoints[kpoint_mask] if self.cache_wavefunction: # use cached coefficients to calculate the overlap on the fine mesh # tetrahedron vertices p1 = self._coeffs[spin][self._coeffs_mapping[spin][b_idx, k_idx]] p2 = self._coeffs[spin][self._coeffs_mapping[spin][band_mask, kpoint_mask]] overlap = get_overlap(p1, p2) else: overlap = self.amset_data.overlap_calculator.get_overlap( spin, b_idx, k, band_mask, k_primes) # put overlap back in array with shape (nbands, nkpoints) all_overlap = np.zeros(self.amset_data.energies[spin].shape) all_overlap[band_kpoint_mask] = overlap # now select the properties at the tetrahedron vertices vert_overlap = all_overlap[property_mask] # get interpolated overlap at centre of tetrahedra cross sections tet_overlap = get_cross_section_values(vert_overlap, *tet_contributions) tetrahedra = tbs.tetrahedra[spin][tet_mask] # have to deal with the case where the tetrahedron cross section crosses the # zone boundary. This is a slight inaccuracy but we just treat the # cross section as if it is on one side of the boundary tet_kpoints = self.amset_data.kpoints[tetrahedra] base_kpoints = tet_kpoints[:, 0][:, None, :] k_diff = pbc_diff(tet_kpoints, base_kpoints) + pbc_diff( base_kpoints, k) # project the tetrahedron cross sections onto 2D surfaces in either a triangle # or quadrilateral k_diff = np.dot(k_diff, rlat) intersections = get_cross_section_values(k_diff, *tet_contributions, average=False) projected_intersections, basis = get_projected_intersections( intersections) k_spacing = np.linalg.norm( np.dot(rlat, 1 / self.amset_data.kpoint_mesh)) qpoints, weights, mapping = get_fine_mesh_qpoints( projected_intersections, basis, *tet_contributions[0:3], high_tol=k_spacing * 0.5, med_tol=k_spacing * 2, cross_section_weights=cs_weights, ) qpoint_norm_sq = np.sum(qpoints**2, axis=-1) k_primes = np.dot(qpoints, np.linalg.inv(rlat)) + k k_primes = kpoints_to_first_bz(k_primes) # unit q in reciprocal cartesian coordinates unit_q = qpoints / np.sqrt(qpoint_norm_sq)[:, None] if energy_diff: e_fd = _get_fd(energy, self.amset_data) emission = energy_diff <= 0 rates = [ s.factor(unit_q, qpoint_norm_sq, emission, e_fd) for s in self.inelastic_scatterers ] mrta_factor = 1 else: mrta_factor = self.amset_data.mrta_calculator.get_mrta_factor( spin, b_idx, k, tet_mask[0][mapping], k_primes) rates = [ s.factor(unit_q, qpoint_norm_sq, spin, b_idx, k, velocity) for s in self.elastic_scatterers ] rates = np.array(rates) rates /= self.amset_data.structure.lattice.reciprocal_lattice.volume rates *= tet_overlap[mapping] * weights * mrta_factor # this is too expensive vs tetrahedron integration and doesn't add much more # accuracy; could offer this as an option # overlap = self.amset_data.overlap_calculator.get_overlap( # spin, b_idx, k, tet_mask[0][mapping], k_primes # ) # rates *= overlap * weights * mrta_factor # sometimes the projected intersections can be nan when the density of states # contribution is infinitesimally small; this catches those errors rates[np.isnan(rates)] = 0 return np.sum(rates, axis=-1)
def shift_and_round(k): k = kpoints_to_first_bz(k) k = np.round(k, round_dp) return list(map(tuple, k))
def calculate_rate(self, spin, b_idx, k_idx, energy_diff=None): rlat = self.amset_data.structure.lattice.reciprocal_lattice.matrix ir_kpoints_idx = self.amset_data.ir_kpoints_idx energy = self.amset_data.energies[spin][b_idx, ir_kpoints_idx][k_idx] if energy_diff: energy += energy_diff tbs = self.amset_data.tetrahedral_band_structure ( tet_dos, tet_mask, cs_weights, tet_contributions, ) = tbs.get_tetrahedra_density_of_states( spin, energy, return_contributions=True, symmetry_reduce=False, # band_idx=b_idx, # turn this on to disable interband scattering ) if len(tet_dos) == 0: return 0 # next, get k-point indices and band_indices # property_mask, band_kpoint_mask, band_mask, kpoint_mask = tbs.get_masks( # spin, tet_mask # ) k = self.amset_data.ir_kpoints[k_idx] # k_primes = self.amset_data.kpoints[kpoint_mask] # overlap = self.amset_data.overlap_calculator.get_overlap( # spin, b_idx, k, band_mask, k_primes # ) # # # put overlap back in array with shape (nbands, nkpoints) # all_overlap = np.zeros(self.amset_data.energies[spin].shape) # all_overlap[band_kpoint_mask] = overlap # # # now select the properties at the tetrahedron vertices # vert_overlap = all_overlap[property_mask] # # # get interpolated overlap at centre of tetrahedra cross sections # tet_overlap = get_cross_section_values(vert_overlap, *tet_contributions) tetrahedra = tbs.tetrahedra[spin][tet_mask] # have to deal with the case where the tetrahedron cross section crosses the # zone boundary. This is a slight inaccuracy but we just treat the # cross section as if it is on one side of the boundary tet_kpoints = self.amset_data.kpoints[tetrahedra] base_kpoints = tet_kpoints[:, 0][:, None, :] k_diff = pbc_diff(tet_kpoints, base_kpoints) + pbc_diff( base_kpoints, k) k_diff = np.dot(k_diff, rlat) intersections = get_cross_section_values(k_diff, *tet_contributions, average=False) projected_intersections, basis = get_projected_intersections( intersections) k_spacing = np.linalg.norm( np.dot(rlat, 1 / self.amset_data.kpoint_mesh)) qpoints, weights, mapping = get_fine_mesh_qpoints( projected_intersections, basis, *tet_contributions[0:3], high_tol=k_spacing * 0.5, med_tol=k_spacing * 2, cross_section_weights=cs_weights) qpoint_norm_sq = np.sum(qpoints**2, axis=-1) k_primes = np.dot(qpoints, np.linalg.inv(rlat)) + k k_primes = kpoints_to_first_bz(k_primes) if energy_diff: fd = _get_fd(energy, self.amset_data) emission = energy_diff <= 0 rates = [ s.factor(qpoint_norm_sq, emission, fd) for s in self.inelastic_scatterers ] mrta_factor = 1 else: mrta_factor = self.amset_data.mrta_calculator.get_mrta_factor( spin, b_idx, k, tet_mask[0][mapping], k_primes) rates = [s.factor(qpoint_norm_sq) for s in self.elastic_scatterers] rates = np.array(rates) # sometimes the projected intersections can be nan when the density of states # contribution is infinitesimally small; this catches those errors rates[np.isnan(rates)] = 0 overlap = self.amset_data.overlap_calculator.get_overlap( spin, b_idx, k, tet_mask[0][mapping], k_primes) rates /= self.amset_data.structure.lattice.reciprocal_lattice.volume rates *= overlap * weights * mrta_factor return np.sum(rates, axis=-1)