Basic Substitutions
This guide demonstrates how to use bsym to generate symmetry-inequivalent structures by substituting atoms in a parent structure.
Overview
The bsym.interface.pymatgen.unique_structure_substitutions function takes a parent structure and generates all symmetry-unique configurations when substituting specific sites with different species.
Key parameters:
structure: The parent pymatgen Structureto_substitute: The atomic species label to be replacedsite_distribution: Dictionary specifying how many of each substituting species (e.g.,{'Na': 1, 'Li': 15})
Returns: A list of Structure objects, each representing a symmetry-inequivalent configuration.
Simple Example: Single Substitution
Let’s start with a simple 4×4 square lattice of lithium atoms and substitute one Li with Na.
Setting Up the Parent Structure
import numpy as np
from pymatgen.core import Structure, Lattice
from bsym.interface.pymatgen import unique_structure_substitutions
# Create a simple square lattice
coords = np.array([[0.0, 0.0, 0.0]])
atom_list = ['Li']
lattice = Lattice.from_parameters(a=1.0, b=1.0, c=1.0, alpha=90, beta=90, gamma=90)
parent_structure = Structure(lattice, atom_list, coords) * [4, 4, 1]
/home/docs/checkouts/readthedocs.org/user_builds/bsym/envs/stable/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
from .autonotebook import tqdm as notebook_tqdm
This creates a 4×4 supercell with 16 lithium atoms.
print(parent_structure)
Full Formula (Li16)
Reduced Formula: Li
abc : 4.000000 4.000000 1.000000
angles: 90.000000 90.000000 90.000000
pbc : True True True
Sites (16)
# SP a b c
--- ---- ---- ---- ---
0 Li 0 0 0
1 Li 0 0.25 0
2 Li 0 0.5 0
3 Li 0 0.75 0
4 Li 0.25 0 0
5 Li 0.25 0.25 0
6 Li 0.25 0.5 0
7 Li 0.25 0.75 0
8 Li 0.5 0 0
9 Li 0.5 0.25 0
10 Li 0.5 0.5 0
11 Li 0.5 0.75 0
12 Li 0.75 0 0
13 Li 0.75 0.25 0
14 Li 0.75 0.5 1
15 Li 0.75 0.75 0
Performing the Substitution
Now we substitute one Li atom with Na:
unique_structures = unique_structure_substitutions(
parent_structure,
'Li',
{'Na': 1, 'Li': 15}
)
print(f"Number of unique structures: {len(unique_structures)}")
Number of unique structures: 1
Due to the high symmetry of the square lattice, all single-site substitutions are symmetry-equivalent, so we get only one unique structure.
print(unique_structures[0])
Full Formula (Na1 Li15)
Reduced Formula: NaLi15
abc : 4.000000 4.000000 1.000000
angles: 90.000000 90.000000 90.000000
pbc : True True True
Sites (16)
# SP a b c
--- ---- ---- ---- ---
0 Na 0 0 0
1 Li 0 0.25 0
2 Li 0 0.5 0
3 Li 0 0.75 0
4 Li 0.25 0 0
5 Li 0.25 0.25 0
6 Li 0.25 0.5 0
7 Li 0.25 0.75 0
8 Li 0.5 0 0
9 Li 0.5 0.25 0
10 Li 0.5 0.5 0
11 Li 0.5 0.75 0
12 Li 0.75 0 0
13 Li 0.75 0.25 0
14 Li 0.75 0.5 1
15 Li 0.75 0.75 0
Understanding the Output
Each structure in the returned list has a special attribute that tracks degeneracy:
na_substituted = unique_structures[0]
print(f"Number of equivalent configurations: {na_substituted.number_of_equivalent_configurations}")
Number of equivalent configurations: 16
The number_of_equivalent_configurations attribute tells us that this unique configuration has 16 symmetry-equivalent variants (one for substituting at each of the 16 sites).
More Complex Example: TiOF2 in ReO₃ Structure
Let’s look at a more realistic example: finding unique O/F orderings in TiOF2 with a pseudo-ReO3 structure.
Setting Up the Structure
a = 3.798 # lattice parameter in Ångströms
coords = np.array([[0.0, 0.0, 0.0],
[0.5, 0.0, 0.0],
[0.0, 0.5, 0.0],
[0.0, 0.0, 0.5]])
atom_list = ['Ti', 'X', 'X', 'X']
lattice = Lattice.from_parameters(a=a, b=a, c=a, alpha=90, beta=90, gamma=90)
unit_cell = Structure(lattice, atom_list, coords)
# Create a 2×2×2 supercell
parent_structure = unit_cell * [2, 2, 2]
print(f"Created supercell with {len(parent_structure)} atoms")
print(f"Number of X sites to substitute: {len([s for s in parent_structure if s.species_string == 'X'])}")
Created supercell with 32 atoms
Number of X sites to substitute: 0
The ‘X’ atoms represent the anion sites that we’ll substitute with O and F.
Finding Unique O/F Orderings
A 2 × 2 × 2 supercell has 24 anion sites. To get the right stoichiometry for TiOF2, we need 8 oxygen atoms and 16 fluorine atoms:
unique_structures = unique_structure_substitutions(
parent_structure,
'X',
{'O': 8, 'F': 16}
)
print(f"Number of symmetry-inequivalent configurations: {len(unique_structures)}")
Number of symmetry-inequivalent configurations: 2664
Each structure in unique_structures represents a distinct arrangement of O and F atoms that cannot be transformed into each other by the symmetry operations of the parent structure.
Checking the Degeneracy
You can check how many equivalent configurations each unique structure represents:
for i, structure in enumerate(unique_structures[:5]): # check the first 5 structures
n_equiv = structure.number_of_equivalent_configurations
print(f"Structure {i}: {n_equiv} equivalent configurations")
total_configs = sum(s.number_of_equivalent_configurations for s in unique_structures)
print(f"\nTotal configurations (including equivalent): {total_configs}")
Structure 0: 3 equivalent configurations
Structure 1: 192 equivalent configurations
Structure 2: 192 equivalent configurations
Structure 3: 96 equivalent configurations
Structure 4: 96 equivalent configurations
Total configurations (including equivalent): 735471
The total number of unique structures multiplied by their degeneracies should equal the total number of ways to choose the O/F distribution without considering symmetry.
Key Points
unique_structure_substitutions()automatically detects the symmetry operations of your parent structureThe function only returns symmetry-inequivalent structures, dramatically reducing the number of configurations you need to consider
The
number_of_equivalent_configurationsattribute tracks degeneracy for statistical mechanics calculationsAll sites labelled with
to_substituteare considered as potential substitution sites
Next Steps
For sequential substitutions where you want to track the full degeneracy chain, see Fixed Composition Substitutions
For generating structures across different compositions, see Varying Composition Substitutions