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

54 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-05-06 04:14 +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 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._primitive_structure.copy() 

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 c = self.orbits[k].representative_cluster.__str__() 

117 s += [f'orbit: {k:3} order: {orbit.order:3}' 

118 f' multiplicity: {len(orbit):3} representative_cluster: {c}'] 

119 return '\n'.join(s) 

120 

121 def get_supercell_orbit_list(self, 

122 structure: Atoms, 

123 fractional_position_tolerance: float): 

124 """ 

125 Returns the orbit list for a supercell structure. 

126 

127 Parameters 

128 ---------- 

129 structure 

130 Atomic structure. 

131 fractional_position_tolerance 

132 Tolerance applied when comparing positions in fractional coordinates. 

133 """ 

134 lolg = LocalOrbitListGenerator( 

135 self, 

136 structure=Structure.from_atoms(structure), 

137 fractional_position_tolerance=fractional_position_tolerance) 

138 supercell_orbit_list = lolg.generate_full_orbit_list() 

139 return supercell_orbit_list 

140 

141 def get_cluster_counts(self, 

142 structure: Atoms, 

143 fractional_position_tolerance: float, 

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

145 """ 

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

147 

148 Parameters 

149 ---------- 

150 structure 

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

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

153 fractional_position_tolerance 

154 Tolerance applied when comparing positions in fractional coordinates. 

155 orbit_indices 

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

157 orbits will be counted. 

158 

159 Returns 

160 ------- 

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

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

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

164 as values. 

165 """ 

166 supercell_orbit_list = self.get_supercell_orbit_list( 

167 structure=structure, 

168 fractional_position_tolerance=fractional_position_tolerance) 

169 

170 # Collect counts for all orbit_indices 

171 if orbit_indices is None: 

172 orbit_indices = range(len(self)) 

173 structure_icet = Structure.from_atoms(structure) 

174 cluster_counts_full = {} 

175 for i in orbit_indices: 

176 orbit = supercell_orbit_list.get_orbit(i) 

177 counts = orbit.get_cluster_counts(structure_icet) 

178 sorted_counts = Counter() 

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

180 symbols = atomic_number_to_chemical_symbol(atomic_numbers) 

181 sorted_symbols = tuple(sorted(symbols)) 

182 sorted_counts[sorted_symbols] += count 

183 cluster_counts_full[i] = sorted_counts 

184 return cluster_counts_full