Source code for pytaser.kpoints

"""
This module generates kpoint-weights for uniform-mesh non-magnetic materials.

This is vital when using the Materials Project database to generate spectra in
PyTASER.
"""

import numpy as np
from pymatgen.core import Structure
from pymatgen.electronic_structure.bandstructure import BandStructureSymmLine


[docs] def get_kpoint_weights(bandstructure, time_reversal=True, symprec=0.1): """ Function to calculate the kpoint_weights for non-magnetic materials (non- metals). Args: bandstructure: PMG bandstructure object time_reversal: Time reversal operator, bool (True for +1, False for -1) symprec: Symmetry precision in Angstrom.(Lower value is more precise, but computationally more expensive) Returns: k-point_weights """ if isinstance(bandstructure, BandStructureSymmLine): raise TypeError( "This bandstructure object uses a line-mode kpoint mesh instead of a uniform mesh. Kpoint " "weighting is not possible" ) kpoints = get_kpoints_from_bandstructure(bandstructure) _, _, _, _, _, kp_mapping = expand_kpoints( bandstructure.structure, kpoints, symprec=symprec, time_reversal=time_reversal, return_mapping=True, ) weights = np.unique(kp_mapping, return_counts=True)[1].astype(float) weights /= np.sum(weights) return weights
[docs] def get_kpoints_from_bandstructure(bandstructure, cartesian=False): """ Function to pull the kpoint from the bandstructure. Args: bandstructure: PMG bandstructure object cartesian: bool, indicate if cartesian or fractional coordinates used. Returns: k-points """ if cartesian: kpoints = np.array([k.cart_coords for k in bandstructure.kpoints]) else: kpoints = np.array([k.frac_coords for k in bandstructure.kpoints]) return kpoints
[docs] def expand_kpoints( structure, kpoints, symprec=0.01, return_mapping=False, time_reversal=True, ): """ Function to expand the kpoints. Args: structure: PMG structure object kpoints: uniform mesh kpoints array. symprec: Symmetry precision in Angstrom.(Lower value is more precise, but computationally more expensive) return_mapping: bool time_reversal: Time reversal operator, bool (True for +1, False for -1) Returns: full_kpoints, rotations, translations, is_tr, op_mapping, kp_mapping """ kpoints = np.array(kpoints).round(8) # due to limited input precision of the k-points, the mesh is returned as a float mesh, is_shifted = get_mesh_from_kpoint_diff(kpoints) shift = np.array([1, 1, 1]) if is_shifted else np.array([0, 0, 0]) # to avoid issues to limited input precision, recalculate the input k-points # so that the mesh is integer and the k-points are not truncated # to a small precision addresses = np.rint((kpoints + shift / (mesh * 2)) * mesh) mesh = np.rint(mesh) kpoints = addresses / mesh - shift / (mesh * 2) rotations, translations, is_tr = get_reciprocal_point_group_operations( structure, symprec=symprec, time_reversal=time_reversal ) len(rotations) # rotate all-kpoints all_rotated_kpoints = [] for r in rotations: all_rotated_kpoints.append(np.dot(r, kpoints.T).T) all_rotated_kpoints = np.concatenate(all_rotated_kpoints) # map to first BZ all_rotated_kpoints -= np.rint(all_rotated_kpoints) all_rotated_kpoints = all_rotated_kpoints.round(8) # zone boundary consistent with VASP not with spglib all_rotated_kpoints[all_rotated_kpoints == -0.5] = 0.5 # Find unique points unique_rotated_kpoints, unique_idxs = np.unique(all_rotated_kpoints, return_index=True, axis=0) # find integer addresses unique_addresses = (unique_rotated_kpoints + shift / (mesh * 2)) * mesh unique_addresses -= np.rint(unique_addresses) in_uniform_mesh = (np.abs(unique_addresses) < 1e-5).all(axis=1) n_mapped = int(np.sum(in_uniform_mesh)) n_expected = int(np.prod(mesh)) if n_mapped != n_expected: raise ValueError(f"Expected {n_expected} points but found {n_mapped}") full_kpoints = unique_rotated_kpoints[in_uniform_mesh] full_idxs = unique_idxs[in_uniform_mesh] if not return_mapping: return full_kpoints op_mapping = np.floor(full_idxs / len(kpoints)).astype(int) kp_mapping = (full_idxs % len(kpoints)).astype(int) return full_kpoints, rotations, translations, is_tr, op_mapping, kp_mapping
[docs] def get_mesh_from_kpoint_diff(kpoints, ktol=1e-5): """ Function to get the uniform mesh from kpoint differences. Args: kpoints: uniform mesh kpoints array. ktol: threshold for filtering changes in kpoint-mesh points. Returns: np.array([na, nb, nc]), is_shifted """ kpoints = np.array(kpoints) # whether the k-point mesh is shifted or Gamma centered mesh is_shifted = np.min(np.linalg.norm(kpoints, axis=1)) > 1e-6 unique_a = np.unique(kpoints[:, 0]) unique_b = np.unique(kpoints[:, 1]) unique_c = np.unique(kpoints[:, 2]) if len(unique_a) == 1: na = 1 else: # filter very small changes, with a tol of 5e-4 this means k-point meshes # denser than 2000x2000x2000 will be treated as numerical noise. Meshes # this dense are extremely unlikely diff = np.diff(unique_a) diff = diff[diff > ktol] na = 1 / np.min(diff[diff > ktol]) if len(unique_b) == 1: nb = 1 else: diff = np.diff(unique_b) nb = 1 / np.min(diff[diff > ktol]) if len(unique_c) == 1: nc = 1 else: diff = np.diff(unique_c) nc = 1 / np.min(diff[diff > ktol]) # due to limited precision of the input k-points, the mesh is returned as a float return np.array([na, nb, nc]), is_shifted
[docs] def get_reciprocal_point_group_operations( structure: Structure, symprec: float = 0.01, time_reversal: bool = True, ): """ Function to get the reciprocal point group operations. Args: structure: PMG structure object symprec: Symmetry precision in Angstrom.(Lower value is more precise, but computationally more expensive) time_reversal: Time reversal operator, bool (True for +1, False for -1) Returns: rotations[sort_idx], translations[sort_idx], is_tr[sort_idx] """ from pymatgen.symmetry.analyzer import SpacegroupAnalyzer sga = SpacegroupAnalyzer(structure, symprec=symprec) if sga.get_symmetry_dataset() is None: # sometimes default angle tolerance doesn't work as expected sga = SpacegroupAnalyzer(structure, symprec=symprec, angle_tolerance=-1) rotations = sga.get_symmetry_dataset()["rotations"].transpose((0, 2, 1)) translations = sga.get_symmetry_dataset()["translations"] is_tr = np.full(len(rotations), False, dtype=bool) if time_reversal: rotations = np.concatenate([rotations, -rotations]) translations = np.concatenate([translations, -translations]) is_tr = np.concatenate([is_tr, ~is_tr]) rotations, unique_ops = np.unique(rotations, axis=0, return_index=True) translations = translations[unique_ops] is_tr = is_tr[unique_ops] # put identity first and time-reversal last sort_idx = np.argsort(np.abs(rotations - np.eye(3)).sum(axis=(1, 2)) + is_tr * 10) return rotations[sort_idx], translations[sort_idx], is_tr[sort_idx]