Single point and general calculation#

Note

This tutorial can be executed as a jupyter notebook. Please copy the mock_registry folder found to your directory of execution, otherwise the mock code cannot locate the pre-computed calculations to be used as dummy output.

This notebook can be downloaded as silicon_sp.ipynb and silicon_sp.md

VaspInputGenerator provides a simplified interface for setting up calculations using pre-defined default inputs for calculation and for workflow execution.

In jupyter notebook, we normally need to load the necessary AiiDA environment:

%load_ext aiida
%aiida

However, for this tutorial, we will use a temporary AiiDA profile. All results will be destroyed once the session close.

We also assume that you have already installed the aiida-vasp plugin and the VASP has been installed locally on your computer.

If you do not have VASP available, run the code cell below to activate mock-vasp which will use cached results instead of running actual VASP calculations.

Setting up the basic environment#

Before we start any calculation, we need to do a few basic setups:

  1. Setup a Computer node, which is this computer (localhost)

  2. Tell AiiDA where to find the VASP executable, or using the mock-vasp executable.

  3. Upload the pseudopotentials (POTCAR) family

The following code creates Computer node:

from aiida_vasp.utils.temp_profile import *
print(load_temp_profile())


# Uncomment the below line to create a localhost Computer if you have not done so
comp = orm.Computer('localhost', 'localhost', transport_type='core.local', scheduler_type='core.direct')
comp.store()


# Some configuration may be needed for first-time user
comp.set_workdir('/tmp/aiida_run/')
comp.configure()
Profile<uuid='cb40adbaf8a1412cb3b8cea564eaa027' name='myprofile'>
<aiida.orm.authinfos.AuthInfo at 0x756ba74d1c50>

Here we use mock-vasp to simulate the VASP executable.

from pathlib import Path
import os
vasp_path = !which mock-vasp
vasp_code = orm.InstalledCode(comp, vasp_path[0], default_calc_job_plugin='vasp.vasp')
print(vasp_path[0])
vasp_code.label ='mock-vasp'
vasp_code.store()
os.environ['MOCK_VASP_REG_BASE'] = str((Path() / 'mock_registry').absolute())
os.environ['MOCK_VASP_UPLOAD_PREFIX'] = 'singlepoint'
print(os.environ['MOCK_VASP_REG_BASE'])
/home/docs/checkouts/readthedocs.org/user_builds/aiida-vasp-fork/envs/latest/bin/mock-vasp
/home/docs/checkouts/readthedocs.org/user_builds/aiida-vasp-fork/checkouts/latest/docs/source/tutorials/mock_registry

