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)