Source code for aiida_wannier90_workflows.utils.kpoints

"""Functions for processing kpoints."""
import typing as ty

import numpy as np

from aiida import orm


[docs]def get_explicit_kpoints(kmesh: orm.KpointsData) -> orm.KpointsData: """Work just like ``kmesh.pl`` of Wannier90. :param kmesh: contains a N1 * N2 * N3 mesh :type kmesh: aiida.orm.KpointsData :raises AttributeError: if kmesh does not contains a mesh :return: an explicit list of kpoints :rtype: aiida.orm.KpointsData """ try: # test if it is a mesh results = kmesh.get_kpoints_mesh() except AttributeError as exc: raise ValueError("input does not contain a mesh!") from exc # currently offset is ignored mesh = results[0] # following is similar to wannier90/kmesh.pl totpts = np.prod(mesh) weights = np.ones([totpts]) / totpts kpoints = np.zeros([totpts, 3]) ind = 0 for x in range(mesh[0]): for y in range(mesh[1]): for z in range(mesh[2]): kpoints[ind, :] = [x / mesh[0], y / mesh[1], z / mesh[2]] ind += 1 klist = orm.KpointsData() klist.set_kpoints(kpoints=kpoints, cartesian=False, weights=weights) return klist
[docs]def create_kpoints_from_distance( structure: orm.StructureData, distance: ty.Union[float, orm.Float], force_parity: ty.Union[bool, orm.Bool] = False, ) -> orm.KpointsData: """Create ``KpointsData`` from a given distance. Different from ``aiida_quantumespresso.calculations.functions.create_kpoints_from_distance``, this is not a ``calcfunction``, so the AiiDA database is unchanged. :param structure: [description] :type structure: [type] :param distance: [description] :type distance: [type] :param force_parity: whether the generated mesh is even or odd. :type force_parity: bool, aiida.orm.Bool :return: [description] :rtype: [type] """ kpoints = orm.KpointsData() kpoints.set_cell_from_structure(structure) if isinstance(distance, orm.Float): distance = distance.value kpoints.set_kpoints_mesh_from_density(distance, force_parity=force_parity) return kpoints
[docs]def get_explicit_kpoints_from_distance( structure: orm.StructureData, distance: ty.Union[float, orm.Float] ) -> orm.KpointsData: """Create an explicit list of kpoints with a given distance. :param structure: [description] :type structure: [type] :param distance: [description] :type distance: [type] :return: [description] :rtype: [type] """ kpoints = create_kpoints_from_distance(structure, distance) kpoints = get_explicit_kpoints(kpoints) return kpoints
[docs]def cartesian_product(*arrays: np.ndarray) -> np.ndarray: """Cartesian product. :return: _description_ :rtype: np.ndarray """ la = len(arrays) # pylint: disable=invalid-name dtype = np.result_type(*arrays) arr = np.empty([len(a) for a in arrays] + [la], dtype=dtype) for i, a in enumerate(np.ix_(*arrays)): # pylint: disable=invalid-name arr[..., i] = a return arr.reshape(-1, la)
[docs]def get_mesh_from_kpoints(kpoints: orm.KpointsData) -> ty.List: """From . :param kpoints: contains a N1 * N2 * N3 mesh :type kpoints: aiida.orm.KpointsData :raises AttributeError: if kmesh does not contains a mesh :return: an explicit list of kpoints :rtype: aiida.orm.KpointsData """ try: # test if it is a mesh mesh, _ = kpoints.get_kpoints_mesh() except AttributeError as exc: klist = kpoints.get_kpoints(also_weights=False, cartesian=False) mesh = [0, 0, 0] kmin = [0, 0, 0] kmax = [0, 0, 0] # 3 directions for i in range(3): uniq_kpt = np.sort(np.unique(klist[:, i])) kmin[i] = uniq_kpt[0] kmax[i] = uniq_kpt[-1] mesh[i] = len(uniq_kpt) klist_recovered = cartesian_product( *[np.linspace(kmin[_], kmax[_], mesh[_]) for _ in range(3)] ) if not np.allclose(klist, klist_recovered): raise ValueError(f"Cannot convert kpoints {kpoints} to a mesh") from exc return mesh
[docs]def create_kpoints_from_mesh( structure: orm.StructureData, mesh: ty.List[int] ) -> orm.KpointsData: """Create KpointsData from a given distance. :param structure: [description] :type structure: [type] :param mesh: [description] :type mesh: [type] :return: [description] :rtype: [type] """ kpoints = orm.KpointsData() kpoints.set_cell_from_structure(structure) if isinstance(mesh, orm.List): mesh = mesh.get_list() kpoints.set_kpoints_mesh(mesh) return kpoints
[docs]def get_explicit_kpoints_from_mesh( structure: orm.StructureData, mesh: ty.List[int], ) -> orm.KpointsData: """Create an explicit list of kpoints with a given distance. :param structure: [description] :type structure: [type] :param mesh: [description] :type mesh: [type] :return: [description] :rtype: [type] """ kpoints = create_kpoints_from_mesh(structure, mesh) kpoints = get_explicit_kpoints(kpoints) return kpoints
[docs]def get_path_from_kpoints(kpoints: orm.KpointsData) -> orm.Dict: """Translate bands kpoints path objects. From the input `bands_kpoints` (a KpointsData object) of PwBandsWorkChain, to the input `kpoint_path` (a Dict object) of Wannier90Calculation. :param kpoints: the input KpointsData must contain `labels`. :type kpoints: orm.KpointsData :return: the returned Dict object contains two keys: `path` and `point_coords`. :rtype: orm.Dict """ assert kpoints.labels is not None, "`kpoints` must have `labels`" assert len(kpoints.labels) >= 2 # default in crystal coordinates explicit_kpoints = kpoints.get_kpoints() # [('GAMMA', 'X'), # ('X', 'U'), # ('K', 'GAMMA'), # ('GAMMA', 'L'), # ('L', 'W'), # ('W', 'X')] path = [] # {'GAMMA': [0.0, 0.0, 0.0], # 'X': [0.5, 0.0, 0.5], # 'L': [0.5, 0.5, 0.5], # 'W': [0.5, 0.25, 0.75], # 'W_2': [0.75, 0.25, 0.5], # 'K': [0.375, 0.375, 0.75], # 'U': [0.625, 0.25, 0.625]} point_coords = {} # [(0, 'GAMMA'), # (43, 'X'), # (57, 'U'), # (58, 'K'), # (103, 'GAMMA'), # (140, 'L'), # (170, 'W'), # (191, 'X')] for idx, lab in kpoints.labels: point_coords[lab] = list(explicit_kpoints[idx]) prev_idx, prev_lab = kpoints.labels[0] for idx, lab in kpoints.labels[1:]: segment = (prev_lab, lab) if idx != prev_idx + 1: path.append(segment) prev_idx = idx prev_lab = lab ret = {"path": path, "point_coords": point_coords} return orm.Dict(ret)
[docs]def get_kpoints_from_bands(bands: orm.BandsData) -> orm.KpointsData: """Create a ``KpointsData`` from a ``BandsData``. :param bands: the input ``BandsData`` object . :type bands: aiida.orm.BandsData :return: the returned ``KpointsData`` must contain ``labels``. :rtype: aiida.orm.KpointsData """ kpoints = orm.KpointsData() cell = bands.attributes["cell"] kpoints.set_cell(cell) kpoints.set_attribute_many( { "pbc1": bands.attributes["pbc1"], "pbc2": bands.attributes["pbc2"], "pbc3": bands.attributes["pbc3"], } ) # default in crystal coordinates explicit_kpoints = bands.get_kpoints() # e.g. ['GAMMA', 'X', 'U', 'K', 'GAMMA', 'L', 'W', 'X'] labels = bands.attributes["labels"] # e.g. [0, 100, 135, 136, 242, 329, 400, 450] label_numbers = bands.attributes["label_numbers"] labels = list(zip(label_numbers, labels)) kpoints.set_kpoints(kpoints=explicit_kpoints, labels=labels) return kpoints