How to setup calculations#

How to know what inputs are allowed for a calculation/workchain#

AiiDA workchains define their inputs using input ports and port namespaces. Each workchain exposes a set of input ports (such as structure, parameters, kpoints, etc.), and these can be grouped into namespaces for logical organization (e.g., parameters.incar). This structure allows for flexible and hierarchical input definitions, making it easier to manage complex workflows.

The easiest way to explore what inputs a workchain can take is to use the ProcessBuilder with tab completion

from aiida.plugin import WorkFlowFactory
builder = WorkflowFactory('vasp.vasp').get_builder()
builder.<tab>

Alternatively, one can use verdi commandline tool to inspect a workchain:

verdi plugin list aiida.workflows vasp.vasp

The third way is to look into the source code, for example, the VaspWorkChain has the following lines :

class VaspWorkChain:

    @classmethod
    def define(cls, spec: ProcessSpec) -> None:
        super(VaspWorkChain, cls).define(spec)
        spec.expose_inputs(cls._process_class, exclude=('metadata',))
        spec.expose_inputs(
            cls._process_class, namespace='calc', include=('metadata',), namespace_options={'populate_defaults': True}
        )

        # Use a custom validator for backward compatibility
        # This needs to be removed in the next major release/formalized workchain interface
        spec.inputs.validator = validate_calc_job_custom
        spec.inputs['calc']['metadata']['options']['resources']._required = False

        spec.input('kpoints', valid_type=orm.KpointsData, required=False)
        spec.input(
            'potential_family',
            valid_type=orm.Str,
            required=True,
            serializer=to_aiida_type,
            validator=potential_family_validator,
        )

In the code above, the spec.input call define a series of ports that a calculation may take as inputs. An input port may contain certain default value, and it may or may not be required by a calculation. A more advanced usage is the spec.expose_inputs call, which expose existing input ports of another calculation/workchain to the current workchain. In above, the inputs of a VaspCalculation is exposed at the top level as well as nested in a calc. However, the latter only contains a metadata port, which is a special input port that allow defining options with request resource and wall-time limits or a VaspCalculation.

How to setup inputs of a calculation or a workflow#

In this document we will learn how to pass necessary input to the calculation and workflows provided by aiida-vasp.

The input and outputs of the workflows as implemented as WorkChain are AiiDA’s Data types. The Data is a subclass of the Node class, which represents data that is stored in the database and could be used by other Process nodes. A WorkChain has a set of pre-defined input and output ports (which can be dynamic, if needed) that specifies the types of data that can be passed to and from it.

Some python native types (float, dict, str) have their Data counterparts, such as Float, Dict, Str - they can be used as inputs to the workflows directly, but the conversion still takes place internally.

There are tree ways to pass inputs to the workflows. The most general way is to pass a dict object contains key-values pairs of the data to be passed to each input port of the workchain.

Using a dict object as inputs to a Process#

from aiida.engine import submit

submit(Process, **inputs)

where Process is a class of the process to be launched. In aiida-vasp, it may be VaspCalculation, VaspWorkChain or other provided processes. The inputs is a dictionary containing a nested key-value pairs defining inputs for each port of the process. A typical inputs dictionary for VaspWorkChain looks like

inputs = {
  'structure': si_structure,  # An instance of aiida.orm.StructureData
  'parameters': incar_tags,   # An instance of aiida.orm.Dict
  'calc':
    {'options':
      {'resources':
         {
          'num_machines': 1
         }
      }
    },
  # ....
}

Note

The first argument should be the workchain class, followed by keyword inputs for each input port. The run_get_node function launches the workchain with the current python interpreter, and in production environments one typically uses the submit function instead. In this case the workchain is stored in the database and marked to be executed by the daemon.

For more complex workflows, we typically construct a dictionary and use the **inputs syntax to pass it to function that launches the workchain.

The ProcessBuilder class#

The approach above is very general but can be cumbersome for complex workflows with many inputs. In addition, the user must somehow remember all the input port names and their types. To address this problem, AiiDA provides the ProcessBuilder class, which can be used to construct the inputs for a workflow in a more structured and interactive way. For example (to be run inside a verdi shell)

from aiida.engine import run_get_node
from aiida.plugins import WorkflowFactory

builder = WorkflowFactory('vasp.v2.vasp').get_builder()
builder.parameters = Dict(dict={'incar': {'encut': 500, 'ismear': 0}})
builder.kpoints_spacing = 0.05

The builder object has attributes corresponding to the input ports of the VaspWorkChain. The conversion and validation of the inputs is done automatically when it is assigned to the attribute.

The InputGenerator class#

