Creating surfaces from bulk crystals

The SurfaceGeneration class has the purpose to otain surfaces from bulk crystals based on the Miller indices of the surface direction. Details on the implementation and an application is given in doi:10.1038/s41524-024-01224-7.

Starting point is a bulk crystal, here we take the cubic GaAs phase which is set upon initialization of the SurfaceGeneration object:

[1]:
from aim2dat.strct import Structure, SurfaceGeneration

strct_crystal = Structure(
    label="GaAs",
    elements=["Ga", "As"],
    positions=[
        [0.0, 0.0, 0.0],
        [0.75, 0.75, 0.75],
    ],
    cell=[
        [0.0, 4.066, 4.0660001],
        [4.066, 0.0, 4.066],
        [4.066, 4.066, 0.0],
    ],
    is_cartesian=False,
    pbc=[True, True, True],
)

surf_gen = SurfaceGeneration(strct_crystal)

For the cubic system there are three different low index directions: denoted by the (100), (110) and the (111) Miller indices. We can quickly create surface slabs by calling the function generate_surface_slabs which returns a StructureCollection object containing one surface for each termination:

[2]:
surfaces_100 = surf_gen.generate_surface_slabs(
    miller_indices=(1, 0, 0),
    nr_layers=5,
    periodic=False,
    vacuum=10.0,
    vacuum_factor=0.0,
    symmetrize=True,
    tolerance=0.01,
    symprec=0.005,
    angle_tolerance=-1.0,
    hall_number=0,
)

The following arguments of the function control the slab’s properties:

  • miller_indices: gives the surface direction. Since Miller indices are usually defined for the conventional unit cell (in this example we created the primitve unit cell) the class makes use of the spglib python package to transform the primitive into the conventional unit cell before using the ase python package to obtain the surface structures. The last 3 keyword arguments are therefore directly passed to the spglib function to determine the space group.

  • nr_layers: defines the slab size in the non-periodic direction normal to the surface plane in repetition units.

  • periodic: periodic boundary condition in the direction normal to the surface plane.

  • vacuum: amount of vacuum space added to separate the bottom and top surface facet.

  • vacuum_factor: overwrites the vacuum argument if larger than 0.0. It adds vacuum space as a multiple of the slab size.

  • symmetrize: whether to return a slab with two equivalent terminations on each side or an asymmetric slab which maintains the stoichiometry of the bulk crystal (the bottom and top termination may be unequivalent).

  • tolerance: numerical tolerance parameter to determine equivalent terminations.

  • symprec, angle_tolerance and hall_number are parameters passed to spglib to determine the conventional unit cell of the input crystal.

The algorithm found two different terminations for the (100) direction:

[3]:
print(surfaces_100)
----------------------------------------------------------------------
------------------------ Structure Collection ------------------------
----------------------------------------------------------------------

 - Number of structures: 2
 - Elements: As-Ga

                              Structures
  - GaAs_100_1          Ga22As20            [True  True  False]
  - GaAs_100_2          As22Ga20            [True  True  False]
----------------------------------------------------------------------

Surfaces as input to high-throughput workflows and AiiDA integration

In order to automatically converge the slab size in an efficient way it is useful to have all the building blocks to create different slabs with a certain termination. This information can be returned using the create_surface function, e.g. in this case for the first termination of the (100) direction:

[4]:
surf_details = surf_gen.create_surface(
    miller_indices=(1, 0, 0),
    termination=1,
    tolerance=0.01,
    symprec=0.005,
    angle_tolerance=-1.0,
    hall_number=0,
)
surf_details.keys()
[4]:
dict_keys(['repeating_structure', 'bottom_structure', 'top_structure', 'top_structure_nsym'])

The output of the function is a dictionary containing the different building blocks to construct a surface slab:

  • The key 'repeating_structure' contains the structure which is repeated and translated in the non-periodic direction (the number repititions defines the number of layers).

  • The key 'bottom_structure' obtains the structure of the bottom termination of the slab.

  • The keys 'top_structure' and 'top_structure_nsym' contain the terminations for a symmetric or a stoichiometric slab, respectively. In case the symmetric slab is already stoichiometric 'top_structure_nsym' is set to None.

The SurfaceData class can store exactly this information as an AiiDA data node and the calculation function create_surface_slab can be included in high-throughput workflows to create surface slabs on the fly.

The AiiDA SurfaceData node can also be created straight-away using the SurfaceGeneration class:

[5]:
from aiida import load_profile

load_profile("tests")

surf_node = surf_gen.to_aiida_surfacedata(miller_indices=(1, 0, 0))

And all data nodes for specific Miller indices can be stored in the database in a group using the store_surfaces_in_aiida_db function:

[6]:
surf_gen.store_surfaces_in_aiida_db(
    miller_indices=(1, 0, 0), group_label="GaAs_100_surfaces"
)
[6]:
[<SurfaceData: uuid: b0fff810-b8a2-4933-bd5f-c88a600781ea (pk: 218)>,
 <SurfaceData: uuid: 6d972ea7-e6a9-4acb-b83f-41b60c5bbfce (pk: 219)>]