{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Fixed Composition Substitutions\n", "\n", "This guide demonstrates how to perform sequential substitutions and track the full configuration degeneracy across multiple substitution steps.\n", "\n", "## Sequential Substitutions\n", "\n", "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.\n", "\n", "### The Li-Na-Mg Example\n", "\n", "Let's start with the same 4×4 lithium lattice from the basic substitutions example, and perform two sequential substitutions." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Created structure with 16 Li sites\n" ] } ], "source": [ "import numpy as np\n", "from pymatgen.core import Structure, Lattice\n", "from bsym.interface.pymatgen import unique_structure_substitutions\n", "\n", "# Create a 4×4 square lattice\n", "coords = np.array([[0.0, 0.0, 0.0]])\n", "atom_list = ['Li']\n", "lattice = Lattice.from_parameters(a=1.0, b=1.0, c=1.0, alpha=90, beta=90, gamma=90)\n", "parent_structure = Structure(lattice, atom_list, coords) * [4, 4, 1]\n", "\n", "print(f\"Created structure with {len(parent_structure)} Li sites\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 1: Substitute Li → Na\n", "\n", "First, we substitute one lithium with sodium:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of unique structures after Na substitution: 1\n", "Degeneracy: 16\n" ] } ], "source": [ "unique_structures = unique_structure_substitutions(\n", " parent_structure,\n", " 'Li',\n", " {'Na': 1, 'Li': 15}\n", ")\n", "\n", "print(f\"Number of unique structures after Na substitution: {len(unique_structures)}\")\n", "na_substituted = unique_structures[0]\n", "print(f\"Degeneracy: {na_substituted.number_of_equivalent_configurations}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we saw before, due to the high symmetry of the square lattice, we get one unique structure with 16 equivalent configurations." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 2: Substitute Li → Mg\n", "\n", "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:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of unique structures: 5\n" ] } ], "source": [ "unique_structures_with_mg = unique_structure_substitutions(\n", " na_substituted,\n", " 'Li',\n", " {'Mg': 1, 'Li': 14}\n", ")\n", "\n", "print(f\"Number of unique structures: {len(unique_structures_with_mg)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now have five symmetry-inequivalent configurations, distinguished by the distance between the Na and Mg atoms:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can verify these correspond to the five distinct Na-Mg separation distances:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Squared distances between Na and Mg: [1. 2. 4. 5. 8.]\n" ] } ], "source": [ "distances_squared = np.array(sorted([\n", " s.get_distance(s.indices_from_symbol('Na')[0],\n", " s.indices_from_symbol('Mg')[0])**2\n", " for s in unique_structures_with_mg\n", "]))\n", "\n", "print(f\"Squared distances between Na and Mg: {distances_squared}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Understanding Configuration Degeneracy\n", "\n", "When performing sequential substitutions, there are two relevant degeneracy values:\n", "\n", "### `number_of_equivalent_configurations`\n", "\n", "This tracks the degeneracy from the **most recent substitution step only**:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Structure 0: 4 equivalent configurations (from Mg substitution)\n", "Structure 1: 2 equivalent configurations (from Mg substitution)\n", "Structure 2: 4 equivalent configurations (from Mg substitution)\n", "Structure 3: 4 equivalent configurations (from Mg substitution)\n", "Structure 4: 1 equivalent configurations (from Mg substitution)\n" ] } ], "source": [ "for i, structure in enumerate(unique_structures_with_mg):\n", " n_equiv = structure.number_of_equivalent_configurations\n", " print(f\"Structure {i}: {n_equiv} equivalent configurations (from Mg substitution)\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `full_configuration_degeneracy`\n", "\n", "This tracks the **total degeneracy** from all substitution steps, relative to the original parent structure:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Structure 0: 64 total configurations (from both substitutions)\n", "Structure 1: 32 total configurations (from both substitutions)\n", "Structure 2: 64 total configurations (from both substitutions)\n", "Structure 3: 64 total configurations (from both substitutions)\n", "Structure 4: 16 total configurations (from both substitutions)\n" ] } ], "source": [ "for i, structure in enumerate(unique_structures_with_mg):\n", " full_deg = structure.full_configuration_degeneracy\n", " print(f\"Structure {i}: {full_deg} total configurations (from both substitutions)\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `full_configuration_degeneracy` equals `number_of_equivalent_configurations` multiplied by the degeneracy from the previous step (16 for the Na substitution).\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Single-Step Multi-Species Substitution\n", "\n", "You can also perform both substitutions in a single step by specifying all species at once:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of unique structures: 5\n" ] } ], "source": [ "unique_structures_single_step = unique_structure_substitutions(\n", " parent_structure,\n", " 'Li',\n", " {'Mg': 1, 'Na': 1, 'Li': 14}\n", ")\n", "\n", "print(f\"Number of unique structures: {len(unique_structures_single_step)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We get the same five unique structures. However, the degeneracy values are different:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Single-step substitution degeneracies:\n", "Structure 0: number_of_equivalent = 64, full_degeneracy = 64\n", "Structure 1: number_of_equivalent = 32, full_degeneracy = 32\n", "Structure 2: number_of_equivalent = 64, full_degeneracy = 64\n", "Structure 3: number_of_equivalent = 64, full_degeneracy = 64\n", "Structure 4: number_of_equivalent = 16, full_degeneracy = 16\n", "\n", "They are equal because there was only one substitution step.\n" ] } ], "source": [ "print(\"Single-step substitution degeneracies:\")\n", "for i, structure in enumerate(unique_structures_single_step):\n", " n_equiv = structure.number_of_equivalent_configurations\n", " full_deg = structure.full_configuration_degeneracy\n", " print(f\"Structure {i}: number_of_equivalent = {n_equiv}, full_degeneracy = {full_deg}\")\n", " \n", "print(\"\\nThey are equal because there was only one substitution step.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Key Points\n", "\n", "- **Sequential substitutions** can break symmetry at each step, generating different numbers of unique structures\n", "- **`number_of_equivalent_configurations`** tracks degeneracy from the most recent substitution only\n", "- **`full_configuration_degeneracy`** tracks total degeneracy across all substitution steps\n", "- **Single-step multi-species substitution** is convenient when you don't need to track intermediate structures\n", "- For statistical mechanics calculations, use `full_configuration_degeneracy` to get correct configuration weights\n", "\n", "## Next Steps\n", "\n", "- For generating structures across different compositions, see [Varying Composition Substitutions](varying_composition.ipynb)\n", "- For performance tips with large systems, see [Performance and Progress Tracking](performance.ipynb)\n", "- To understand the theory behind configuration enumeration, see the [Theory section](../theory/unique_configurations.md)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.9" } }, "nbformat": 4, "nbformat_minor": 4 }