Source code for aiida_vasp.parsers.content_parsers.potcar
"""
POTCAR parser.
--------------
The file parser that handles the parsing of POTCAR files. Also contains methods to
find, import, compose and write POTCAR files.
"""
from itertools import groupby
import os
from pathlib import Path
import re
from parsevasp.potcar import Potcar
from aiida_vasp.parsers.content_parsers.base import BaseFileParser
from aiida_vasp.utils.aiida_utils import get_data_class
from aiida_vasp.utils.delegates import delegate_method_kwargs
[docs]
class PotcarParser(BaseFileParser):
"""A lightweight interface that provides access to POTCAR metadata parsing.
Similar to the other content parser for VASP in structure, but only used directly in the POTCAR
handling logic.
"""
DEFAULT_SETTINGS = {}
PARSABLE_QUANTITIES = {}
def _init_from_handler(self, handler):
"""Initialize using a file like handler.
Parameters
----------
handler : object
A file like object that provides the necessary content to be parsed.
"""
try:
self._content_parser = Potcar(file_handler=handler, logger=self._logger)
except SystemExit:
self._logger.warning('Parsevasp exited abnormally.')
@property
def metadata(self):
"""Return the metadata Potcar instance."""
return self._content_parser
def _init_from_data(self, data):
"""No need to init from an AiiDA data structure."""
raise NotImplementedError('PotcarParser does not implement a _init_from_data() method.')
def _content_data_to_content_parser(self):
"""Since no need to accept AiiDA data structure, no need to convert it."""
raise NotImplementedError('PotcarParser does not implement a _content_data_to_content_parser() method.')
[docs]
class PotcarIo(): # pylint: disable=useless-object-inheritance
"""
Deals with VASP input output of POTCAR files.
Instanciate with one of the following kwargs:
:param path: (string) absolute path to the POTCAR file
:param potcar_node: a PotcarData node
:param potcar_file_node: a PotcarFileNode
:param contents: a string with the POTCAR content
"""
def __init__(self, **kwargs):
"""Init from Potcar object or delegate to kwargs initializers."""
self.potcar_obj = None
self.sha512 = None
self.init_with_kwargs(**kwargs)
[docs]
@delegate_method_kwargs(prefix='_init_with_')
def init_with_kwargs(self, **kwargs):
"""Delegate initialization to _init_with - methods."""
def _init_with_path(self, file_path):
"""Initialize with a path."""
node, _ = get_data_class('vasp.potcar').get_or_create_from_file(file_path=file_path)
self.sha512 = node.sha512
def _init_with_potcar_file_node(self, node):
"""Initialize with an existing potential file node."""
self.sha512 = node.sha512
def _init_with_potcar_node(self, node):
"""Initialize with an existing potential node."""
self._init_with_potcar_file_node(node.find_file_node())
def _init_with_contents(self, contents):
"""Initialize with a string."""
try:
contents = contents.encode('utf-8')
except AttributeError:
pass
node, _ = get_data_class('vasp.potcar').get_or_create_from_contents(contents)
self.sha512 = node.sha512
@property
def file_node(self):
return get_data_class('vasp.potcar').find_one(sha512=self.sha512).find_file_node()
@property
def node(self):
return get_data_class('vasp.potcar').find_one(sha512=self.sha512)
@property
def content(self):
return self.file_node.get_content()
[docs]
@classmethod
def from_(cls, potcar):
"""Determine the best guess at how the input represents a POTCAR file and construct a PotcarIo instance based on that."""
if isinstance(potcar, str):
try:
path_exists = Path(potcar).exists()
except OSError:
# We failed possibly due to a too long filename or that the potcar content is in fact
# potcar content, revert to the os module to check if it exists
path_exists = os.path.exists(potcar)
if path_exists:
potcar = cls(path=potcar)
else:
potcar = cls(contents=potcar)
elif isinstance(potcar, get_data_class('vasp.potcar')):
potcar = cls(potcar_node=potcar)
elif isinstance(potcar, get_data_class('vasp.potcar_file')):
potcar = cls(potcar_file_node=potcar)
elif isinstance(potcar, PotcarIo):
pass
else:
potcar = cls.from_(potcar)
return potcar
def __eq__(self, other):
return self.sha512 == other.sha512
[docs]
class MultiPotcarIo(): # pylint: disable=useless-object-inheritance
"""Handle file i/o for POTCAR files with one or more potentials."""
def __init__(self, potcars=None):
self._potcars = []
if potcars:
for potcar in potcars:
self.append(PotcarIo.from_(potcar))
[docs]
def append(self, potcar):
self._potcars.append(PotcarIo.from_(potcar))
[docs]
def write(self, path):
path = Path(path)
with path.open('wb') as dest_fo:
for potcar in self._potcars:
dest_fo.write(potcar.content)
[docs]
@classmethod
def read(cls, path):
"""Read a POTCAR file that may contain one or more potentials into a list of PotcarIo objects."""
potcars = cls()
path = Path(path)
with path.open('r', encoding='utf8') as potcar_fo:
potcar_strings = re.compile(r'\n?(\s*.*?End of Dataset\n)', re.S).findall(potcar_fo.read())
for potcar_contents in potcar_strings:
potcars.append(PotcarIo.from_(potcar_contents))
return potcars
@property
def potcars(self):
return self._potcars
[docs]
@classmethod
def from_structure(cls, structure, potentials_dict):
"""Create a MultiPotcarIo from an AiiDA `StructureData` object and a dictionary with a potential for each kind in the structure."""
symbol_order = cls.potentials_order(structure)
return cls(potcars=[potentials_dict[symbol] for symbol in symbol_order])
[docs]
def get_potentials_dict(self, structure):
"""
Get a dictionary {kind_name: PotcarData} that would fit the structure.
If the PotcarData contained in MultiPotcarIo do not match the structure, an exception is raised.
"""
structure_elements = structure.get_symbols_set()
if structure_elements != self.element_symbols:
raise ValueError('structure elements do not match POTCAR elements')
if len(structure.get_kind_names()) != len(structure_elements):
raise ValueError('structure has more kind names than elements')
element_potcars = {potcario.node.element: potcario.node for potcario in self.potcars}
return {kind.name: element_potcars[kind.symbol] for kind in structure.kinds}
@property
def element_symbols(self):
return {potcario.node.element for potcario in self.potcars}
[docs]
@classmethod
def potentials_order(cls, structure):
return [kind[0] for kind in cls.count_kinds(structure)]
[docs]
@classmethod
def count_kinds(cls, structure):
"""
Count consecutive kinds that compose the different sites.
:return: [(kind_name, num), ... ]
"""
kind_name_order = [site.kind_name for site in structure.sites]
groups = groupby(kind_name_order)
counts = [(label, sum(1 for _ in group)) for label, group in groups]
return counts