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_configurations tracks degeneracy from the most recent substitution only

  • full_configuration_degeneracy tracks total degeneracy across all substitution steps

  • Single-step multi-species substitution is convenient when you don’t need to track intermediate structures

  • For statistical mechanics calculations, use full_configuration_degeneracy to get correct configuration weights

Next Steps