If you have VASP installed, uncomment and run the the code below to create the InstalledCode node`:

#vasp_path = !which vasp_std
#vasp_code = orm.InstalledCode(comp, vasp_path[0], default_calc_job_plugin='vasp.vasp')
#vasp_code.label ='vasp'
#vasp_code.store()

Hint

Setting the MOCK_VASP_VASP_CMD environment variable will allow the mock-vasp to run real VASP calculation and add the results to the registry whenever needed. This environmental variable should be set when generating the test/demo data.

We also need to upload the pseudopotential family, here we use the sample Si POTCAR with a family name of PBE.EXAMPLE:

from aiida_vasp.data.potcar import PotcarData, PotcarFileData
from pathlib import Path

print(PotcarData.upload_potcar_family(str(Path('potcars').absolute()), "PBE.EXAMPLE", "PBE.EXAMPLE"))
(1, 1, 1)

Running the calculation#

We can now set up the calculation, but first we need to create a StructureData node for the Si structure. Here we use the ase package to create the structure.

from ase.build import bulk
si = bulk('Si', 'diamond', 5.4)
si_node = orm.StructureData(ase=si)

Tip

It is also possible to create a StructureData node from a pymatgen.core.Structure object.

from pymatgen.core import Structure
si = Structure.from_spacegroup('Fm-3m', Lattice.cubic(5.4), ['Si'], [[0, 0, 0]]).get_primitive_structure()
si_node = orm.StructureData(pymatgen=si)

using the VaspInputGenerator class:

from aiida import orm
from aiida_vasp.protocols.generator import VaspInputGenerator

# This instantiate a VaspInputGenerator object and apply the preset
# The default name is `default` stored in the code repository.
# You can place your own preset at ~/.aiida-vasp/protocol_presets and use them for production
# calculations.
upd = VaspInputGenerator(protocol="balanced")
upd.get_builder(structure=si_node, code='mock-vasp@localhost', overrides={"potential_family": "PBE.EXAMPLE"})
Process class: VaspWorkChain
Inputs:
calc:
  metadata:
    options:
      max_wallclock_seconds: 3600
      resources:
        num_machines: 1
        tot_num_mpiprocs: 1
      withmpi: false
clean_workdir: true
code: mock-vasp@localhost
kpoints_spacing: 0.05
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
max_iterations: 5
parameters:
  incar:
    algo: fast
    ediff: 2.0e-06
    encut: 250
    gga: ps
    ismear: 0
    ispin: 2
    lasph: true
    lcharg: false
    lorbit: null
    lreal: false
    lvhar: true
    lwave: false
    nedos: 100
    nelm: 200
    nelmin: 4
    nwrite: 1
    prec: normal
    sigma: 0.05
potential_family: PBE.EXAMPLE
potential_mapping:
  Si: Si
settings: {}
structure: Si

The code block above create a VaspInputGenerator object and apply the preset for the Si structure. We can verify that this configures the ProcessBuilder object with the correct inputs:

upd.builder
Process class: VaspWorkChain
Inputs:
calc:
  metadata:
    options:
      max_wallclock_seconds: 3600
      resources:
        num_machines: 1
        tot_num_mpiprocs: 1
      withmpi: false
clean_workdir: true
code: mock-vasp@localhost
kpoints_spacing: 0.05
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
max_iterations: 5
parameters:
  incar:
    algo: fast
    ediff: 2.0e-06
    encut: 250
    gga: ps
    ismear: 0
    ispin: 2
    lasph: true
    lcharg: false
    lorbit: null
    lreal: false
    lvhar: true
    lwave: false
    nedos: 100
    nelm: 200
    nelmin: 4
    nwrite: 1
    prec: normal
    sigma: 0.05
potential_family: PBE.EXAMPLE
potential_mapping:
  Si: Si
settings: {}
structure: Si

If this looks all fine, we can run the calculation using the run_get_node method:

results = upd.run_get_node()
results
04/11/2026 04:17:17 PM <1124> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [15|VaspWorkChain|run_process]: launching VaspCalculation<20> iteration #1
04/11/2026 04:17:18 PM <1124> aiida.engine.processes.calcjobs.tasks: [WARNING] CalcJob<20> already marked as `CalcJobState.STASHING`, skipping task_monitor_job
04/11/2026 04:17:19 PM <1124> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [15|VaspWorkChain|results]: work chain completed after 1 iterations
04/11/2026 04:17:19 PM <1124> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [15|VaspWorkChain|on_terminated]: cleaned remote folders of calculations: 20
ResultAndNode(result={'remote_folder': <RemoteData: uuid: ea4b5598-fa43-4eb4-8c4c-ba297c32d896 (pk: 21)>, 'retrieved': <FolderData: uuid: 4fb7e694-3941-4b21-9ba6-cd26cca8bc60 (pk: 22)>, 'misc': <Dict: uuid: 28a59f92-8275-49ad-9224-8c10e311afdc (pk: 23)>}, node=<WorkChainNode: uuid: 427f0252-bf89-4af7-802d-54130d4fe4b6 (pk: 15) (aiida.workflows:vasp.v2.vasp)>)

Accessing the results#

The returned results contains the outputs as well as the WorkChainNode object representing the workflow that was executed.

workchain_node = results.node
workchain_node
<WorkChainNode: uuid: 427f0252-bf89-4af7-802d-54130d4fe4b6 (pk: 15) (aiida.workflows:vasp.v2.vasp)>

Note that our workchain_node has a pk as well as a uuid. Both of them are identifiers for the node. You can load the node using the load_node method:

from aiida.orm import load_node
node = load_node(workchain_node.pk)

at a later time to access the results.

Hint

Although pk and uuid are both unique identifiers for this node, they serve different purposes. The pk is a unique identifier within the current aiida database, while the uuid is a truelly unique identifier that would always refer to the same node, even if the data is exported and imported to different databases.

Useful information such as the total energy, forces and stresses are stored in the misc output node:

workchain_node.outputs.misc.get_dict()
{'total_energies': {'energy_extrapolated': -10.61487473,
  'energy_extrapolated_electronic': -10.61487473},
 'stress': [[17.87702469, 0.0, 0.0],
  [0.0, 17.87702469, 0.0],
  [0.0, 0.0, 17.87702469]],
 'forces': [[0.0, -0.0, -0.0], [-0.0, 0.0, 0.0]],
 'fermi_level': 6.19018828,
 'band_properties': {'cbm': 6.407,
  'vbm': 5.979,
  'is_direct_gap': False,
  'band_gap': 0.428},
 'version': '6.2.0',
 'magnetization': [-0.0004385],
 'site_magnetization': {'sphere': {'x': {'site_moment': {},
    'total_magnetization': {}},
   'y': {'site_moment': {}, 'total_magnetization': {}},
   'z': {'site_moment': {}, 'total_magnetization': {}}},
  'full_cell': [-0.0004385]},
 'run_stats': {'mem_usage_base': 30000.0,
  'mem_usage_nonl-proj': 471.0,
  'mem_usage_fftplans': 1184.0,
  'mem_usage_grid': 2809.0,
  'mem_usage_one-center': 12.0,
  'mem_usage_wavefun': 2160.0,
  'total_cpu_time_used': 3.335,
  'user_time': 3.276,
  'system_time': 0.059,
  'elapsed_time': 3.614,
  'maximum_memory_used': 99984.0,
  'average_memory_used': None},
 'run_status': {'nelm': 200,
  'nsw': 0,
  'last_iteration_index': [1, 13],
  'finished': True,
  'ionic_converged': None,
  'electronic_converged': True,
  'consistent_nelm_breach': False,
  'contains_nelm_breach': False,
  'nbands': 9},
 'notifications': [{'name': 'xc_enforced',
   'kind': 'ADVICE',
   'message': 'You enforced a specific xc type in the INCAR file but a different\ntype was found in the POTCAR file.\nI HOPE YOU KNOW WHAT YOU ARE DOING!',
   'regex': 'You enforced a specific xc type in the INCAR file but a different'},
  {'name': 'generic_box_error',
   'kind': 'WARNING',
   'message': 'Note: The following floating-point exceptions are signalling: IEEE_UNDERFLOW_FLAG IEEE_DENORMA\nthe initial magnetic moment with the MAGMOM tag. Note that a\ndefault of 1 will be used for all atoms. This ferromagnetic setup\nmay break the symmetry of the crystal, in particular it may rule\nout finding an antiferromagnetic solution. Thence, we recommend\nsetting the initial magnetic moment manually or verifying carefully\nthat this magnetic setup is desired.',
   'regex': 'Note: The following floating-point exceptions are signalling: IEEE_UNDERFLOW_FLAG IEEE_DENORMA'}]}

The advantage of using a database like AiiDA is that we can easily query for the results of our calculations. For example, to get all calculations that used the PBE.EXAMPLE family:

from aiida.orm import QueryBuilder
from aiida.plugins import WorkflowFactory
q = QueryBuilder()
q.append(orm.WorkChainNode, tag='workchain', project=['*'])
q.append(orm.Str, with_outgoing='workchain', edge_filters={'label': 'potential_family'},
         filters={'attributes.value': 'PBE.EXAMPLE'})
q.all()
[[<WorkChainNode: uuid: 427f0252-bf89-4af7-802d-54130d4fe4b6 (pk: 15) (aiida.workflows:vasp.v2.vasp)>]]

The QueryBuilder object allows us to construct complex queries path that combine different nodes and their properties. The first append method says that we need a WorkChainNode and we want to return the node itself. The second append method defines that such WorkChainNode should have an outgoing Dict node with a label of potential_family and a value of PBE.EXAMPLE. This filters the results to only include nodes that used the PBE.EXAMPLE family. The final q.all() method returns a list of all the combinations of node and edges that match the query.