Source code for smact.distorter
"""
smact.distorter: Module for generating symmetry-unique substitutions on a given sub-lattice.
As input it takes the ASE crystal object (as built by smact.builder)
and the sub-lattice on which substitutions are to be made.
"""
from __future__ import annotations
import copy
from typing import TYPE_CHECKING, cast
import smact
if TYPE_CHECKING:
from ase import Atoms
from ase.atom import Atom
import spglib
from ase.spacegroup import Spacegroup
[docs]
def get_sg(lattice: Atoms) -> Spacegroup:
"""
Get the space-group of the system.
Args:
----
lattice: the ASE crystal class
Returns:
sg (Spacegroup): ASE Spacegroup object
"""
cell = (
lattice.get_cell().tolist(), # type: ignore[union-attr] # ASE Cell stubs lack tolist
lattice.get_scaled_positions().tolist(),
lattice.get_atomic_numbers().tolist(),
)
spacegroup: str | None = spglib.get_spacegroup(cell, symprec=1e-5) # type: ignore[attr-defined] # spglib stubs incomplete
if spacegroup is None:
msg = "spglib could not determine the spacegroup of the lattice"
raise ValueError(msg)
space_split = spacegroup.split()
spg_num = space_split[1].replace("(", "").replace(")", "")
return Spacegroup(int(spg_num))
[docs]
def get_inequivalent_sites(
sub_lattice: list[list[float]],
lattice: Atoms,
) -> list[list[float]]:
"""
Given a sub lattice, returns symmetry unique sites for substitutions.
Args:
----
sub_lattice (list of lists): array containing fractional coordinates
of the sub-lattice of interest
lattice (ASE crystal): the total lattice
Returns:
-------
List of sites
"""
sg = get_sg(lattice)
inequivalent_sites = []
for site in sub_lattice:
new_site = True
# Check against the existing members of the list of inequivalent sites
if len(inequivalent_sites) > 0:
for inequiv_site in inequivalent_sites:
if smact.are_eq(site, inequiv_site):
new_site = False
# Check against symmetry related members of the list of inequivalent sites
equiv_inequiv_sites, _ = sg.equivalent_sites(inequiv_site)
for equiv_inequiv_site in equiv_inequiv_sites:
if smact.are_eq(site, equiv_inequiv_site):
new_site = False
if new_site:
inequivalent_sites.append(site)
return inequivalent_sites
[docs]
def make_substitution(lattice: Atoms, site: list[float], new_species: str) -> Atoms:
"""
Change atomic species on lattice site to new_species.
Args:
----
lattice (ASE crystal): Input lattice
site (list): Fractional coordinates of the substitution site
new_species (str): New species
Returns:
-------
lattice
"""
# deepcopy is necessary, otherwise changes applied to the clone also apply to the parent object.
new_lattice = copy.deepcopy(lattice)
lattice_sites = new_lattice.get_scaled_positions()
found = False
for i, lattice_site in enumerate(lattice_sites):
if smact.are_eq(lattice_site, site):
atom = cast("Atom", new_lattice[i])
atom.symbol = new_species
found = True
if not found:
import warnings
warnings.warn(
f"Site {site} not found in lattice; returning unmodified copy.",
stacklevel=2,
)
return new_lattice
[docs]
def build_sub_lattice(lattice: Atoms, symbol: str) -> list[list[float]]:
"""
Generate a sub-lattice of the lattice based on equivalent atomic species.
Args:
----
lattice (ASE crystal class): Input lattice
symbol (string): Symbol of species identifying sub-lattice
Returns:
-------
list of lists:
sub_lattice: Fractional coordinates of the sub-lattice of symbol
"""
sub_lattice = []
atomic_labels = lattice.get_chemical_symbols()
positions = lattice.get_scaled_positions()
for atom, pos in zip(atomic_labels, positions, strict=True):
if atom == symbol:
sub_lattice.append(pos)
return sub_lattice