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

1from collections import namedtuple 

2from typing import Dict 

3 

4import numpy as np 

5 

6from ase import Atoms 

7from icet import ClusterSpace 

8from mchammer.observers import ClusterCountObserver 

9from mchammer.observers.base_observer import BaseObserver 

10 

11ClusterCountInfo = namedtuple('ClusterCountInfo', ['counts', 'dc_tags']) 

12 

13 

14class BinaryShortRangeOrderObserver(BaseObserver): 

15 """ 

16 This class represents a short range order (SRO) observer for a 

17 binary system. 

18 

19 

20 Parameters 

21 ---------- 

22 cluster_space : icet.ClusterSpace 

23 cluster space used for initialization 

24 structure : ase.Atoms 

25 defines the lattice which the observer will work on 

26 interval : int 

27 the observation interval, defaults to None meaning that if the 

28 observer is used in a Monte Carlo simulations, then the Ensemble object 

29 will set the interval. 

30 radius : float 

31 the maximum radius for the neigbhor shells considered 

32 

33 Attributes 

34 ---------- 

35 tag : str 

36 human readable observer name (`BinaryShortRangeOrderObserver`) 

37 interval : int 

38 observation interval 

39 

40 Example 

41 ------- 

42 The following snippet illustrate how to use the short-range order (SRO) 

43 observer in a Monte Carlo simulation of a bulk supercell. Here, the 

44 parameters of the cluster expansion are set to emulate a simple Ising model 

45 in order to obtain an example that can be run without modification. In 

46 practice, one should of course use a proper cluster expansion:: 

47 

48 >>> from ase.build import bulk 

49 >>> from icet import ClusterExpansion, ClusterSpace 

50 >>> from mchammer.calculators import ClusterExpansionCalculator 

51 >>> from mchammer.ensembles import CanonicalEnsemble 

52 >>> from mchammer.observers import BinaryShortRangeOrderObserver 

53 

54 >>> # prepare cluster expansion 

55 >>> # the setup emulates a second nearest-neighbor (NN) Ising model 

56 >>> # (zerolet and singlet ECIs are zero; only first and second neighbor 

57 >>> # pairs are included) 

58 >>> prim = bulk('Au') 

59 >>> cs = ClusterSpace(prim, cutoffs=[4.3], chemical_symbols=['Ag', 'Au']) 

60 >>> ce = ClusterExpansion(cs, [0, 0, 0.1, -0.02]) 

61 

62 >>> # prepare initial configuration 

63 >>> nAg = 10 

64 >>> structure = prim.repeat(3) 

65 >>> structure.set_chemical_symbols(nAg * ['Ag'] + (len(structure) - nAg) * ['Au']) 

66 

67 >>> # set up MC simulation 

68 >>> calc = ClusterExpansionCalculator(structure, ce) 

69 >>> mc = CanonicalEnsemble(structure=structure, calculator=calc, temperature=600, 

70 ... dc_filename='myrun_sro.dc') 

71 

72 # set up observer and attach it to the MC simulation 

73 sro = BinaryShortRangeOrderObserver(cs, structure, interval=len(structure), 

74 radius=4.3) 

75 mc.attach_observer(sro) 

76 

77 # run 1000 trial steps 

78 mc.run(1000) 

79 

80 After having run this snippet one can access the SRO parameters via the 

81 data container:: 

82 

83 print(mc.data_container.data) 

84 """ 

85 

86 def __init__(self, cluster_space, structure: Atoms, 

87 radius: float, interval: int = None) -> None: 

88 super().__init__(interval=interval, return_type=dict, 

89 tag='BinaryShortRangeOrderObserver') 

90 

91 self._structure = structure 

92 

93 self._cluster_space = ClusterSpace( 

94 structure=cluster_space.primitive_structure, 

95 cutoffs=[radius], 

96 chemical_symbols=cluster_space.chemical_symbols) 

97 self._cluster_count_observer = ClusterCountObserver( 

98 cluster_space=self._cluster_space, structure=structure, 

99 interval=interval) 

100 

101 self._sublattices = self._cluster_space.get_sublattices(structure) 

