1. Structure relaxation

In this tutorial, we will first perform a calculation of the electronic structure of wurtzite-AlN, following by a relaxation of the structure. The idea here is to understand how we can use the builder to set up the inputs for the call script and how to interface with the bundled workchain for relaxing structures.

Before continuing it is worthwhile to consider symmetry and work with that in a more general manner. It is good practice to familiar ourself about these topics from time to time as much of it is easily forgotten and taken for granted, giving us problems downstream that could be avoided with a bit better oversight initially.

Symmetrizing structures

It is recommended to symmetrize the crystal structure of interest if the space group type is known. This can be done by using for instance spglib, which should already be installed when installing AiiDA-VASP. Let’s take an example POSCAR of wurtzite-type SiC structure, which can be obtained from the Materials project database:

Si2 C2
1.0
3.092007 0.000000 0.000000
-1.546004 2.677757 0.000000
0.000000 0.000000 5.073347
Si C
2 2
direct
0.333333 0.666667 0.499589 Si
0.666667 0.333333 0.999589 Si
0.333333 0.666667 0.875411 C
0.666667 0.333333 0.375411 C

This is already symmetrized but we can symmetrize it even more if we want more number of digits for \(\sqrt{3}\) and \(1/3\) that often appear in hexagonal crystals. An ad-hoc Python script to symmetrize this may look like:

import numpy as np
import spglib

poscar_lines = """Si2 C2
1.0
3.092007 0.000000 0.000000
-1.546004 2.677757 0.000000
0.000000 0.000000 5.073347
Si C
2 2
direct
0.333333 0.666667 0.499589 Si
0.666667 0.333333 0.999589 Si
0.333333 0.666667 0.875411 C
0.666667 0.333333 0.375411 C""".splitlines()

lattice = np.genfromtxt(poscar_lines[2:5]).reshape(3, 3)
points = np.genfromtxt(poscar_lines[8:12]).reshape(4, -1)[:, :3]
numbers = [14, 14, 6, 6]
cell = (lattice, points, numbers)

sym_cell = spglib.refine_cell(cell)
print('Spacegroup:', spglib.get_spacegroup(cell))
np.set_printoptions(precision=15)
[print(sym_cell[i]) for i in range(3)]

Upon saving and executing this Python script, the space group should be printed as P6_3mc (186). And the symmetrized cell should be:

[[ 3.092007293580808  0.                 0.               ]
 [-1.546003646790404  2.677756864927749  0.               ]
 [ 0.                 0.                 5.073347         ]]
[[0.333333333333333 0.666666666666667 0.499589         ]
 [0.666666666666667 0.333333333333333 0.999589         ]
 [0.333333333333333 0.666666666666667 0.875411         ]
 [0.666666666666667 0.333333333333333 0.375411         ]]
[14 14  6  6]

The third component of the atomic position, i.e. 0.499589, may be expected to be zero or 0.5, but this can never be achieved exactly by spglib, or other codes working with numerics. The take home message from this is that you should know what kind of structure you are looking into and from time to time make checks to make sure you are still where you think you are. Let us continue and relax this structure using VASP and the plugin.

Relaxation of the structure

Here we will relax the structure, but first, we will perform an initial run without relaxing the structure so that you get familiar with the system and also are able to make the quick changes to enable relaxation from the calling script.

