Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import random 

2import numpy as np 

3from typing import Dict, List, Tuple 

4from ase import Atoms 

5from icet.core.sublattices import Sublattices 

6from icet.tools.geometry import atomic_number_to_chemical_symbol 

7 

8 

9class SwapNotPossibleError(Exception): 

10 pass 

11 

12 

13class ConfigurationManager(object): 

14 """ 

15 The ConfigurationManager owns and handles information pertaining to a 

16 configuration being sampled in a Monte Carlo simulation. 

17 

18 Parameters 

19 ---------- 

20 structure : ase.Atoms 

21 configuration to be handled 

22 sublattices : :class:`Sublattices <icet.core.sublattices.Sublattices>` 

23 sublattices class used to define allowed occupations and so on 

24 

25 Todo 

26 ---- 

27 * revise docstrings 

28 """ 

29 

30 def __init__(self, structure: Atoms, sublattices: Sublattices) -> None: 

31 

32 self._structure = structure.copy() 

33 self._occupations = self._structure.numbers 

34 self._sublattices = sublattices 

35 

36 self._sites_by_species = self._get_sites_by_species() 

37 

38 def _get_sites_by_species(self) -> List[Dict[int, List[int]]]: 

39 """Returns the sites that are occupied for each species. Each 

40 dictionary represents one sublattice where the key is the 

41 species (by atomic number) and the value is the list of sites 

42 occupied by said species in the respective sublattice. 

43 """ 

44 sites_by_species = [] 

45 for sl in self._sublattices: 

46 species_dict = {key: [] for key in sl.atomic_numbers} 

47 for site in sl.indices: 

48 species_dict[self._occupations[site]].append(site) 

49 sites_by_species.append(species_dict) 

50 return sites_by_species 

51 

52 @property 

53 def occupations(self) -> np.ndarray: 

54 """ occupation vector of the configuration (copy) """ 

55 return self._occupations.copy() 

56 

57 @property 

58 def sublattices(self) -> Sublattices: 

59 """sublattices of the configuration""" 

60 return self._sublattices 

61 

62 @property 

63 def structure(self) -> Atoms: 

64 """ atomic structure associated with configuration (copy) """ 

65 structure = self._structure.copy() 

66 structure.set_atomic_numbers(self.occupations) 

67 return structure 

68 

69 def get_occupations_on_sublattice(self, sublattice_index: int) -> List[int]: 

70 """ 

71 Returns the occupations on one sublattice. 

72 

73 Parameters 

74 --------- 

75 sublattice_index 

76 the sublattice for which the occupations should be returned 

77 """ 

78 sl = self.sublattices[sublattice_index] 

79 return list(self.occupations[sl.indices]) 

80 

81 def is_swap_possible(self, sublattice_index: int, 

82 allowed_species: List[int] = None) -> bool: 

83 """ Checks if swap is possible on specific sublattice. 

84 

85 Parameters 

86 ---------- 

87 sublattice_index 

88 index of sublattice to be checked 

89 allowed_species 

90 list of atomic numbers for allowed species 

91 """ 

92 sl = self.sublattices[sublattice_index] 

93 if allowed_species is None: 

94 swap_symbols = set(self.occupations[sl.indices]) 

95 else: 

96 swap_symbols = set([o for o in self.occupations[sl.indices] if o in 

97 allowed_species]) 

98 return len(swap_symbols) > 1 

99 

100 def get_swapped_state(self, sublattice_index: int, 

101 allowed_species: List[int] = None 

102 ) -> Tuple[List[int], List[int]]: 

103 """Returns two random sites (first element of tuple) and their 

104 occupation after a swap (second element of tuple). The new 

105 configuration will obey the occupation constraints associated 

106 with the configuration mananger. 

107 

108 Parameters 

109 ---------- 

110 sublattice_index 

111 sublattice from which to pick sites 

112 allowed_species 

113 list of atomic numbers for allowed species 

114 """ 

115 # pick the first site 

116 if allowed_species is None: 

117 available_sites =\ 

