# 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_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))
```