Initial static run

  1. First assemble a script to launch a VASP calculation using the wurtzite-type SiC structure . The scripts to launch certain calculations can be designed in many different way. Let us fetch an example AiiDA-VASP run file:

    $ wget https://github.com/aiida-vasp/aiida-vasp/raw/master/tutorials/run_sic.py
    
  2. Inspect the file, which has the following content:

    import numpy as np
    
    from aiida.common.extendeddicts import AttributeDict
    from aiida.engine import submit
    from aiida.manage.configuration import load_profile
    from aiida.orm import Bool, Code, Float, Int, Str
    from aiida.plugins import DataFactory, WorkflowFactory
    
    load_profile()
    
    
    def launch_aiida(structure, code_string, options, potential_family, potential_mapping, label='SiC VASP calculation'):
        Dict = DataFactory('dict')
        KpointsData = DataFactory('array.kpoints')
    
        incar_dict = {
            'PREC': 'Accurate',
            'IBRION': -1,
            'EDIFF': 1e-8,
            'NELMIN': 5,
            'NELM': 100,
            'ENCUT': 500,
            'IALGO': 38,
            'ISMEAR': 0,
            'SIGMA': 0.01,
            'LREAL': False,
            'LCHARG': False,
            'LWAVE': False,
        }
    
        kpoints = KpointsData()
        kpoints.set_kpoints_mesh([6, 6, 4], offset=[0, 0, 0.5])
    
        parser_settings = {'add_energies': True, 'add_forces': True, 'add_stress': True}
    
        code = Code.get_from_string(code_string)
        Workflow = WorkflowFactory('vasp.vasp')
        builder = Workflow.get_builder()
        builder.code = code
        builder.parameters = Dict(dict={'incar': incar_dict})
        builder.structure = structure
        builder.settings = Dict(dict={'parser_settings': parser_settings})
        builder.potential_family = Str(potential_family)
        builder.potential_mapping = Dict(dict=potential_mapping)
        builder.kpoints = kpoints
        builder.options = Dict(dict=options)
        builder.metadata.label = label
        builder.metadata.description = label
        builder.clean_workdir = Bool(False)
        node = submit(builder)
        return node
    
    
    def get_structure_SiC():
        """Set up SiC cell
    
        Si C
           1.0
             3.0920072935808083    0.0000000000000000    0.0000000000000000
            -1.5460036467904041    2.6777568649277486    0.0000000000000000
             0.0000000000000000    0.0000000000000000    5.0733470000000001
         Si C
           2   2
        Direct
           0.3333333333333333  0.6666666666666665  0.4995889999999998
           0.6666666666666667  0.3333333333333333  0.9995889999999998
           0.3333333333333333  0.6666666666666665  0.8754109999999998
           0.6666666666666667  0.3333333333333333  0.3754109999999997
    
        """
    
        StructureData = DataFactory('structure')
        a = 3.092
        c = 5.073
        lattice = [[a, 0, 0], [-a / 2, a / 2 * np.sqrt(3), 0], [0, 0, c]]
        structure = StructureData(cell=lattice)
        for pos_direct, symbol in zip(([1. / 3, 2. / 3, 0], [2. / 3, 1. / 3, 0.5], [1. / 3, 2. / 3, 0.375822
                                                                                    ], [2. / 3, 1. / 3, 0.875822]),
                                      ('Si', 'Si', 'C', 'C')):
            pos_cartesian = np.dot(pos_direct, lattice)
            structure.append_atom(position=pos_cartesian, symbols=symbol)
        return structure
    
    
    def main(code_string, options, potential_family, potential_mapping):
        structure = get_structure_SiC()
        launch_aiida(structure, code_string, options, potential_family, potential_mapping)
    
    
    if __name__ == '__main__':
        code_string = 'vasp@mycluster'
        options = {
            'resources': {
                'num_machines': 1,
                'num_mpiprocs_per_machine': 8
            },
            'account': '',
            'max_memory_kb': 2000000,
            'max_wallclock_seconds': 3600
        }
        potential_family = 'PBE.54'
        potential_mapping = {'Si': 'Si', 'C': 'C'}
        main(code_string, options, potential_family, potential_mapping)
    
  3. Change the relevant bits, e.g. the code_string, the options and the potential_family for your stored setup. Please consult previous tutorials for details on this.

  4. Run the modified script to launch the calculation:

    $ python run_sic.py
    
  5. Wait a bit and once the VaspWorkChain is finalized, get its PK using verdi process list -a, here 2663.

  6. Check the output nodes of 2663:

    $ verdi process show 2663
    Property     Value
    -----------  ------------------------------------
    type         VaspWorkChain
    state        Finished [0]
    pk           2663
    uuid         cc1f4f29-9c96-4e17-ba2f-3a140f789d49
    label        SiC VASP calculation
    description  SiC VASP calculation
    ctime        2023-03-22 10:47:17.841728+01:00
    mtime        2023-03-22 10:49:45.096366+01:00
    
    Inputs               PK  Type
    -----------------  ----  -------------
    clean_workdir      2660  Bool
    code                  3  InstalledCode
    kpoints            2658  KpointsData
    max_iterations     2661  Int
    options            2659  Dict
    parameters         2653  Dict
    potential_family   2656  Str
    potential_mapping  2657  Dict
    settings           2655  Dict
    structure          2654  StructureData
    verbose            2662  Bool
    
    Outputs          PK  Type
    -------------  ----  ----------
    energies       2668  ArrayData
    forces         2670  ArrayData
    misc           2669  Dict
    remote_folder  2666  RemoteData
    retrieved      2667  FolderData
    stress         2671  ArrayData
    
    Called          PK  Type
    ------------  ----  ---------------
    iteration_01  2665  VaspCalculation
    
    Log messages
    ---------------------------------------------
    There are 3 log messages for this calculation
    Run 'verdi process report 2663' to see them
    
  7. And inspect for instance the energies output node:

    $ verdi data array show 2668
    {
        "electronic_steps": [
            1
        ],
        "energy_extrapolated": [
            -30.09913368
        ],
        "energy_extrapolated_electronic": [
            -30.09913368
        ]
    }
    

    Do not take these values for granted and compare them to yours. They depend on the system you executed, potential used etc.

  8. We can also inspect it from Python. In order to make all the AiiDA machinery available in Python, we can use verdi shell. Start verdi shell and then load the node:

    $ verdi shell
    noPython 3.10.10 (main, Mar  5 2023, 22:26:53) [GCC 12.2.1 20230201]
    Type 'copyright', 'credits' or 'license' for more information
    IPython 7.34.0 -- An enhanced Interactive Python. Type '?' for help.
    
    In [1]: n = load_node(2663)
    
    In [2]: n.outputs.energies.get_array('energy_extrapolated')
    Out[2]: array([-30.09913368])
    
    In [3]: n.outputs.stress.get_array('final')
    Out[3]:
    array([[-0.01000677,  0.        ,  0.        ],
           [-0.        , -0.01000677,  0.        ],
           [ 0.        ,  0.        ,  0.34873446]])
    
  9. Exit verdi shell by typing exit.

