Special quasirandom structures

Note

Looking for a simple way to generate special quasirandom structures? Feel free to try this SHARC web application, which uses icet under the hood.

Random alloys are often of special interest. This is true in particular for systems that form random solid solutions below the melting point. It is, however, not always easy to model such structures, because the system sizes that lend themselves to, for example, DFT calculations, are often too small to accomodate a structure that may be regarded as random; the periodicity imposed by boundary conditions introduces correlations that make the modeled structure deviate from the random alloy. This problem can sometimes be alleviated with the use of so-called special quasirandom structures (SQS) [ZunWeiFer90]. SQS cells are the best possible approximations to random alloys in the sense that their cluster vectors closely resemble the cluster vectors of truly random alloys. This tutorial demonstrates how SQS cells can be generated in icet using a simulated annealing approach.

There is no unique way to measure the similarity between the cluster vector of the SQS cell and the random alloy. The implementation in icet uses the measure proposed in [WalTiwJon13]. Specifically, the objective function \(Q\) is calculated as

\[Q = - \omega L + \sum_{\alpha} \left| \Gamma_{\alpha} - \Gamma^{\text{target}}_{\alpha} \right|.\]

Here, \(\Gamma_{\alpha}\) are components in the cluster vector and \(\Gamma^\text{target}_{\alpha}\) the corresponding target values. The factor \(\omega\) is the radius (in Ångström) of the largest pair cluster such that all clusters with the same or smaller radii have \(\Gamma_{\alpha} - \Gamma^\text{target}_{\alpha} = 0\). The parameter \(L\), by default 1.0, can be specified by the user.

The functionality for generating SQS cells is just a special case of a more general algorithm for generating a structure with a cluster vector similar to any target cluster vector. The below example demonstrates both applications.

Import modules

The generate_sqs and/or generate_target_structure functions need to be imported together with some additional functions from ASE and icet. It is advisable to turn on logging, since the SQS cell generation may otherwise run quietly for a few minutes.

from ase import Atom
from ase.build import bulk
from icet import ClusterSpace
from icet.tools.structure_generation import (generate_sqs,
                                             generate_sqs_from_supercells,
                                             generate_sqs_by_enumeration,
                                             generate_target_structure)

from icet.input_output.logging_tools import set_log_config
set_log_config(level='INFO')

Generate binary SQS cells

In the following example, a binary FCC SQS cell with 8 atoms will be generated. To this end, an icet.ClusterSpace and target concentrations need to be defined. The cutoffs in the cluster space are important, since they determine how many elements are to be included when cluster vectors are compared. It is usually sufficient to use cutoffs such that the length of the cluster vector is on the order of 10. Target concentrations are specified via a dictionary, which should contain all the involved elements and their fractions of the total number of atoms. Internally, the function carries out simulated annealing with Monte Carlo trial swaps and can be expected to run for a minute or so.

primitive_structure = bulk('Au')
cs = ClusterSpace(primitive_structure, [8.0, 4.0], ['Au', 'Pd'])
target_concentrations = {'Au': 0.5, 'Pd': 0.5}
sqs = generate_sqs(cluster_space=cs,
                   max_size=8,
                   target_concentrations=target_concentrations)
print('Cluster vector of generated structure:', cs.get_cluster_vector(sqs))

If for some reason a particular supercell is needed, there is another function generate_sqs_from_supercells that works similarly, but in which it is possible to explicitly provide the accepted supercell. The code will then look for the optimal SQS among the provided supercells.

supercells = [primitive_structure.repeat((1, 2, 4))]
sqs = generate_sqs_from_supercells(cluster_space=cs,
                                   supercells=supercells,
                                   n_steps=10000,
                                   target_concentrations=target_concentrations)
print('Cluster vector of generated structure:', cs.get_cluster_vector(sqs))

Generate SQS cells by enumeration

In the above simple case, in which the target structure size is very small, it is more efficient to generate the best SQS cell by exhaustive enumeration of all binary FCC structures having up to 8 atoms in the supercell:

sqs = generate_sqs_by_enumeration(cluster_space=cs,
                                  max_size=8,
                                  target_concentrations=target_concentrations)
print('Cluster vector of generated structure:', cs.get_cluster_vector(sqs))

Generation of SQS cells by enumeration is preferable over the Monte Carlo approach if the size of the system permits, because with enumeration there is no risk that the optimal SQS cell is missed.

Generate SQS cells for a system with sublattices

It is possible to generate SQS cells also for systems with sublattices. In the below example, an SQS cell is generated for a system with two sublattices; one FCC sublattice on which Au, Cu, and Pd are allowed, and another FCC sublattice on which H and vacancies (X) are allowed. Target concentrations are specified per sublattice. The sublattices are defined by the letters shown at the top of the printout of a ClusterSpace.

primitive_structure = bulk('Au', a=4.0)
primitive_structure.append(Atom('H', position=(2.0, 2.0, 2.0)))
cs = ClusterSpace(primitive_structure, [7.0], [['Au', 'Cu', 'Pd'], ['H', 'X']])
print(cs)

This should result in something similar to this:

====================================== Cluster Space =======================================
 space group                            : Fm-3m (225)
 chemical species                       : ['Au', 'Cu', 'Pd'] (sublattice A), ['H', 'X'] (sublattice B)
 cutoffs                                : 7.0000
 total number of parameters             : 40
 number of parameters by order          : 0= 1  1= 3  2= 36
 fractional_position_tolerance          : 2e-06
 position_tolerance                     : 1e-05
 symprec                                : 1e-05