While ProcessBuilder class is a convenient, one still has to write inputs explicitly. To make it even easier to construct inputs, we provide the VaspInputGenerator class, which can be used to generate a ProcessBuilder object using pre-defined protocols The main advantage is that it allows the user to start from a predefined set of input values which can be modified or added to.

There two kinds of pre-defined defaults that a VaspInputGenerator can uses. The first one is based on protocols, which defines base parameters for calculations and workflows. The protocols may contain the default INCAR tags, the k-points spacing to be used and the pseudopotential configurations as well as higher level control parameters for workchains.

The default balanced protocol is stored in the <root>/src/protocols/ folder with the following content:

default_potential_mapping: &potential_mapping
  Ac: Ac
  Ag: Ag
  Al: Al
  Ar: Ar
  As: As
  Au: Au
  B: B
  Ba: Ba_sv
  Be: Be_sv
  Bi: Bi
  Br: Br
  C: C
  Ca: Ca_sv
  Cd: Cd
  Ce: Ce
  Cl: Cl
  Co: Co
  Cr: Cr_pv
  Cs: Cs_sv
  Cu: Cu
  Dy: Dy_3
  Er: Er_3
  Eu: Eu
  F: F
  Fe: Fe_pv
  Ga: Ga_d
  Gd: Gd
  Ge: Ge_d
  H: H
  He: He
  Hf: Hf_pv
  Hg: Hg
  Ho: Ho_3
  I: I
  In: In_d
  Ir: Ir
  K: K_sv
  Kr: Kr
  La: La
  Li: Li_sv
  Lu: Lu_3
  Mg: Mg_pv
  Mn: Mn_pv
  Mo: Mo_pv
  N: N
  Na: Na_pv
  Nb: Nb_pv
  Nd: Nd_3
  Ne: Ne
  Ni: Ni_pv
  Np: Np
  O: O
  Os: Os_pv
  P: P
  Pa: Pa
  Pb: Pb_d
  Pd: Pd
  Pm: Pm_3
  Pr: Pr_3
  Pt: Pt
  Pu: Pu
  Rb: Rb_sv
  Re: Re_pv
  Rh: Rh_pv
  Ru: Ru_pv
  S: S
  Sb: Sb
  Sc: Sc_sv
  Se: Se
  Si: Si
  Sm: Sm_3
  Sn: Sn_d
  Sr: Sr_sv
  Ta: Ta_pv
  Tb: Tb_3
  Tc: Tc_pv
  Te: Te
  Th: Th
  Ti: Ti_pv
  Tl: Tl_d
  Tm: Tm_3
  U: U
  V: V_pv
  W: W_sv
  Xe: Xe
  Y: Y_sv
  Yb: Yb_2
  Zn: Zn
  Zr: Zr_sv

magmom_mapping: &magmom_mapping
  Ce: 5
  Co: 5
  Cr: 5
  Dy: 5
  Eu: 10
  Fe: 5
  Gd: 7
  Ho: 4
  La: 0.6
  Lu: 0.6
  Mn: 5
  Mo: 5
  Nd: 3
  Ni: 5
  Pm: 4
  Pr: 2
  Sm: 5
  Tb: 6
  Tm: 2
  V: 5
  W: 5
  Yb: 1
  default: 1.0

default_inputs:
    clean_workdir: True
    kpoints_spacing: 0.05
    max_iterations: 5
    potential_family: 'PBE.54'
    potential_mapping: *potential_mapping
    magmom_mapping: *magmom_mapping
    meta_parameters:
      ediff_per_atom: 1.0e-6
    settings:
    ldau_mapping:   # Mapping for LDU+U
      # mappings:
      #   Fe: ['d', 4.0]
      #   Co: ['d', 4.0]
    parameters:
      incar:
        prec: accurate
        encut: 550
        lreal: false
        ismear: 0
        sigma: 0.05
        ispin: 2
        algo: normal
        nwrite: 1
        lwave: False
        nelm: 200
        nelmin: 4
        lasph: true
        lvhar: true
        lorbit: 11
        nedos: 2000
        gga: ps
    calc:
        metadata:
            options:
                resources:
                    num_machines: 1
                max_wallclock_seconds: 43200  # Twelve hours
                withmpi: True

default_protocol: balanced

protocols:
  balanced:
    description: 'Balanced protocol for VASP calculations. Previously the UCLRelaxSet, but no default LDAU mapping is apllied.'

While protocols default how parameters of each calculation and workchain are defined. The PresetConfig offers control at a higher level - it records the default input set to be used as well as any code-specific overrides needed.

The default configuration is stored in the <root>/src/aiida_vasp/protocols/presets/default.yaml with the following content:

# The Preset contains mostly code-specific settings controlling the
# execution of calculations
name: default
default_protocol: balanced
default_code: vasp@localhost
default_options:
  resources:
    num_machines: 1
    tot_num_mpiprocs: 1
  max_wallclock_seconds: 1800

# Code specific options
code_specific:
  "vasp@localhost":
    options:
      max_wallclock_seconds: 1800
  "mock-vasp@localhost":
    options:
        max_wallclock_seconds: 3600
        resources:
            tot_num_mpiprocs: 1
            num_machines: 1
        withmpi: false
    settings: {}
    incar:
        nedos: 100
        lorbit: null
        encut: 250
        prec: normal
        algo: fast
        lcharg: false

This default preset file is used for tests and documentation examples with mock-vasp@localhost code.

Using the VaspInputGenerator class is intended to simply the input construction process. For example, to construct a VaspWorkChain with the default INCAR tags, k-points spacing and pseudopotential for a silicon structure (si_node), can be a simple as:

from aiida_vasp.protocols.generator import VaspInputGenerator

upd = VaspInputGenerator()
upd.get_builder(structure=si_node, code='<my_code>@<computer>')
upd.submit()

When not using VaspInputGenerator, the get_builder_from_protocol method of the workchain can be used to obtain the ProcessBuilder directly. Alternatively, the workchain will have to specified either through a multi-line mini script using the ProcessBuilder obtained with get_builder method or a large nested dictionary for complex workflows.

Nevertheless, one should still inspect the actual input passed to the workchain, this can be done by simply returning the builder attribute of the VaspInputGenerator object.

upd.builder  # Should print the input to each port namespace of the workchain

There are InputGenerator class specific to each class:

WorkChain class

InputGenerator class

aiida_vasp.workchains.v2.relax:VaspRelaxWorkChain

aiida_vasp.protocols.generator:VaspRelaxInputGenerator

aiida_vasp.workchains.v2.vasp:VaspWorkChain

aiida_vasp.protocols.generator:VaspInputGenerator

aiida_vasp.workchains.v2.band:VaspBandsWorkChain

aiida_vasp.protocols.generator:VaspBandsInputGenerator

aiida_vasp.workchains.v2.band:VaspHybridBandsWorkChain

aiida_vasp.protocols.generator:VaspHybridBandsInputGenerator

aiida_vasp.workchains.v2.converge:VaspConvergenceWorkChain

aiida_vasp.protocols.generator:VaspConvegenceInputGenerator

Customize protocols and presets#

In practice, one may want to have their own default inputs This can be achieved by creating a new <preset_name>.yaml file inside ~/.aiida-vasp/presets with the desired settings. The default configuration shown above can be used as a starting point.

It is also possible to have your own protocol - simply place the YAML files in the same ~/.aiida-vasp/<workchain tag>/<alias>.yaml folder.

The <workchain tag> is an alias for the workchains.

WorkChain Tag

Workchain Class

vasp

aiida_vasp.workchains.v2.vasp:VaspWorkChain

relax

aiida_vasp.workchains.v2.relax:VaspRelaxWorkChain

band

aiida_vasp.workchains.v2.relax:VaspBandsWorkChain

band

aiida_vasp.workchains.v2.relax:VaspHybridBandsWorkChain

conv

aiida_vasp.workchains.v2.converge:VaspConvergenceWorkChain

The <alias> is an user defined alias for the protocol set.

For example, to have a custom relaxation protocol, create a file at ~/.aiida-vasp/protocols/relax/custom.yaml with the follow content:

# Default input values for the workflow
default_inputs:
  verbose: False               # Verbosity of the workflow output
  base_workchain_protocol: balanced

# Protocol definitions
protocols:
  posonly:
    description: |
      A protocol for relaxation that only relax positions
    relax_settings:
      shape: false
      volume: false
      positions: true
  fixxy:
    description: "Relax only the z axis"
    vasp:
      parameters:
        ioptcell: 0 0 0 0 0 0 0 0 1
    base_workchain_protocol: balanced

