Fixed Composition Substitutions
This guide demonstrates how to perform sequential substitutions and track the full configuration degeneracy across multiple substitution steps.
Sequential Substitutions
When performing multiple substitutions in sequence, each step generates new symmetry-inequivalent structures. Understanding how degeneracy accumulates across these steps is important for statistical mechanics calculations.
The Li-Na-Mg Example
Let’s start with the same 4×4 lithium lattice from the basic substitutions example, and perform two sequential substitutions.
import numpy as np
from pymatgen.core import Structure, Lattice
from bsym.interface.pymatgen import unique_structure_substitutions
# Create a 4×4 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]
print(f"Created structure with {len(parent_structure)} Li sites")
Created structure with 16 Li sites
/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
Step 1: Substitute Li → Na
First, we substitute one lithium with sodium:
unique_structures = unique_structure_substitutions(
parent_structure,
'Li',
{'Na': 1, 'Li': 15}
)
print(f"Number of unique structures after Na substitution: {len(unique_structures)}")
na_substituted = unique_structures[0]
print(f"Degeneracy: {na_substituted.number_of_equivalent_configurations}")
Number of unique structures after Na substitution: 1
Degeneracy: 16
As we saw before, due to the high symmetry of the square lattice, we get one unique structure with 16 equivalent configurations.
Step 2: Substitute Li → Mg
Now we take the Na-substituted structure and perform a second substitution, replacing one more Li with Mg. The Li→Na substitution has broken the symmetry of the parent lattice, so we now get multiple unique structures:
unique_structures_with_mg = unique_structure_substitutions(
na_substituted,
'Li',
{'Mg': 1, 'Li': 14}
)
print(f"Number of unique structures: {len(unique_structures_with_mg)}")
Number of unique structures: 5
We now have five symmetry-inequivalent configurations, distinguished by the distance between the Na and Mg atoms:
We can verify these correspond to the five distinct Na-Mg separation distances:
distances_squared = np.array(sorted([
s.get_distance(s.indices_from_symbol('Na')[0],
s.indices_from_symbol('Mg')[0])**2
for s in unique_structures_with_mg
]))
print(f"Squared distances between Na and Mg: {distances_squared}")
Squared distances between Na and Mg: [1. 2. 4. 5. 8.]
Understanding Configuration Degeneracy
When performing sequential substitutions, there are two relevant degeneracy values:
number_of_equivalent_configurations
This tracks the degeneracy from the most recent substitution step only:
for i, structure in enumerate(unique_structures_with_mg):
n_equiv = structure.number_of_equivalent_configurations
print(f"Structure {i}: {n_equiv} equivalent configurations (from Mg substitution)")
Structure 0: 4 equivalent configurations (from Mg substitution)
Structure 1: 2 equivalent configurations (from Mg substitution)
Structure 2: 4 equivalent configurations (from Mg substitution)
Structure 3: 4 equivalent configurations (from Mg substitution)
Structure 4: 1 equivalent configurations (from Mg substitution)
full_configuration_degeneracy
This tracks the total degeneracy from all substitution steps, relative to the original parent structure:
for i, structure in enumerate(unique_structures_with_mg):
full_deg = structure.full_configuration_degeneracy
print(f"Structure {i}: {full_deg} total configurations (from both substitutions)")
Structure 0: 64 total configurations (from both substitutions)
Structure 1: 32 total configurations (from both substitutions)
Structure 2: 64 total configurations (from both substitutions)
Structure 3: 64 total configurations (from both substitutions)
Structure 4: 16 total configurations (from both substitutions)
The full_configuration_degeneracy equals number_of_equivalent_configurations multiplied by the degeneracy from the previous step (16 for the Na substitution).
This is important for statistical mechanics: if you’re calculating configuration probabilities or partition functions, you need the full degeneracy relative to your starting structure.
Single-Step Multi-Species Substitution
You can also perform both substitutions in a single step by specifying all species at once:
unique_structures_single_step = unique_structure_substitutions(
parent_structure,
'Li',
{'Mg': 1, 'Na': 1, 'Li': 14}
)
print(f"Number of unique structures: {len(unique_structures_single_step)}")
Number of unique structures: 5
We get the same five unique structures. However, the degeneracy values are different:
print("Single-step substitution degeneracies:")
for i, structure in enumerate(unique_structures_single_step):
n_equiv = structure.number_of_equivalent_configurations
full_deg = structure.full_configuration_degeneracy
print(f"Structure {i}: number_of_equivalent = {n_equiv}, full_degeneracy = {full_deg}")
print("\nThey are equal because there was only one substitution step.")
Single-step substitution degeneracies:
Structure 0: number_of_equivalent = 64, full_degeneracy = 64
Structure 1: number_of_equivalent = 32, full_degeneracy = 32
Structure 2: number_of_equivalent = 64, full_degeneracy = 64
Structure 3: number_of_equivalent = 64, full_degeneracy = 64
Structure 4: number_of_equivalent = 16, full_degeneracy = 16
They are equal because there was only one substitution step.
When performing substitutions in a single step, number_of_equivalent_configurations and full_configuration_degeneracy contain the same values, since there’s only one substitution step to track.
Key Points
Sequential substitutions can break symmetry at each step, generating different numbers of unique structures
number_of_equivalent_configurationstracks degeneracy from the most recent substitution onlyfull_configuration_degeneracytracks total degeneracy across all substitution stepsSingle-step multi-species substitution is convenient when you don’t need to track intermediate structures
For statistical mechanics calculations, use
full_configuration_degeneracyto get correct configuration weights
Next Steps
For generating structures across different compositions, see Varying Composition Substitutions
For performance tips with large systems, see Performance and Progress Tracking
To understand the theory behind configuration enumeration, see the Theory section