--------------------------------------------------------------------------------------------
index | order |  radius  | multiplicity | orbit_index | multi_component_vector | sublattices
--------------------------------------------------------------------------------------------
   0  |   0   |   0.0000 |        1     |      -1     |           .            |      .
   1  |   1   |   0.0000 |        1     |       0     |          [0]           |      A
   2  |   1   |   0.0000 |        1     |       0     |          [1]           |      A
   3  |   1   |   0.0000 |        1     |       1     |          [0]           |      B
   4  |   2   |   1.0000 |        6     |       2     |         [0, 0]         |     A-B
...

Here we see that the sublattice with Au, Cu and Pd is sublattice A, while H and X are on sublattice B. These letters can now be used when the target concentrations are specified.

In the below example, an SQS cell is generated for a supercell that is 16 times larger than the primitive cell, in total 32 atoms. The keyword include_smaller_cells=False guarantees that the generated structure has 32 atoms (otherwise the structure search would have been carried out among structures having 32 atoms or less).

In this example, the number of trial steps is manually set to 50,000. This number may be insufficient, but will most likely provide a reasonable SQS cell, albeit perhaps not the best one. The default number of trial steps is 3,000 times the number of inequivalent supercell shapes. The latter quantity increases quickly with the size of the supercell.

target_concentrations = {'A': {'Au': 6 / 8, 'Cu': 1 / 8, 'Pd': 1 / 8},
                         'B': {'H': 1 / 4, 'X': 3 / 4}}
sqs = generate_sqs(cluster_space=cs,
                   max_size=16,
                   include_smaller_cells=False,
                   target_concentrations=target_concentrations,
                   n_steps=50000)
print('Cluster vector of generated structure:', cs.get_cluster_vector(sqs))

Generate a structure matching an arbitrary cluster vector

The SQS cell generation approach can be utilized to generate the structure that most closely resembles any cluster vector. To do so, one can employ the same procedure but the target cluster vector must be specified manually. Note that there are no restrictions on what target vectors can be specified (except their length, which must match the cluster space length), but the space of cluster vectors that can be realized by structures is restricted in multiple ways. The similarity between the target cluster vector and the cluster vector of the generated structure may thus appear poor.

primitive_structure = bulk('Au')
cs = ClusterSpace(primitive_structure, [5.0], ['Au', 'Pd'])
target_cluster_vector = [1.0, 0.0] + [0.5] * (len(cs) - 2)
target_concentrations = {'Au': 0.5, 'Pd': 0.5}
sqs = generate_target_structure(cluster_space=cs,
                                max_size=8,
                                target_cluster_vector=target_cluster_vector,
                                target_concentrations=target_concentrations)
print('Cluster vector of generated structure:', cs.get_cluster_vector(sqs))

Source code

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

"""
This example demonstrates how to generate special quasirandom structure.
"""

# Import modules
from ase import Atom
from ase.build import bulk
from icet import ClusterSpace
from icet.tools.structure_generation import (generate_sqs,
                                             generate_sqs_from_supercells,
                                             generate_sqs_by_enumeration,
                                             generate_target_structure)

from icet.input_output.logging_tools import set_log_config
set_log_config(level='INFO')

# Generate SQS for binary fcc, 50 % concentration
primitive_structure = bulk('Au')
cs = ClusterSpace(primitive_structure, [8.0, 4.0], ['Au', 'Pd'])
target_concentrations = {'Au': 0.5, 'Pd': 0.5}
sqs = generate_sqs(cluster_space=cs,
                   max_size=8,
                   target_concentrations=target_concentrations)
print('Cluster vector of generated structure:', cs.get_cluster_vector(sqs))

# Generate SQS for binary fcc with specified supercells
supercells = [primitive_structure.repeat((1, 2, 4))]
sqs = generate_sqs_from_supercells(cluster_space=cs,
                                   supercells=supercells,
                                   n_steps=10000,
                                   target_concentrations=target_concentrations)
print('Cluster vector of generated structure:', cs.get_cluster_vector(sqs))

# Use enumeration to generate SQS for binary fcc, 50 % concentration
sqs = generate_sqs_by_enumeration(cluster_space=cs,
                                  max_size=8,
                                  target_concentrations=target_concentrations)
print('Cluster vector of generated structure:', cs.get_cluster_vector(sqs))

# Generate SQS for a system with two sublattices
primitive_structure = bulk('Au', a=4.0)
primitive_structure.append(Atom('H', position=(2.0, 2.0, 2.0)))
cs = ClusterSpace(primitive_structure, [7.0], [['Au', 'Cu', 'Pd'], ['H', 'X']])
print(cs)
# Target concentrations are specified per sublattice
target_concentrations = {'A': {'Au': 6 / 8, 'Cu': 1 / 8, 'Pd': 1 / 8},
                         'B': {'H': 1 / 4, 'X': 3 / 4}}
sqs = generate_sqs(cluster_space=cs,
                   max_size=16,
                   include_smaller_cells=False,
                   target_concentrations=target_concentrations,
                   n_steps=50000)
print('Cluster vector of generated structure:', cs.get_cluster_vector(sqs))

# Generate structure with a specified cluster vector
primitive_structure = bulk('Au')
cs = ClusterSpace(primitive_structure, [5.0], ['Au', 'Pd'])
target_cluster_vector = [1.0, 0.0] + [0.5] * (len(cs) - 2)
target_concentrations = {'Au': 0.5, 'Pd': 0.5}
sqs = generate_target_structure(cluster_space=cs,
                                max_size=8,
                                target_cluster_vector=target_cluster_vector,
                                target_concentrations=target_concentrations)
print('Cluster vector of generated structure:', cs.get_cluster_vector(sqs))