102 binary_sublattice_counts = 0 

103 for symbols in self._sublattices.allowed_species: 

104 if len(symbols) == 2: 

105 binary_sublattice_counts += 1 

106 self._symbols = sorted(symbols) 

107 elif len(symbols) > 2: 

108 raise ValueError('Cluster space has more than two allowed' 

109 ' species on a sublattice. ' 

110 'Allowed species: {}'.format(symbols)) 

111 if binary_sublattice_counts != 1: 

112 raise ValueError('Number of binary sublattices must equal one,' 

113 ' not {}'.format(binary_sublattice_counts)) 

114 

115 def get_observable(self, structure: Atoms) -> Dict[str, float]: 

116 """Returns the value of the property from a cluster expansion 

117 model for a given atomic configurations. 

118 

119 Parameters 

120 ---------- 

121 structure 

122 input atomic structure 

123 """ 

124 

125 self._cluster_count_observer._generate_counts(structure) 

126 df = self._cluster_count_observer.count_frame 

127 

128 symbol_counts = self._get_atom_count(structure) 

129 conc_B = self._get_concentrations(structure)[self._symbols[0]] 

130 

131 pair_orbit_indices = set( 

132 df.loc[df['order'] == 2]['orbit_index'].tolist()) 

133 N = symbol_counts[self._symbols[0]] + symbol_counts[self._symbols[1]] 

134 sro_parameters = {} 

135 for k, orbit_index in enumerate(sorted(pair_orbit_indices)): 

136 orbit_df = df.loc[df['orbit_index'] == orbit_index] 

137 A_B_pair_count = 0 

138 total_count = 0 

139 total_A_count = 0 

140 for i, row in orbit_df.iterrows(): 

141 total_count += row.cluster_count 

142 if self._symbols[0] in row.occupation: 

143 total_A_count += row.cluster_count 

144 if self._symbols[0] in row.occupation and \ 

145 self._symbols[1] in row.occupation: 

146 A_B_pair_count += row.cluster_count 

147 

148 key = 'sro_{}_{}'.format(self._symbols[0], k+1) 

149 Z_tot = symbol_counts[self._symbols[0]] * 2 * total_count / N 

150 if conc_B == 1 or Z_tot == 0: 150 ↛ 151line 150 didn't jump to line 151, because the condition on line 150 was never true

151 value = 0 

152 else: 

153 value = 1 - A_B_pair_count/(Z_tot * (1-conc_B)) 

154 sro_parameters[key] = value 

155 

156 return sro_parameters 

157 

158 def _get_concentrations(self, structure: Atoms) -> Dict[str, float]: 

159 """Returns concentrations for each species relative its 

160 sublattice. 

161 

162 Parameters 

163 ---------- 

164 structure 

165 the configuration that will be analyzed 

166 """ 

167 occupation = np.array(structure.get_chemical_symbols()) 

168 concentrations = {} 

169 for sublattice in self._sublattices: 

170 if len(sublattice.chemical_symbols) == 1: 170 ↛ 171line 170 didn't jump to line 171, because the condition on line 170 was never true

171 continue 

172 for symbol in sublattice.chemical_symbols: 

173 symbol_count = occupation[sublattice.indices].tolist().count( 

174 symbol) 

175 concentration = symbol_count / len(sublattice.indices) 

176 concentrations[symbol] = concentration 

177 return concentrations 

178 

179 def _get_atom_count(self, structure: Atoms) -> Dict[str, float]: 

180 """Returns atom counts for each species relative its 

181 sublattice. 

182 

183 Parameters 

184 ---------- 

185 structure 

186 the configuration that will be analyzed 

187 """ 

188 occupation = np.array(structure.get_chemical_symbols()) 

189 counts = {} 

190 for sublattice in self._sublattices: 

191 if len(sublattice.chemical_symbols) == 1: 191 ↛ 192line 191 didn't jump to line 192, because the condition on line 191 was never true

192 continue 

193 for symbol in sublattice.chemical_symbols: 

194 symbol_count = occupation[sublattice.indices].tolist().count( 

195 symbol) 

196 counts[symbol] = symbol_count 

197 return counts