Exemple #1
0
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
Exemple #2
0
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
Exemple #3
0
    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
Exemple #4
0
    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)
Exemple #5
0
    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)
Exemple #6
0
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,
    )
Exemple #7
0
    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)
Exemple #8
0
 def shift_and_round(k):
     k = kpoints_to_first_bz(k)
     k = np.round(k, round_dp)
     return list(map(tuple, k))
Exemple #9
0
    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)