# Special quasirandom structures¶

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

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_by_enumeration,
generate_target_structure)
from icet.io.logging 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.

```
atoms = bulk('Au')
cs = ClusterSpace(atoms, [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))
```

In this 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 (V) are allowed. Note that
target concentrations are specified with respect to *all* atoms, which means
that the concentrations must always sum up to 1. The example generates an SQS
cell 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.

```
atoms = bulk('Au', a=4.0)
atoms.append(Atom('H', position=(2.0, 2.0, 2.0)))
cs = ClusterSpace(atoms, [7.0], [['Au', 'Cu', 'Pd'], ['H', 'V']])
target_concentrations = {'Au': 6 / 16, 'Cu': 1 / 16, 'Pd': 1 / 16,
'H': 2 / 16, 'V': 6 / 16}
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.

```
atoms = bulk('Au')
cs = ClusterSpace(atoms, [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¶

`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_by_enumeration,
generate_target_structure)
from icet.io.logging import set_log_config
set_log_config(level='INFO')
# Generate SQS for binary fcc, 50 % concentration
atoms = bulk('Au')
cs = ClusterSpace(atoms, [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))
# 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,
# fcc lattices with Au, Cu, Pd on one lattice and H, V on another
atoms = bulk('Au', a=4.0)
atoms.append(Atom('H', position=(2.0, 2.0, 2.0)))
cs = ClusterSpace(atoms, [7.0], [['Au', 'Cu', 'Pd'], ['H', 'V']])
target_concentrations = {'Au': 6 / 16, 'Cu': 1 / 16, 'Pd': 1 / 16,
'H': 2 / 16, 'V': 6 / 16}
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
atoms = bulk('Au')
cs = ClusterSpace(atoms, [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))
```