Coverage for icet/core/orbit_list.py: 84%
56 statements
« prev ^ index » next coverage.py v7.10.1, created at 2025-09-14 04:08 +0000
« prev ^ index » next coverage.py v7.10.1, created at 2025-09-14 04:08 +0000
1"""
2This module provides the OrbitList class.
3"""
5from collections import Counter
7import numpy as np
9from _icet import _OrbitList
10from icet.core.orbit import Orbit # noqa
11from ase import Atoms
12from icet.core.local_orbit_list_generator import LocalOrbitListGenerator
13from icet.core.neighbor_list import get_neighbor_lists
14from icet.core.matrix_of_equivalent_positions import \
15 _get_lattice_site_matrix_of_equivalent_positions, \
16 matrix_of_equivalent_positions_from_structure
17from icet.core.structure import Structure
18from icet.tools.geometry import (chemical_symbols_to_numbers,
19 atomic_number_to_chemical_symbol)
20from icet.input_output.logging_tools import logger
22logger = logger.getChild('orbit_list')
25class OrbitList(_OrbitList):
26 """
27 The orbit list object handles an internal list of orbits.
29 An orbit has a list of equivalent sites with the restriction
30 that at least one site is in the cell of the primitive structure.
32 Note
33 ----
34 As a user you will usually not interact directly with objects of this type.
36 Parameters
37 ----------
38 structure
39 This structure will be used to construct a primitive
40 structure on which all the lattice sites in the orbits
41 are based.
42 cutoffs
43 The `i`-th element of this list is the cutoff for orbits with
44 order `i+2`.
45 chemical_symbols
46 List of chemical symbols, each of which must map to an element
47 of the periodic table.
49 The outer list must be the same length as the `structure` object
50 and :attr:`chemical_symbols[i]` will correspond to the allowed species
51 on lattice site ``i``.
52 symprec
53 Tolerance imposed when analyzing the symmetry using spglib.
54 position_tolerance
55 Tolerance applied when comparing positions in Cartesian coordinates.
56 fractional_position_tolerance
57 Tolerance applied when comparing positions in fractional coordinates.
58 """
60 def __init__(self,
61 structure: Atoms,
62 cutoffs: list[float],
63 chemical_symbols: list[list[str]],
64 symprec: float,
65 position_tolerance: float,
66 fractional_position_tolerance: float) -> None:
67 max_cutoff = np.max(cutoffs)
68 # Set up a permutation matrix
69 matrix_of_equivalent_positions, prim_structure, _ \
70 = matrix_of_equivalent_positions_from_structure(structure=structure,
71 cutoff=max_cutoff,
72 position_tolerance=position_tolerance,
73 find_primitive=False,
74 symprec=symprec)
75 prim_structure.allowed_atomic_numbers = [chemical_symbols_to_numbers(syms)
76 for syms in chemical_symbols]
78 logger.info('Done getting matrix_of_equivalent_positions.')
80 # Get a list of neighbor lists
81 neighbor_lists = get_neighbor_lists(structure=prim_structure, cutoffs=cutoffs,
82 position_tolerance=position_tolerance)
84 logger.info('Done getting neighbor lists.')
86 # Transform matrix_of_equivalent_positions to be in lattice site format
87 pm_lattice_sites = _get_lattice_site_matrix_of_equivalent_positions(
88 structure=prim_structure,
89 matrix_of_equivalent_positions=matrix_of_equivalent_positions,
90 fractional_position_tolerance=fractional_position_tolerance,
91 prune=True)
93 logger.info('Transformation of matrix of equivalent positions'
94 ' to lattice neighbor format completed.')
96 _OrbitList.__init__(self,
97 structure=prim_structure,
98 matrix_of_equivalent_sites=pm_lattice_sites,
99 neighbor_lists=neighbor_lists,
100 position_tolerance=position_tolerance)
101 logger.info('Finished construction of orbit list.')
103 @property
104 def primitive_structure(self):
105 """
106 A copy of the primitive structure to which the lattice sites in
107 the orbits are referenced to.
108 """
109 return self.get_structure().to_atoms()
111 def __str__(self):
112 """ String representation. """
113 s = []
114 s += ['Number of orbits: {}'.format(len(self))]
115 for k, orbit in enumerate(self.orbits):
116 s += [f'Orbit {k}:']
117 s += [f'\t{s_tmp}' for s_tmp in orbit.__str__().split('\n')][:-1]
118 return '\n'.join(s)
120 def __getitem__(self, ind: int):
121 return self.get_orbit(ind)
123 def get_supercell_orbit_list(self,
124 structure: Atoms,
125 fractional_position_tolerance: float):
126 """
127 Returns the orbit list for a supercell structure.
129 Parameters
130 ----------
131 structure
132 Atomic structure.
133 fractional_position_tolerance
134 Tolerance applied when comparing positions in fractional coordinates.
135 """
136 lolg = LocalOrbitListGenerator(
137 self,
138 structure=Structure.from_atoms(structure),
139 fractional_position_tolerance=fractional_position_tolerance)
140 supercell_orbit_list = lolg.generate_full_orbit_list()
141 return supercell_orbit_list
143 def get_cluster_counts(self,
144 structure: Atoms,
145 fractional_position_tolerance: float,
146 orbit_indices: list[int] = None) -> dict[int, Counter]:
147 """
148 Counts all clusters in a structure by finding their local orbit list.
150 Parameters
151 ----------
152 structure
153 Structure for which to count clusters. This structure needs to
154 be commensurate with the structure this orbit list is based on.
155 fractional_position_tolerance
156 Tolerance applied when comparing positions in fractional coordinates.
157 orbit_indices
158 Indices of orbits, for which counts are requested; if ``None`` all
159 orbits will be counted.
161 Returns
162 -------
163 Dictionary, the keys of which are orbit indices and the values
164 cluster counts. The latter are themselves dicts, with tuples
165 of chemical symbols as keys and the number of such clusters
166 as values.
167 """
168 supercell_orbit_list = self.get_supercell_orbit_list(
169 structure=structure,
170 fractional_position_tolerance=fractional_position_tolerance)
172 # Collect counts for all orbit_indices
173 if orbit_indices is None:
174 orbit_indices = range(len(self))
175 structure_icet = Structure.from_atoms(structure)
176 cluster_counts_full = {}
177 for i in orbit_indices:
178 orbit = supercell_orbit_list.get_orbit(i)
179 counts = orbit.get_cluster_counts(structure_icet)
180 sorted_counts = Counter()
181 for atomic_numbers, count in counts.items():
182 symbols = atomic_number_to_chemical_symbol(atomic_numbers)
183 sorted_symbols = tuple(sorted(symbols))
184 sorted_counts[sorted_symbols] += count
185 cluster_counts_full[i] = sorted_counts
186 return cluster_counts_full