Parsing

AiiDA-VASP provides flexible parsing of VASP output files to store data in the AiiDA database and repository.

The quantities that can be parsed are now fully customisable. The user interface for configuring the parsing settings takes place in the settings['parser_settings'] dictionary entry. The default parser_settings is presently:

Warning

Notice however, that even though the parser and the node composer is configurable, the output check in AiiDA will complain that your newly added custom node is not detected in the spec definitions of for instance your VaspCalculation. If you do add additional nodes outside the ones defined in the code already, please consider to also add it to the spec.output section in the VaspCalculation class and potentially also to workchains if need be.


NODES = {
    'misc': {
        'link_name':
            'misc',
        'type':
            'dict',
        'quantities': [
            'total_energies',
            'maximum_stress',
            'maximum_force',
            'magnetization',
            'notifications',
            'run_status',
            'run_stats',
            'version',
        ]
    },
    'kpoints': {
        'link_name': 'kpoints',
        'type': 'array.kpoints',
        'quantities': ['kpoints'],
    },
    'structure': {
        'link_name': 'structure',
        'type': 'structure',
        'quantities': ['structure'],
    },
    'poscar-structure': {
        'link_name': 'structure',
        'type': 'structure',
        'quantities': ['poscar-structure'],
    },
    'trajectory': {
        'link_name': 'trajectory',
        'type': 'array.trajectory',
        'quantities': ['trajectory'],
    },
    'forces': {
        'link_name': 'forces',
        'type': 'array',
        'quantities': ['forces'],
    },
    'stress': {
        'link_name': 'stress',
        'type': 'array',
        'quantities': ['stress'],
    },
    'bands': {
        'link_name': 'bands',
        'type': 'array.bands',
        'quantities': ['eigenvalues', 'kpoints', 'occupancies'],
    },
    'dos': {
        'link_name': 'dos',
        'type': 'array',
        'quantities': ['dos'],
    },
    'energies': {
        'link_name': 'energies',
        'type': 'array',
        'quantities': ['energies'],
    },
    'projectors': {
        'link_name': 'projectors',
        'type': 'array',
        'quantities': ['projectors'],
    },
    'born_charges': {
        'link_name': 'born_charges',
        'type': 'array',
        'quantities': ['born_charges'],
    },
    'dielectrics': {
        'link_name': 'dielectrics',
        'type': 'array',
        'quantities': ['dielectrics'],
    },
    'hessian': {
        'link_name': 'hessian',
        'type': 'array',
        'quantities': ['hessian'],
    },
    'dynmat': {
        'link_name': 'dynmat',
        'type': 'array',
        'quantities': ['dynmat'],
    },
    'chgcar': {
        'link_name': 'chgcar',
        'type': 'vasp.chargedensity',
        'quantities': ['chgcar'],
    },
    'wavecar': {
        'link_name': 'wavecar',
        'type': 'vasp.wavefun',
        'quantities': ['wavecar'],
    },
    'site_magnetization': {
        'link_name': 'site_magnetization',
        'type': 'dict',
        'quantities': ['site_magnetization'],
    },
}


class ParserDefinitions(object):  # pylint: disable=useless-object-inheritance
    """Container of parser definitions"""

    def __init__(self, file_parser_set='default'):
        self._parser_definitions = {}
        self._init_parser_definitions(file_parser_set)

    @property
    def parser_definitions(self):
        return self._parser_definitions

    def add_parser_definition(self, filename, parser_dict):
        """Add custum parser definition"""
        self._parser_definitions[filename] = parser_dict

    def _init_parser_definitions(self, file_parser_set):
        """Load a set of parser definitions."""
        if file_parser_set not in FILE_PARSER_SETS:
            return
        for file_name, parser_dict in FILE_PARSER_SETS.get(file_parser_set).items():
            self._parser_definitions[file_name] = deepcopy(parser_dict)


As you can see, the <node_name> named bands is composed of three quantities, the eigenvalues, the kpoints and the occupancies. You typically need all three when you analyze the band structure. Similarly it is possible to customize the output stored by composing different quantities. However, the regular user should not need to utilize these functions. It is more usefull when developing new workchains, where it makes sense to introduce a new output container which does not already exists.

