Coverage for icet/core/sublattices.py: 100%
81 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-12-26 04:12 +0000
« prev ^ index » next coverage.py v7.5.0, created at 2024-12-26 04:12 +0000
1from copy import deepcopy
2from icet.core.structure import Structure
3from typing import List, Iterator
4from ase import Atoms
5import copy
6from itertools import product
7from string import ascii_uppercase
8import numpy as np
9from icet.tools.geometry import chemical_symbols_to_numbers
12class Sublattice:
13 """
14 This class stores and provides information about a specific
15 sublattice. A sublattice is always supercell specific since
16 it contains lattice indices.
18 Note
19 ----
20 As a user you will usually not interact directly with objects of this type.
22 Parameters
23 ----------
24 chemical_symbols
25 The allowed species on this sublattice.
26 indices
27 The lattice indices the sublattice consists of.
28 symbol
29 String used to mark the sublattice.
30 """
32 def __init__(self,
33 chemical_symbols: List[str],
34 indices: List[int],
35 symbol: str):
36 self._chemical_symbols = chemical_symbols
37 self._indices = indices
38 self._symbol = symbol
39 self._numbers = chemical_symbols_to_numbers(chemical_symbols)
41 @property
42 def chemical_symbols(self):
43 return copy.deepcopy(self._chemical_symbols)
45 @property
46 def atomic_numbers(self):
47 return self._numbers.copy()
49 @property
50 def indices(self):
51 return self._indices.copy()
53 @property
54 def symbol(self):
55 """Symbol representation of sublattice, i.e. A, B, C, etc."""
56 return self._symbol
59class Sublattices:
60 """
61 This class stores and provides information about the sublattices
62 of a structure.
64 Note
65 ----
66 As a user you will usually not interact directly with objects of this type.
68 Parameters
69 ----------
70 allowed_species
71 List of the allowed species on each site of the primitve
72 structure. For example this can be the chemical_symbols from
73 a cluster space.
74 primitive_structure
75 The primitive structure the allowed species reference to.
76 structure
77 The structure that the sublattices will be based on.
78 fractional_position_tolerance
79 Tolerance applied when comparing positions in fractional coordinates.
80 """
82 def __init__(self,
83 allowed_species: List[List[str]],
84 primitive_structure: Atoms,
85 structure: Atoms,
86 fractional_position_tolerance: float):
87 self._structure = structure
88 # sorted unique sites, this basically decides A, B, C... sublattices
89 active_lattices = sorted(set([tuple(sorted(symbols))
90 for symbols in allowed_species if len(symbols) > 1]))
91 inactive_lattices = sorted(
92 set([tuple(sorted(symbols)) for symbols in allowed_species if len(symbols) == 1]))
93 self._allowed_species = active_lattices + inactive_lattices
95 n = int(np.sqrt(len(self._allowed_species))) + 1
96 symbols = [''.join(p) for r in range(1, n+1) for p in product(ascii_uppercase, repeat=r)]
98 cpp_prim_structure = Structure.from_atoms(primitive_structure)
99 self._sublattices = []
100 sublattice_to_indices = [[] for _ in range(len(self._allowed_species))]
101 for index, position in enumerate(structure.positions):
102 lattice_site = cpp_prim_structure.find_lattice_site_by_position(
103 position=position, fractional_position_tolerance=fractional_position_tolerance)
105 # Get allowed species on this site
106 species = allowed_species[lattice_site.index]
108 # Get what sublattice those species correspond to
109 sublattice = self._allowed_species.index(tuple(sorted(species)))
111 sublattice_to_indices[sublattice].append(index)
113 for symbol, species, indices in zip(symbols, self._allowed_species, sublattice_to_indices):
114 sublattice = Sublattice(chemical_symbols=species, indices=indices, symbol=symbol)
115 self._sublattices.append(sublattice)
117 # Map lattice index to sublattice index
118 self._index_to_sublattice = {}
119 for k, sublattice in enumerate(self):
120 for index in sublattice.indices:
121 self._index_to_sublattice[index] = k
123 def __getitem__(self, key: int) -> Sublattice:
124 """ Returns a sublattice according to key. """
125 return self._sublattices[key]
127 def __len__(self):
128 """ Returns number of sublattices. """
129 return len(self._sublattices)
131 def __iter__(self) -> Iterator[Sublattice]:
132 """ Generator over sublattices. """
133 yield from self._sublattices
135 def get_sublattice_index_from_site_index(self, index: int) -> int:
136 """ Returns the index of the sublattice the
137 index in the structure belongs to.
139 Parameters
140 ----------
141 index
142 Index of site in the structure.
143 """
144 return self._index_to_sublattice[index]
146 @property
147 def allowed_species(self) -> List[List[str]]:
148 """Lists of the allowed species on each sublattice in order. """
149 return deepcopy(self._allowed_species)
151 def get_sublattice_sites(self, index: int) -> List[int]:
152 """Returns the sites that belong to the sublattice with the
153 corresponding index.
155 Parameters
156 ----------
157 index
158 Index of the sublattice.
159 """
160 return self[index].indices
162 def get_allowed_symbols_on_site(self, index: int) -> List[str]:
163 """Returns the allowed symbols on the site.
165 Parameters
166 -----------
167 index
168 Lattice site index.
169 """
170 return self[self._index_to_sublattice[index]].chemical_symbols
172 def get_allowed_numbers_on_site(self, index: int) -> List[int]:
173 """Returns the allowed atomic numbers on the site.
175 Parameters
176 -----------
177 index
178 Lattice site index.
179 """
180 return self[self._index_to_sublattice[index]].atomic_numbers
182 @property
183 def active_sublattices(self) -> List[Sublattice]:
184 """ List of the active sublattices. """
185 return [sl for sl in self if len(sl.chemical_symbols) > 1]
187 @property
188 def inactive_sublattices(self) -> List[Sublattice]:
189 """ List of the active sublattices. """
190 return [sl for sl in self if len(sl.chemical_symbols) == 1]
192 def assert_occupation_is_allowed(self, chemical_symbols: List[str]):
193 """ Asserts that the current occupation obeys the sublattices. """
194 if len(chemical_symbols) != len(self._structure):
195 raise ValueError(f'Length of input chemical symbols ({len(chemical_symbols)}) does not'
196 f' match length of supercell ({len(self._structure)}')
197 for sl in self:
198 for i in sl.indices:
199 if not chemical_symbols[i] in sl.chemical_symbols:
200 msg = ('Occupations of structure not compatible with the sublattice.'
201 ' Site {} with occupation {} not allowed on sublattice {}'
202 .format(i, chemical_symbols[i], sl.chemical_symbols))
203 raise ValueError(msg)