Source code for icet.core.sublattices
from copy import deepcopy
from icet.core.structure import Structure
from typing import List, Iterator
from ase import Atoms
import copy
from itertools import product
from string import ascii_uppercase
import numpy as np
from icet.tools.geometry import chemical_symbols_to_numbers
[docs]
class Sublattice:
"""
This class stores and provides information about a specific
sublattice. A sublattice is always supercell specific since
it contains lattice indices.
Note
----
As a user you will usually not interact directly with objects of this type.
Parameters
----------
chemical_symbols
The allowed species on this sublattice.
indices
The lattice indices the sublattice consists of.
symbol
String used to mark the sublattice.
"""
def __init__(self,
chemical_symbols: List[str],
indices: List[int],
symbol: str):
self._chemical_symbols = chemical_symbols
self._indices = indices
self._symbol = symbol
self._numbers = chemical_symbols_to_numbers(chemical_symbols)
@property
def chemical_symbols(self):
return copy.deepcopy(self._chemical_symbols)
@property
def atomic_numbers(self):
return self._numbers.copy()
@property
def indices(self):
return self._indices.copy()
@property
def symbol(self):
"""Symbol representation of sublattice, i.e. A, B, C, etc."""
return self._symbol
[docs]
class Sublattices:
"""
This class stores and provides information about the sublattices
of a structure.
Note
----
As a user you will usually not interact directly with objects of this type.
Parameters
----------
allowed_species
List of the allowed species on each site of the primitve
structure. For example this can be the chemical_symbols from
a cluster space.
primitive_structure
The primitive structure the allowed species reference to.
structure
The structure that the sublattices will be based on.
fractional_position_tolerance
Tolerance applied when comparing positions in fractional coordinates.
"""
def __init__(self,
allowed_species: List[List[str]],
primitive_structure: Atoms,
structure: Atoms,
fractional_position_tolerance: float):
self._structure = structure
# sorted unique sites, this basically decides A, B, C... sublattices
active_lattices = sorted(set([tuple(sorted(symbols))
for symbols in allowed_species if len(symbols) > 1]))
inactive_lattices = sorted(
set([tuple(sorted(symbols)) for symbols in allowed_species if len(symbols) == 1]))
self._allowed_species = active_lattices + inactive_lattices
n = int(np.sqrt(len(self._allowed_species))) + 1
symbols = [''.join(p) for r in range(1, n+1) for p in product(ascii_uppercase, repeat=r)]
cpp_prim_structure = Structure.from_atoms(primitive_structure)
self._sublattices = []
sublattice_to_indices = [[] for _ in range(len(self._allowed_species))]
for index, position in enumerate(structure.positions):
lattice_site = cpp_prim_structure.find_lattice_site_by_position(
position=position, fractional_position_tolerance=fractional_position_tolerance)
# Get allowed species on this site
species = allowed_species[lattice_site.index]
# Get what sublattice those species correspond to
sublattice = self._allowed_species.index(tuple(sorted(species)))
sublattice_to_indices[sublattice].append(index)
for symbol, species, indices in zip(symbols, self._allowed_species, sublattice_to_indices):
sublattice = Sublattice(chemical_symbols=species, indices=indices, symbol=symbol)
self._sublattices.append(sublattice)
# Map lattice index to sublattice index
self._index_to_sublattice = {}
for k, sublattice in enumerate(self):
for index in sublattice.indices:
self._index_to_sublattice[index] = k
def __getitem__(self, key: int) -> Sublattice:
""" Returns a sublattice according to key. """
return self._sublattices[key]
def __len__(self):
""" Returns number of sublattices. """
return len(self._sublattices)
def __iter__(self) -> Iterator[Sublattice]:
""" Generator over sublattices. """
yield from self._sublattices
[docs]
def get_sublattice_index_from_site_index(self, index: int) -> int:
""" Returns the index of the sublattice the
index in the structure belongs to.
Parameters
----------
index
Index of site in the structure.
"""
return self._index_to_sublattice[index]
@property
def allowed_species(self) -> List[List[str]]:
"""Lists of the allowed species on each sublattice in order. """
return deepcopy(self._allowed_species)
[docs]
def get_sublattice_sites(self, index: int) -> List[int]:
"""Returns the sites that belong to the sublattice with the
corresponding index.
Parameters
----------
index
Index of the sublattice.
"""
return self[index].indices
[docs]
def get_allowed_symbols_on_site(self, index: int) -> List[str]:
"""Returns the allowed symbols on the site.
Parameters
-----------
index
Lattice site index.
"""
return self[self._index_to_sublattice[index]].chemical_symbols
[docs]
def get_allowed_numbers_on_site(self, index: int) -> List[int]:
"""Returns the allowed atomic numbers on the site.
Parameters
-----------
index
Lattice site index.
"""
return self[self._index_to_sublattice[index]].atomic_numbers
@property
def active_sublattices(self) -> List[Sublattice]:
""" List of the active sublattices. """
return [sl for sl in self if len(sl.chemical_symbols) > 1]
@property
def inactive_sublattices(self) -> List[Sublattice]:
""" List of the active sublattices. """
return [sl for sl in self if len(sl.chemical_symbols) == 1]
[docs]
def assert_occupation_is_allowed(self, chemical_symbols: List[str]):
""" Asserts that the current occupation obeys the sublattices. """
if len(chemical_symbols) != len(self._structure):
raise ValueError(f'Length of input chemical symbols ({len(chemical_symbols)}) does not'
f' match length of supercell ({len(self._structure)}')
for sl in self:
for i in sl.indices:
if not chemical_symbols[i] in sl.chemical_symbols:
msg = ('Occupations of structure not compatible with the sublattice.'
' Site {} with occupation {} not allowed on sublattice {}'
.format(i, chemical_symbols[i], sl.chemical_symbols))
raise ValueError(msg)