118 self.sublattices[sublattice_index].indices 

119 else: 

120 available_sites =\ 

121 [s for Z in allowed_species for s in 

122 self._get_sites_by_species()[sublattice_index][Z]] 

123 

124 try: 

125 site1 = random.choice(available_sites) 

126 except IndexError: 

127 raise SwapNotPossibleError( 

128 'Sublattice {} is empty.'.format(sublattice_index)) 

129 

130 # pick the second site 

131 if allowed_species is None: 

132 possible_swap_species = \ 

133 set(self._sublattices.get_allowed_numbers_on_site(site1)) - \ 

134 set([self._occupations[site1]]) 

135 else: 

136 possible_swap_species = \ 

137 set(allowed_species) - set([self._occupations[site1]]) 

138 possible_swap_sites = [] 

139 for Z in possible_swap_species: 

140 possible_swap_sites.extend(self._sites_by_species[sublattice_index][Z]) 

141 

142 possible_swap_sites = np.array(possible_swap_sites) 

143 

144 try: 

145 site2 = random.choice(possible_swap_sites) 

146 except IndexError: 

147 raise SwapNotPossibleError( 

148 'Cannot swap on sublattice {} since it is full of {} species .' 

149 .format(sublattice_index, 

150 atomic_number_to_chemical_symbol([self._occupations[site1]])[0])) 

151 

152 return ([site1, site2], [self._occupations[site2], self._occupations[site1]]) 

153 

154 def get_flip_state(self, sublattice_index: int, 

155 allowed_species: List[int] = None) -> Tuple[int, int]: 

156 """ 

157 Returns a site index and a new species for the site. 

158 

159 Parameters 

160 ---------- 

161 sublattice_index 

162 index of sublattice from which to pick a site 

163 allowed_species 

164 list of atomic numbers for allowed species 

165 """ 

166 if allowed_species is None: 

167 available_sites = self._sublattices[sublattice_index].indices 

168 else: 

169 available_sites = [s for Z in allowed_species for s in 

170 self._get_sites_by_species()[sublattice_index][Z]] 

171 

172 site = random.choice(available_sites) 

173 if allowed_species is not None: 

174 species = random.choice(list( 

175 set(allowed_species) - set([self._occupations[site]]))) 

176 else: 

177 species = random.choice(list( 

178 set(self._sublattices[sublattice_index].atomic_numbers) - 

179 set([self._occupations[site]]))) 

180 return site, species 

181 

182 def update_occupations(self, sites: List[int], species: List[int]): 

183 """ 

184 Updates the occupation vector of the configuration being sampled. 

185 This will change the state in both the configuration in the calculator 

186 and the configuration manager. 

187 

188 Parameters 

189 ---------- 

190 sites 

191 indices of sites of the configuration to change 

192 species 

193 new occupations by atomic number 

194 """ 

195 

196 # Update sublattices 

197 for site, new_Z in zip(sites, species): 

198 if 0 > new_Z > 118: 198 ↛ 199line 198 didn't jump to line 199, because the condition on line 198 was never true

199 raise ValueError('Invalid new species {} on site {}'.format(new_Z, site)) 

200 if len(self._occupations) >= site < 0: 

201 raise ValueError('Site {} is not a valid site index'.format(site)) 

202 old_Z = self._occupations[site] 

203 sublattice_index = self.sublattices.get_sublattice_index(site) 

204 

205 if new_Z not in self.sublattices[sublattice_index].atomic_numbers: 

206 raise ValueError('Invalid new species {} on site {}'.format(new_Z, site)) 

207 

208 # Remove site from list of sites for old species 

209 self._sites_by_species[sublattice_index][old_Z].remove(site) 

210 # Add site to list of sites for new species 

211 try: 

212 self._sites_by_species[sublattice_index][new_Z].append(site) 

213 except KeyError: 

214 raise ValueError('Invalid new species {} on site {}'.format(new_Z, site)) 

215 

216 # Update occupation vector itself 

217 self._occupations[sites] = species