{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Basic Substitutions\n", "\n", "This guide demonstrates how to use `bsym` to generate symmetry-inequivalent structures by substituting atoms in a parent structure.\n", "\n", "## Overview\n", "\n", "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.\n", "\n", "**Key parameters:**\n", "- `structure`: The parent pymatgen Structure\n", "- `to_substitute`: The atomic species label to be replaced\n", "- `site_distribution`: Dictionary specifying how many of each substituting species (e.g., `{'Na': 1, 'Li': 15}`)\n", "\n", "**Returns:** A list of Structure objects, each representing a symmetry-inequivalent configuration." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Simple Example: Single Substitution\n", "\n", "Let's start with a simple 4×4 square lattice of lithium atoms and substitute one Li with Na.\n", "\n", "### Setting Up the Parent Structure" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "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 simple 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]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This creates a 4×4 supercell with 16 lithium atoms." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Full Formula (Li16)\n", "Reduced Formula: Li\n", "abc : 4.000000 4.000000 1.000000\n", "angles: 90.000000 90.000000 90.000000\n", "pbc : True True True\n", "Sites (16)\n", " # SP a b c\n", "--- ---- ---- ---- ---\n", " 0 Li 0 0 0\n", " 1 Li 0 0.25 0\n", " 2 Li 0 0.5 0\n", " 3 Li 0 0.75 0\n", " 4 Li 0.25 0 0\n", " 5 Li 0.25 0.25 0\n", " 6 Li 0.25 0.5 0\n", " 7 Li 0.25 0.75 0\n", " 8 Li 0.5 0 0\n", " 9 Li 0.5 0.25 1\n", " 10 Li 0.5 0.5 0\n", " 11 Li 0.5 0.75 0\n", " 12 Li 0.75 0 0\n", " 13 Li 0.75 0.25 0\n", " 14 Li 0.75 0.5 1\n", " 15 Li 0.75 0.75 0\n" ] } ], "source": [ "print(parent_structure)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Performing the Substitution\n", "\n", "Now we substitute one Li atom with Na:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of unique structures: 1\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: {len(unique_structures)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Due to the high symmetry of the square lattice, all single-site substitutions are symmetry-equivalent, so we get only **one unique structure**.\n", "\n", "
\n", " \n", "
" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Full Formula (Na1 Li15)\n", "Reduced Formula: NaLi15\n", "abc : 4.000000 4.000000 1.000000\n", "angles: 90.000000 90.000000 90.000000\n", "pbc : True True True\n", "Sites (16)\n", " # SP a b c\n", "--- ---- ---- ---- ---\n", " 0 Na 0 0 0\n", " 1 Li 0 0.25 0\n", " 2 Li 0 0.5 0\n", " 3 Li 0 0.75 0\n", " 4 Li 0.25 0 0\n", " 5 Li 0.25 0.25 0\n", " 6 Li 0.25 0.5 0\n", " 7 Li 0.25 0.75 0\n", " 8 Li 0.5 0 0\n", " 9 Li 0.5 0.25 1\n", " 10 Li 0.5 0.5 0\n", " 11 Li 0.5 0.75 0\n", " 12 Li 0.75 0 0\n", " 13 Li 0.75 0.25 0\n", " 14 Li 0.75 0.5 1\n", " 15 Li 0.75 0.75 0\n" ] } ], "source": [ "print(unique_structures[0])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Understanding the Output\n", "\n", "Each structure in the returned list has a special attribute that tracks degeneracy:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of equivalent configurations: 16\n" ] } ], "source": [ "na_substituted = unique_structures[0]\n", "print(f\"Number of equivalent configurations: {na_substituted.number_of_equivalent_configurations}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## More Complex Example: TiOF2 in ReO₃ Structure\n", "\n", "Let's look at a more realistic example: finding unique O/F orderings in TiOF2 with a pseudo-ReO3 structure.\n", "\n", "### Setting Up the Structure" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Created supercell with 32 atoms\n", "Number of X sites to substitute: 0\n" ] } ], "source": [ "a = 3.798 # lattice parameter in Ångströms\n", "\n", "coords = np.array([[0.0, 0.0, 0.0],\n", " [0.5, 0.0, 0.0],\n", " [0.0, 0.5, 0.0],\n", " [0.0, 0.0, 0.5]])\n", "atom_list = ['Ti', 'X', 'X', 'X']\n", "lattice = Lattice.from_parameters(a=a, b=a, c=a, alpha=90, beta=90, gamma=90)\n", "unit_cell = Structure(lattice, atom_list, coords)\n", "\n", "# Create a 2×2×2 supercell\n", "parent_structure = unit_cell * [2, 2, 2]\n", "print(f\"Created supercell with {len(parent_structure)} atoms\")\n", "print(f\"Number of X sites to substitute: {len([s for s in parent_structure if s.species_string == 'X'])}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The 'X' atoms represent the anion sites that we'll substitute with O and F." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Finding Unique O/F Orderings\n", "\n", "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:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of symmetry-inequivalent configurations: 2664\n" ] } ], "source": [ "unique_structures = unique_structure_substitutions(\n", " parent_structure, \n", " 'X', \n", " {'O': 8, 'F': 16}\n", ")\n", "\n", "print(f\"Number of symmetry-inequivalent configurations: {len(unique_structures)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Checking the Degeneracy\n", "\n", "You can check how many equivalent configurations each unique structure represents:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Structure 0: 3 equivalent configurations\n", "Structure 1: 192 equivalent configurations\n", "Structure 2: 192 equivalent configurations\n", "Structure 3: 96 equivalent configurations\n", "Structure 4: 96 equivalent configurations\n", "\n", "Total configurations (including equivalent): 735471\n" ] } ], "source": [ "for i, structure in enumerate(unique_structures[:5]): # check the first 5 structures\n", " n_equiv = structure.number_of_equivalent_configurations\n", " print(f\"Structure {i}: {n_equiv} equivalent configurations\")\n", "\n", "total_configs = sum(s.number_of_equivalent_configurations for s in unique_structures)\n", "print(f\"\\nTotal configurations (including equivalent): {total_configs}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Key Points\n", "\n", "- `unique_structure_substitutions()` automatically detects the symmetry operations of your parent structure\n", "- The function only returns symmetry-inequivalent structures, dramatically reducing the number of configurations you need to consider\n", "- The `number_of_equivalent_configurations` attribute tracks degeneracy for statistical mechanics calculations\n", "- All sites labelled with `to_substitute` are considered as potential substitution sites\n", "\n", "## Next Steps\n", "\n", "- For sequential substitutions where you want to track the full degeneracy chain, see [Fixed Composition Substitutions](fixed_composition.ipynb)\n", "- For generating structures across different compositions, see [Varying Composition Substitutions](varying_composition.ipynb)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "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 }