There are four ways to interact and set the parser properties.

  1. Using a boolean:

    settings['parser_settings'] = {'add_<node_name>': True}
    

This will enable the parsing of <node_name>. This is the recommended way to interact with the current parser.

  1. Specifying a list:

    settings['parser_settings'] = {'add_<node_name>': ['<quantity1>', '<quantity2>', ... ]'}
    

    Where the list contains strings with quantity names that should be used for this node. This will work only for predefined nodes and available quantities. This options is not for regular users.

  2. Specifying a dict:

    settings['parser_settings'] = {'add_<node_name>': { 'type': '<node_type>', 'quantities': ['<quantity1>', '<quantity2>', ...], 'link_name': '<link_name>'}}
    

    This will define a new custom node. The link_name is optional and will be set to the <node_name> if it is not present. This option is not for regular users.

  3. Defining the parsing interactively

    The three ways above were all mediated by modifying settings. There is also an interactive way, if you have an instance of VaspParser:

    VaspParser.add_custom_node(node_name, node_definition)
    

    where the format for the node_definition is as in the previous example with the custom nodes.

The parser will check for any error detected for the underlying VASP calculation. This information is stored in the notification quantity, which contains a list of error/warnings detected. By default, a non-zero exit state is returned if any critical error is found. The default list of critical errors is defined under the critical_notifications key inside DEFAULT_OPTIONS. Additional settings may also be supplied under parser_settings to modify the behaviours:

  • ignore_all_errors: a boolen value to control whether all notifications should be ignored, defaults to False.

  • critical_notifications: a dictionary with items like 'add_<error>': True for controlling which errors to be regarded as critical or not.

Composing the quantities into an output node

A NodeComposer has been added to compose output nodes based on a given set of quantities. It can be initialised in two different ways:

composer = NodeComposer(vasp_parser=...)
composer = NodeComposer(file_parsers=[...])

where either a VaspParser object or a list of FileParser objects are required for the NodeComposer to get the required quantities for a node. Here is an example for the interface for composing a node from the VasprunParser test:

composer = NodeComposer(file_parsers=[vasprun_parser])
data_obj = composer.compose('array.kpoints', quantities=['kpoints'])

For most node types there are default quantities defined as:

.. literalinclude:: ../../../aiida_vasp/parsers/node_composer.py
start-after

NODE_TYPES

end-before

NodeComposer

so that the above could be shortened to:

data_obj = composer.compose('array.kpoints')

Short-cut properties

Since none of the quantities will actually return an AiiDA output node anymore, there are some built-in short-cut properties in order to save some typing and also to avoid using the get_quantity interface directly. An example would be:

parser = PoscarParser(file_path=path)
result = parser.structure

where the whole composer part is dealt with internally:

@property
def structure(self):
    if self._structure is None:
        composer = NodeComposer(file_parsers=[self])
        self._structure = composer.compose('structure', quantities=['poscar-structure'])
    return self._structure

At the moment existing short-cut properties are:

  • PoscarParser.structure

  • KpointsParser.kpoints

  • OutcarParser.parameter

  • DoscarParser.dos

  • WavecarParser.wavecar

  • ChgcarParser.chgcar

  • IncarParser.incar (This is an exception in so far that it actually just returns a dictionary)

Prominently missing from this list are bands from the EigenvalParser and everything from the VasprunParser. For the former the reason is that bands require quantities from other FileParsers `and for the latter the name space is crowded.

Quantity definitions and alternatives

For alternatives, quantity.name can be defined, where name is the identifier of the main quantity this quantity is an alternative to. In general the alternative system now works in the following way:

  • The main quantity (the one with the highest priority) has a list with alternatives.

  • All other quantities will if their identifier does not equal their quantity.name add their identifier to the quantity.names alternative list.

  • When a quantity gets requested as part of node, the VaspParser will check, whether that quantity can be parsed. If not it will go through the list of alternatives and check those.

Setting the alternatives list on the main quantity is only required if a well defined sequence of priority is required by the developer. Otherwise the alternatives lists will be automatically set when loading the quantities.

The quantities now have a built-in mapping between their quantity identifier and the main quantity they are an alternative to, helps resolving issues of assigning the correct quantities to a node. E.g. the Fermi level will now always be fermi_level in misc and not sometimes outcar-fermi_level if it has been parsed from OUTCAR.