Source code for aiida_vasp.parsers.content_parsers.kpoints

"""
The ``KPOINTS`` parser interface.

Contains the parsing interfaces to parsevasp used to parse ``KPOINTS`` content.
"""

from __future__ import annotations

from typing import Any, TextIO

import numpy as np
from aiida import orm
from parsevasp.kpoints import Kpoint, Kpoints

from aiida_vasp.parsers.content_parsers.base import BaseFileParser


[docs] class KpointsParser(BaseFileParser): """The parser interface that enables parsing of ``KPOINTS`` content. The parser is triggered by using the ``kpoints-kpoints`` quantity key. The quantity key ``kpoints`` will on the other hand parse the k-points using the XML parser. """ DEFAULT_SETTINGS = {'quantities_to_parse': ['kpoints-kpoints']} PARSABLE_QUANTITIES = { 'kpoints-kpoints': { 'inputs': [], 'name': 'kpoints', 'prerequisites': [], }, }
[docs] def _init_from_handler(self, handler: TextIO) -> None: """Initialize using a file like handler. :param handler: A file like object that provides the necessary content to be parsed. :type handler: file-like object """ try: self._content_parser = Kpoints(file_handler=handler, logger=self._logger) except SystemExit: self._logger.warning('Parsevasp exited abnormally.')
[docs] def _init_from_data(self, data: orm.KpointsData) -> None: """Initialize using AiiDA KpointsData.""" if isinstance(data, orm.KpointsData): self._content_data = data else: raise TypeError('The supplied AiiDA data structure is not a KpointsData.')
@property def kpoints(self) -> dict[str, Any] | None: """Return kpoints that is ready to be consumed by the the AiiDA ``KpointsData``. AiiDA does not support the line mode used in VASP, so we give a warning that parsing this is not supported. :returns: A dict that contain keys ``comment``, ``divisions``, ``shifts``, ``points``, ``tetra``, ``tetra_volume``, ``mode`` ``centering``, ``num_kpoints``, ``weights`` and ``cartesian`` which are compatible with consumption of the initialization of the AiiDA KpointsData. :rtype: dict """ aiida_kpoints = parsevasp_to_aiida(self._content_parser, self._logger) return aiida_kpoints
[docs] def _content_data_to_content_parser(self) -> Any: """Convert an AiiDA ``KpointsData`` to a content parser instance of ``Kpoints`` from ``parsevasp``. :returns: An instance of ``Kpoints`` from ``parsevasp``. :rtype: object """ try: # Check if the ``KpointsData`` contain a mesh. _ = self._content_data.base.attributes.get('mesh') mode = 'automatic' except AttributeError: pass try: # Check to see if the ``KpointsData`` contain an explicit k-point list. _ = self._content_data.base.attributes.get('array|kpoints') mode = 'explicit' except AttributeError: pass kpoints_dict = {} for keyword in [ 'comment', 'divisions', 'shifts', 'points', 'tetra', 'tetra_volume', 'mode', 'centering', 'num_kpoints', 'generating_vectors', ]: kpoints_dict[keyword] = None kpoints_dict.update(getattr(self, '_get_kpointsdict_' + mode)(self._content_data)) # We brake hard if ``parsevasp`` fail here. If we can not write we will not try another parser. content_parser = Kpoints(kpoints_dict=kpoints_dict, logger=self._logger) return content_parser
[docs] def _get_kpointsdict_explicit(self, kpoints_data: orm.KpointsData) -> dict[str, Any]: """Turn Aiida ``KpointData`` into a k-points dictionary with explicit generation of points. :param kpoints_data: An AiiDA ``KpointsData`` object containing explicit k-point sets. :type kpoints_data: object :returns: A dictionary that can be used to initialize a ``parsevasp`` ``Kpoints`` instance. :rtype: dict """ kpoints_dict = {} kpts = [] try: points, weights = kpoints_data.get_kpoints(also_weights=True) except AttributeError: points = kpoints_data.get_kpoints() weights = None for index, point in enumerate(points): if weights is not None: kpt = Kpoint(point, weight=weights[index]) else: # No weights supplied, so set them to 1.0 kpt = Kpoint(point, weight=1.0) kpts.append(kpt) kpoints_dict['points'] = kpts kpoints_dict['mode'] = 'explicit' kpoints_dict['num_kpoints'] = len(kpts) return kpoints_dict
[docs] @staticmethod def _get_kpointsdict_automatic(kpointsdata: orm.KpointsData) -> dict[str, Any]: """Turn Aiida ``KpointData`` into a k-point dictionary with automatic generation of points. :param kpointsdata: An AiiDA ``KpointsData`` object containing meshed k-point sets. :type kpointsdata: object :returns: A dictionary that can be used to initialize a ``parsevasp`` ``Kpoints`` instance. :rtype: dict """ kpoints_dict = {} # Automatic mode mesh = kpointsdata.get_kpoints_mesh() kpoints_dict['divisions'] = mesh[0] kpoints_dict['shifts'] = mesh[1] kpoints_dict['mode'] = 'automatic' # Here we need to make a choice, so should add more to AiiDA to make this better defined kpoints_dict['centering'] = 'Gamma' kpoints_dict['num_kpoints'] = 0 return kpoints_dict
[docs] def parsevasp_to_aiida(kpoints: Kpoints, logger: Any) -> dict[str, Any] | None: """``parsevasp`` to AiiDA conversion. Generate an AiiDA data structure that can be consumed by ``KpointsData`` on initialization from the ``parsevasp`` instance of the ``Kpoints`` class. :param kpoints: An instance of the ``Kpoints`` class in ``parsevasp``. :type kpoints: object :param logger: Logger instance for warnings and debug messages. :type logger: object :returns: A dictionary representation which are ready to be used when creating an AiiDA ``KpointsData`` instance. :rtype: dict """ if kpoints.entries.get('mode') == 'line': # AiiDA does not support line mode logger.warning('The read KPOINTS contained line mode which isnot supported. Returning None.') return None # Fetch a dictionary containing the k-points information kpoints_dict = kpoints.get_dict() # Now unpack points, weights and check direct versus cartesian. Set to # None if mode is automatic. points = [] weights = [] cartesian = [] for key, value in kpoints_dict.items(): if key == 'points': if value is not None: for item in value: points.append(item[0]) weights.append(item[1]) # AiiDA wants cartesian and not direct flags, so revert cartesian.append(not item[2]) else: points = None weights = None cartesian = None # Make sure weights is ndarray if weights is not None: weights = np.array(weights) # Check that we only have similar elements in the direct list as # AiiDA can only work with all points being either in direct or cartesian # coordinates. if cartesian is not None: if not cartesian.count(cartesian[0]) == len(cartesian): raise ValueError('Different coordinate systems have been detected among the k-points.') cartesian = cartesian[0] # Modify dict to AiiDA spec kpoints_dict['points'] = points kpoints_dict['weights'] = weights kpoints_dict['cartesian'] = cartesian return kpoints_dict