Coverage for icet/core/orbit_list.py: 83%

57 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-12-26 04:12 +0000

1""" 

2This module provides the OrbitList class. 

3""" 

4 

5from typing import List, Dict 

6from collections import Counter 

7 

8import numpy as np 

9 

10from _icet import _OrbitList 

11from icet.core.orbit import Orbit # noqa 

12from ase import Atoms 

13from icet.core.local_orbit_list_generator import LocalOrbitListGenerator 

14from icet.core.neighbor_list import get_neighbor_lists 

15from icet.core.matrix_of_equivalent_positions import \ 

16 _get_lattice_site_matrix_of_equivalent_positions, \ 

17 matrix_of_equivalent_positions_from_structure 

18from icet.core.structure import Structure 

19from icet.tools.geometry import (chemical_symbols_to_numbers, 

20 atomic_number_to_chemical_symbol) 

21from icet.input_output.logging_tools import logger 

22 

23logger = logger.getChild('orbit_list') 

24 

25 

26class OrbitList(_OrbitList): 

27 """ 

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

29 

30 An orbit has a list of equivalent sites with the restriction 

31 that at least one site is in the cell of the primitive structure. 

32 

33 Note 

34 ---- 

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

36 

37 Parameters 

38 ---------- 

39 structure 

40 This structure will be used to construct a primitive 

41 structure on which all the lattice sites in the orbits 

42 are based. 

43 cutoffs 

44 The `i`-th element of this list is the cutoff for orbits with 

45 order `i+2`. 

46 chemical_symbols 

47 List of chemical symbols, each of which must map to an element 

48 of the periodic table. 

49 

50 The outer list must be the same length as the `structure` object 

51 and :attr:`chemical_symbols[i]` will correspond to the allowed species 

52 on lattice site ``i``. 

53 symprec 

54 Tolerance imposed when analyzing the symmetry using spglib. 

55 position_tolerance 

56 Tolerance applied when comparing positions in Cartesian coordinates. 

57 fractional_position_tolerance 

58 Tolerance applied when comparing positions in fractional coordinates. 

59 """ 

60 

61 def __init__(self, 

62 structure: Atoms, 

63 cutoffs: List[float], 

64 chemical_symbols: List[List[str]], 

65 symprec: float, 

66 position_tolerance: float, 

67 fractional_position_tolerance: float) -> None: 

68 max_cutoff = np.max(cutoffs) 

69 # Set up a permutation matrix 

70 matrix_of_equivalent_positions, prim_structure, _ \ 

71 = matrix_of_equivalent_positions_from_structure(structure=structure, 

72 cutoff=max_cutoff, 

73 position_tolerance=position_tolerance, 

74 find_primitive=False, 

75 symprec=symprec) 

76 prim_structure.allowed_atomic_numbers = [chemical_symbols_to_numbers(syms) 

77 for syms in chemical_symbols] 

78 

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

80 

81 # Get a list of neighbor-lists 

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

83 position_tolerance=position_tolerance) 

84 

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

86 

87 # Transform matrix_of_equivalent_positions to be in lattice site format 

88 pm_lattice_sites = _get_lattice_site_matrix_of_equivalent_positions( 

89 structure=prim_structure, 

90 matrix_of_equivalent_positions=matrix_of_equivalent_positions, 

91 fractional_position_tolerance=fractional_position_tolerance, 

92 prune=True) 

93 

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

95 ' to lattice neighbor format completed.') 

96 

97 _OrbitList.__init__(self, 

98 structure=prim_structure, 

99 matrix_of_equivalent_sites=pm_lattice_sites, 

100 neighbor_lists=neighbor_lists, 

101 position_tolerance=position_tolerance) 

102 logger.info('Finished construction of orbit list.') 

103 

104 @property 

105 def primitive_structure(self): 

106 """ 

107 A copy of the primitive structure to which the lattice sites in 

108 the orbits are referenced to. 

109 """ 

110 return self.get_structure().to_atoms() 

111 

112 def __str__(self): 

113 """ String representation. """ 

114 s = [] 

115 s += ['Number of orbits: {}'.format(len(self))] 

116 for k, orbit in enumerate(self.orbits): 

117 s += [f'Orbit {k}:'] 

118 s += [f'\t{s_tmp}' for s_tmp in orbit.__str__().split('\n')][:-1] 

119 return '\n'.join(s) 

120 

121 def __getitem__(self, ind: int): 

122 return self.get_orbit(ind) 

123 

124 def get_supercell_orbit_list(self, 

125 structure: Atoms, 

126 fractional_position_tolerance: float): 

127 """ 

128 Returns the orbit list for a supercell structure. 

129 

130 Parameters 

131 ---------- 

132 structure 

133 Atomic structure. 

134 fractional_position_tolerance 

135 Tolerance applied when comparing positions in fractional coordinates. 

136 """ 

137 lolg = LocalOrbitListGenerator( 

138 self, 

139 structure=Structure.from_atoms(structure), 

140 fractional_position_tolerance=fractional_position_tolerance) 

141 supercell_orbit_list = lolg.generate_full_orbit_list() 

142 return supercell_orbit_list 

143 

144 def get_cluster_counts(self, 

145 structure: Atoms, 

146 fractional_position_tolerance: float, 

147 orbit_indices: List[int] = None) -> Dict[int, Counter]: 

148 """ 

149 Counts all clusters in a structure by finding their local orbit list. 

150 

151 Parameters 

152 ---------- 

153 structure 

154 Structure for which to count clusters. This structure needs to 

155 be commensurate with the structure this orbit list is based on. 

156 fractional_position_tolerance 

157 Tolerance applied when comparing positions in fractional coordinates. 

158 orbit_indices 

159 Indices of orbits, for which counts are requested; if ``None`` all 

160 orbits will be counted. 

161 

162 Returns 

163 ------- 

164 Dictionary, the keys of which are orbit indices and the values 

165 cluster counts. The latter are themselves dicts, with tuples 

166 of chemical symbols as keys and the number of such clusters 

167 as values. 

168 """ 

169 supercell_orbit_list = self.get_supercell_orbit_list( 

170 structure=structure, 

171 fractional_position_tolerance=fractional_position_tolerance) 

172 

173 # Collect counts for all orbit_indices 

174 if orbit_indices is None: 

175 orbit_indices = range(len(self)) 

176 structure_icet = Structure.from_atoms(structure) 

177 cluster_counts_full = {} 

178 for i in orbit_indices: 

179 orbit = supercell_orbit_list.get_orbit(i) 

180 counts = orbit.get_cluster_counts(structure_icet) 

181 sorted_counts = Counter() 

182 for atomic_numbers, count in counts.items(): 

183 symbols = atomic_number_to_chemical_symbol(atomic_numbers) 

184 sorted_symbols = tuple(sorted(symbols)) 

185 sorted_counts[sorted_symbols] += count 

186 cluster_counts_full[i] = sorted_counts 

187 return cluster_counts_full