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

1""" 

2This module provides the OrbitList class. 

3""" 

4 

5from collections import Counter 

6 

7import numpy as np 

8 

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 

21 

22logger = logger.getChild('orbit_list') 

23 

24 

25class OrbitList(_OrbitList): 

26 """ 

27 The orbit list object handles an internal list of orbits. 

28 

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. 

31 

32 Note 

33 ---- 

34 As a user you will usually not interact directly with objects of this type. 

35 

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. 

48 

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 """ 

59 

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] 

77 

78 logger.info('Done getting matrix_of_equivalent_positions.') 

79 

80 # Get a list of neighbor lists 

81 neighbor_lists = get_neighbor_lists(structure=prim_structure, cutoffs=cutoffs, 

82 position_tolerance=position_tolerance) 

83 

84 logger.info('Done getting neighbor lists.') 

85 

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) 

92 

93 logger.info('Transformation of matrix of equivalent positions' 

94 ' to lattice neighbor format completed.') 

95 

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.') 

102 

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() 

110 

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) 

119 

120 def __getitem__(self, ind: int): 

121 return self.get_orbit(ind) 

122 

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. 

128 

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 

142 

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. 

149 

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. 

160 

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) 

171 

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