Mapping structures

A cluster vector calculation requires all atoms to reside on a fixed lattice. Properties of interest, on the other hand, are typically calculated for a structure in which cell metric and atoms have been allowed to relax. Unless the ideal structures have been saved prior to relaxation, one is therefore faced with the task of mapping back the relaxed structure onto the ideal one. In some cases, in particular involving vacancies, relaxation can also lead to atoms moving between sites, in which case remapping is mandatory.

This is the purpose of the function map_structure_to_reference(). The function is also useful to analyze whether the relaxation has gone too far for the cluster expansion to be viable, i.e., whether the ideal structure from which the relaxation started is not a valid representation of the structure for which the property has been obtained.

Import modules

The map_structure_to_reference() function needs to be imported together with some additional functionality from ASE.

from icet.tools import map_structure_to_reference
from ase.build import bulk
from ase import Atom

Prepare dummy structures

First, for the sake of demonstration, a reference structure defining the ideal lattice is created, and a supercell thereof is scaled and rattled to simulate relaxation in an energy minimixation.

reference = bulk('Au', a=4.00)

supercell = reference.repeat(3)
supercell.rattle(0.1)
supercell.set_cell(1.05 * supercell.cell, scale_atoms=True)

# Switch some atoms to Pd
for i in [0, 1, 5, 8, 10]:
    supercell[i].symbol = 'Pd'

Map relaxed structure onto ideal structure

The structure can now be mapped onto a structure in which all atoms reside on ideal lattice sites. The function returns the ideal structure, as well as the maximum and average displacement.

ideal_structure, info = map_structure_to_reference(supercell, reference)
print('Maximum displacement: {:.3f} Angstrom'.format(info['drmax']))
print('Average displacement: {:.3f} Angstrom'.format(info['dravg']))

Structures with vacancies

For cluster expansions with vacancies, one typically wants to map the relaxed structure onto an ideal lattice that explicitly contains vacant sites. In that case, if the volume of the cell has changed during relaxation, it can be tricky to determine the size of the ideal supercell. To help the function with this task, an additional keyword, inert_species, can be specified, which is a list of species that reside on sublattices without vacancies.

In the example below, a Au-Pd-H-vacancy system is created. The system of choice consists of two sublattices, one occupied by Au and Pd and another occupied by H and vacancies. Since Au and Pd belong to a sublattice in which we do not allow vacancies, we may set inert_species = ['Au', 'Pd'].

reference = bulk('Au', a=4.00)
reference.append(Atom(('H'), (2.0, 2.0, 2.0)))

supercell = reference.repeat(3)
supercell.rattle(0.1)
supercell.set_cell(1.05 * supercell.cell, scale_atoms=True)

# Switch some Au to Pd and delete some H (to create vacancies)
for i in [0, 4, 6, 2, 7, 3, 17]:
    if supercell[i].symbol == 'Au':
        supercell[i].symbol = 'Pd'
    elif supercell[i].symbol == 'H':
        del supercell[i]

ideal_structure, info = map_structure_to_reference(supercell, reference,
                                                   inert_species=['Au', 'Pd'])
print('Maximum displacement: {:.3f} Angstrom'.format(info['drmax']))
print('Average displacement: {:.3f} Angstrom'.format(info['dravg']))

The mapped structure will contain atoms of type X, which represent vacancies.

If there is no sublattice without vacancies, one typically has to set the keyword argument assume_no_cell_relaxation to True. The algorithm will then use the cell metric of the relaxed structure as the ideal one.

Source code

The complete source code is available in examples/map_structure_to_reference.py

"""
This example demonstrates how to map a structure in which the cell has
been scaled and/or the atoms displaced onto an ideal (primitive) structure
"""

# Import modules
from icet.tools import map_structure_to_reference
from ase.build import bulk
from ase import Atom
# End import

# Begin by creating a reference structure, in this case fcc Au.
# Then create a supercell structure, scale the cell and displace the atoms to
# simulate a relaxed structure.
reference = bulk('Au', a=4.00)

supercell = reference.repeat(3)
supercell.rattle(0.1)
supercell.set_cell(1.05 * supercell.cell, scale_atoms=True)

# Switch some atoms to Pd
for i in [0, 1, 5, 8, 10]:
    supercell[i].symbol = 'Pd'

# Map the "relaxed" structure onto an ideal supercell
ideal_structure, info = map_structure_to_reference(supercell, reference)
print('Maximum displacement: {:.3f} Angstrom'.format(info['drmax']))
print('Average displacement: {:.3f} Angstrom'.format(info['dravg']))

# Map a structure that contains vacancies, in this case Pd-Au-H-Vac, in which
# Pd and Au share one sublattice and Pd and H another.
reference = bulk('Au', a=4.00)
reference.append(Atom(('H'), (2.0, 2.0, 2.0)))

supercell = reference.repeat(3)
supercell.rattle(0.1)
supercell.set_cell(1.05 * supercell.cell, scale_atoms=True)

# Switch some Au to Pd and delete some H (to create vacancies)
for i in [0, 4, 6, 2, 7, 3, 17]:
    if supercell[i].symbol == 'Au':
        supercell[i].symbol = 'Pd'
    elif supercell[i].symbol == 'H':
        del supercell[i]

ideal_structure, info = map_structure_to_reference(supercell, reference,
                                                   inert_species=['Au', 'Pd'])
print('Maximum displacement: {:.3f} Angstrom'.format(info['drmax']))
print('Average displacement: {:.3f} Angstrom'.format(info['dravg']))