Now that we have verified that the script works, let us extend it to enable relaxation of the structure.

Relaxation run

Let us now modify the script so that we perform a structure relaxation. If we want to fully relax the crystal structure, we need to modify the script accordingly.

  1. Open the run_sic.py launch script again.

  2. Replace WorkflowFactory('vasp.vasp') with WorkflowFactory('vasp.relax')

  3. Remove the IBRION entry from incar_dict

  4. Add add after builder.clean_workdir = Bool(False) the following:

    relax = AttributeDict()
    relax.perform = Bool(True)        # Turn on relaxation of the structure
    relax.force_cutoff = Float(1e-5)  # Relax force cutoff
    relax.steps = Int(10)             # Relax number of ionic steps
    relax.positions = Bool(True)      # Relax atomic positions
    relax.shape = Bool(True)          # Relax cell shape (alpha, beta, gamma)
    relax.volume = Bool(True)         # Relax volume
    builder.relax = relax
    builder.verbose = Bool(True)
    

    In the end, the necessary changes to run_sic.py can be summarize as:

    --- /home/docs/checkouts/readthedocs.org/user_builds/aiida-vasp/checkouts/master/tutorials/run_sic.py
    +++ /home/docs/checkouts/readthedocs.org/user_builds/aiida-vasp/checkouts/master/tutorials/run_sic_relax.py
    @@ -15,7 +15,6 @@
     
         incar_dict = {
             'PREC': 'Accurate',
    -        'IBRION': -1,
             'EDIFF': 1e-8,
             'NELMIN': 5,
             'NELM': 100,
    @@ -34,7 +33,7 @@
         parser_settings = {'add_energies': True, 'add_forces': True, 'add_stress': True}
     
         code = Code.get_from_string(code_string)
    -    Workflow = WorkflowFactory('vasp.vasp')
    +    Workflow = WorkflowFactory('vasp.relax')
         builder = Workflow.get_builder()
         builder.code = code
         builder.parameters = Dict(dict={'incar': incar_dict})
    @@ -46,6 +45,15 @@
         builder.options = Dict(dict=options)
         builder.metadata.label = label
         builder.metadata.description = label
    +    relax = AttributeDict()
    +    relax.perform = Bool(True)  # Turn on relaxation of the structure
    +    relax.force_cutoff = Float(1e-5)  # Relax force cutoff
    +    relax.steps = Int(10)  # Relax number of ionic steps cutoff
    +    relax.positions = Bool(True)  # Relax atomic positions
    +    relax.shape = Bool(True)  # Relax cell shape (alpha, beta, gamma)
    +    relax.volume = Bool(True)  # Relax volume
    +    builder.relax = relax
    +    builder.verbose = Bool(True)
         builder.clean_workdir = Bool(False)
         node = submit(builder)
         return node
    

    The plugin will then set the correct ISIF and EDIFFG etc. The point of using dedicated settings like this is two folded: (i) it makes more sense to the user, and (ii) it makes this workflow independent on VASP and can in principle be executed with any other backend, say Quantum Espresso as long as the conversion in the backend is done properly. This is the role of the ParametersMassage. The long term goal of the development of these plugins is that we will eventually have a more unified interface for the workflows that in principle can be code independent.

  5. Save the modified script and relaunch it.

  6. Locate the PK of the finalized RelaxWorkChain, in this case 2698 and launch verdi shell again. Then locate the relaxed structure and the stress:

    $ verdi shell
    Python 3.10.10 (main, Mar  5 2023, 22:26:53) [GCC 12.2.1 20230201]
    Type 'copyright', 'credits' or 'license' for more information
    IPython 7.34.0 -- An enhanced Interactive Python. Type '?' for help.
    
    In [1]: n = load_node(2698)
    
    In [2]: n.outputs.relax.structure.cell
    Out[2]:
    [[3.09208384, 0.0, 0.0],
     [-1.54604192, 2.67782316, 0.0],
     [0.0, 0.0, 5.07299923]]
    
    In [3]: n.outputs.stress.get_array('final')
    Out[3]:
    array([[-0.00109338,  0.        ,  0.        ],
           [ 0.        , -0.00109338,  0.        ],
           [ 0.        ,  0.        , -0.00031531]])
    

There are more options for the relax workchain, e.g., running VASP several time iteratively until convergence, which is used in the bulk modulus example in the next section.

After the relaxation, sometimes the crystal symmetry can be slightly broken by the VASP calculation, especially for hexagonal crystals. So it is recommended to symmetrize the final structure if this is the case, depending on what you want to use it for.