The protocol above can be referenced using VaspRelaxWorkChain.get_builder_from_protocol(...., protocol="posonly@custom"

Caution

One should be careful when modifying or extending existing preset or protocol files as they may render calculations results incompatible for comparison. Thankfully, AiiDA still preservers the full provenance of the calculation can be traced as the actual inputs are faithfully stored in the database.

How to fix the atoms during relaxation#

Atoms may be fined by setting the dynamics input port using a dictionary:

dynamics = {
  'positions_dof': [
    [1, 1, 1],
    [0, 0, 0],
    [0, 0, 1],
  ]
}

This means to:

T T T
F F F
T T F

in the generated POSCAR file for the three atoms for selective dynamics. The T means that the atom is allow to move in this degree of freedom, and F means that the atom is fixed in this direction.

For example, if one wants to completely fix all atoms between \(\mathrm{2 \AA}\) to \(\mathrm{4 \AA}\) in the z direction:

z = builder.structure.get_ase().positions[:, 2]
to_fix  = (z < 4) & (z > 2)
dof = [[1, 1, 1] if not fix else [0, 0, 0] for fix in to_fix]

builder.dynamics = {
  'positions_dof': dof
}

Caution

The T and F applies to the direct (fractional) coordinates. To fix the cartesian coordinates, the $$lattice vectors needs to align with the x, y, z direction respectively.

Using ASE constraints for selective dynamics#

ASE constraint objects are automatically converted when assigned to builder.dynamics:

from ase.constraints import FixAtoms, FixScaled

# Fix atoms completely
atoms.set_constraint(FixAtoms(indices=[0, 1, 2]))
builder.dynamics = atoms

# Or fix specific directions (e.g., z for slabs)
atoms.set_constraint(FixScaled([0, 1, 2], mask=(False, False, True)))
builder.dynamics = atoms

Supported: FixAtoms (fixes all directions), FixScaled (fixes fractional directions), FixCartesian (requires an axis-aligned/diagonal cell matrix). Multiple constraints are accumulated. ASE’s mask convention (True=fixed) is automatically converted to VASP’s convention (True=movable).

Alternatively, manually specify the positions_dof array:

# Manually define: True=movable, False=fixed
builder.dynamics = {
    'positions_dof': [
        [False, False, False],  # atom 0: fixed
        [False, False, False],  # atom 1: fixed
        [True, True, False],    # atom 2: z fixed, x,y movable
        [True, True, True],     # atom 3: free
    ]
}

How set initial magnetization for magnetic calculations#

VASP uses the MAGMOM tag in the INCAR file for the initial magnetic momenets. This tag can be an explicit list of moments for each specie:

MAGMOM = 0.0 1.0 5.0 5.0

or a compact format with * indicating repeats:

MAGMOM = 0.1*1 1.0*1 5.0*2

Very often, we want the initial magnetic moments to be specie-dependent.

We can of course explictly set the MAGMOM tags programatically::

config = {'O': 0.0, 'Fe': 5.0}
builder = VaspWorkChain.get_builder()
builder.structure = structure
builder.parameters = {
  'incar': {
    ....,
    'magmom': [config.get(site.kind_name, 0.6) for site in structure.sites]
  }
}

This will assign the initial magnetic moment of all Fe atoms to be 5.0 and O atoms to be 0.0, other species will have 0.6 as the initial magnetic moment.

In fact, VaspWorkChain provides an input port to do exactly this. The above code is equivalent to:

config = {'O': 0.0, 'Fe': 5.0, 'default': 0.6}
builder = VaspWorkChain.get_builder()
builder.magmom_mapping = config

In addition, the ISPIN tag will be set to 2 if it is not explicitly defined in the input.

Note

At the time of writing, magmom_mapping port cannot be used for setting the initial three-dimensional spin for none-collinear spin calculations.

How configure LDA+U calculations#

LDA+U calcualtion in VAPS requires the following tags to be set in INCAR:

  • LDAU: should be set to .TRUE. as an overall switch of +U

  • LDAUTYPE: determines the type of the LDA+U formulism. Usually 2 is used which uses only LDAUU.

  • LDAUU: sets the $\(U\)$ value of each specie

  • LDAUJ: sets the $\(J\)\( value of each specie. The value of \)\(U-J\)\( is often referred as \)\(U_{eff}\)$ for a sepcific specie. This parameter is only used when LDAUTYPE is 1.

  • LDAUL: controls which angular momentum channel the +U should be applied for each specie. For 3d transition metals, the angular momentum channel is 2. The U is not applied if it is set to -1.

For example, to add U on Ni atoms with $\(U_eff=6 \mathrm{eV}\)$ in NiO, we can set

LDAU = .TRUE.
LDAUTYPE = 2
LDAUU = 6.0 0.0
LDAUL = 2 -1

This assumes that Ni comes first in the POSCAR and POTCAR, followed by O.

We can explicitly define these tags in the inputs to various workchain. However, it is easier (and less prone to mistake) to use the ldau_mapping port for automatically generating these tags:

config = {'mapping': {'Ni': [2, 6.0], 'Fe': [2, 4.0]}}
builder = VaspWorkChain.get_builder()
builder.structure = structure
builder.ldau_mapping = config

which sets $\(U_{eff} = 6.0 \mathrm{eV}\)\( for Ni's \)\(l=2\)\( angular momentum channel, e.g. its \)\(3d\)$ Internally, the VaspWorkChain uses get_ldau_keys function to generate the INCAR tags and the available keys can be found in its docstring.