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 ase import Atoms 

2from icet import ClusterSpace 

3from icet.core.structure import Structure 

4from mchammer.observers.base_observer import BaseObserver 

5from typing import List, Dict 

6import numpy as np 

7 

8 

9class SiteOccupancyObserver(BaseObserver): 

10 """ 

11 This class represents a site occupation factor (SOF) observer. 

12 

13 A SOF observer allows to compute the site occupation factors along the 

14 trajectory sampled by a Monte Carlo (MC) simulation. 

15 

16 Parameters 

17 ---------- 

18 cluster_space : icet.ClusterSpace 

19 cluster space from which the allowed species are extracted 

20 

21 structure : ase.Atoms 

22 supercell consistent with primitive structure in cluster space; used 

23 to determine which species are allowed on each site 

24 

25 sites : dict(str, list(int)) 

26 dictionary containing lists of sites that are to be considered; 

27 the keys will be taken as the names of the sites; the indices refer 

28 to the primitive structure associated with the cluster space 

29 

30 interval : int 

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

32 observer is used in a Monte Carlo simulation, then the Ensemble object 

33 will set the interval. 

34 

35 Attributes 

36 ---------- 

37 tag : str 

38 name of observer 

39 

40 interval : int 

41 observation interval 

42 

43 Example 

44 ------- 

45 The following snippet illustrate how to use the site occupancy factor (SOF) 

46 observer in a Monte Carlo simulation of a surface slab. Here, the SOF 

47 observer is used to monitor the concentrations of different species at the 

48 surface, the first subsurface layer, and the remaining "bulk". A minimal 

49 cluster expansion is used with slightly modified surface interactions in 

50 order to obtain an example that can be run without much ado. In practice, 

51 one should of course use a proper cluster expansion:: 

52 

53 >>> from ase.build import fcc111 

54 >>> from icet import ClusterExpansion, ClusterSpace 

55 >>> from mchammer.calculators import ClusterExpansionCalculator 

56 >>> from mchammer.ensembles import CanonicalEnsemble 

57 >>> from mchammer.observers import SiteOccupancyObserver 

58 

59 >>> # prepare reference structure 

60 >>> prim = fcc111('Au', size=(1, 1, 10), vacuum=10.0) 

61 >>> prim.translate((0.1, 0.1, 0.0)) 

62 >>> prim.wrap() 

63 >>> prim.pbc = True # icet requires pbc in all directions 

64 

65 >>> # prepare cluster expansion 

66 >>> cs = ClusterSpace(prim, cutoffs=[3.7], chemical_symbols=['Ag', 'Au']) 

67 >>> params = [0] + 5 * [0] + 10 * [0.1] 

68 >>> params[1] = 0.01 

69 >>> params[6] = 0.12 

70 >>> ce = ClusterExpansion(cs, params) 

71 >>> print(ce) 

72 

73 >>> # prepare initial configuration based on a 2x2 supercell 

74 >>> structure = prim.repeat((2, 2, 1)) 

75 >>> for k in range(20): 

76 >>> structure[k].symbol = 'Ag' 

77 

78 >>> # set up MC simulation 

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

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

81 ... dc_filename='myrun_sof.dc') 

82 

83 >>> # set up observer and attach it to the MC simulation 

84 >>> sites = {'surface': [0, 9], 'subsurface': [1, 8], 

85 ... 'bulk': list(range(2, 8))} 

86 >>> sof = SiteOccupancyObserver(cs, structure, sites, interval=len(structure)) 

87 >>> mc.attach_observer(sof) 

88 

89 >>> # run 1000 trial steps 

90 >>> mc.run(1000) 

91 

92 After having run this snippet one can access the SOFs via the data 

93 container:: 

94 

95 >>> print(mc.data_container.data) 

96 """ 

97 

98 def __init__(self, cluster_space: ClusterSpace, 

99 structure: Atoms, 

100 sites: Dict[str, List[int]], 

101 interval: int = None) -> None: 

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

103 tag='SiteOccupancyObserver') 

104 

105 self._sites = {site: sorted(indices) 

106 for site, indices in sites.items()} 

107 

108 self._set_allowed_species(cluster_space, structure) 

109 

110 def _set_allowed_species(self, 

111 cluster_space: ClusterSpace, 

112 structure: Atoms): 

113 """ 

114 Set the allowed species for the selected sites in the Atoms object 

115 

116 Parameters 

117 ---------- 

118 cluster_space 

119 Cluster space implicitly defining allowed species 

120 structure 

121 Specific supercell (consistent with cluster_space) whose 

122 allowed species are to be determined 

123 """ 

124 

125 primitive_structure = Structure.from_atoms(cluster_space.primitive_structure) 

126 chemical_symbols = cluster_space.get_chemical_symbols() 

127 

128 if len(chemical_symbols) == 1: 

129 # If the allowed species are the same for all sites no loop is 

130 # required 

131 allowed_species = {site: chemical_symbols[0] for 

132 site in self._sites.keys()} 

133 else: 

134 # Loop over the lattice sites to find the allowed species 

135 allowed_species = {} 

136 for site, indices in self._sites.items(): 

137 allowed_species[site] = None 

138 positions = structure.positions[np.array(indices)] 

139 lattice_sites = primitive_structure.find_lattice_sites_by_positions( 

140 positions=positions, 

141 fractional_position_tolerance=cluster_space.fractional_position_tolerance) 

142 for l, lattice_site in enumerate(lattice_sites): 

143 species = chemical_symbols[lattice_site.index] 

144 # check that the allowed species are equal for all sites 

145 if allowed_species[site] is not None and \ 

146 species != allowed_species[site]: 

147 raise Exception("The allowed species {} for the site" 

148 " with index {} differs from the" 

149 " result {} for the previous index" 

150 " ({})!".format(species, indices[l], 

151 allowed_species[site], 

152 indices[l-1])) 

153 allowed_species[site] = species 

154 

155 self._allowed_species = allowed_species 

156 

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

158 """ 

159 Returns the site occupation factors for a given atomic configuration. 

160 

161 Parameters 

162 ---------- 

163 structure 

164 input atomic structure. 

165 """ 

166 

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

168 sofs = {} 

169 for site, indices in self._sites.items(): 

170 counts = {species: 0 for species in self._allowed_species[site]} 

171 symbols, sym_counts = np.unique(chemical_symbols[indices], 

172 return_counts=True) 

173 for sym, count in zip(symbols, sym_counts): 

174 counts[sym] += count 

175 

176 for species in counts.keys(): 

177 key = 'sof_{}_{}'.format(site, species) 

178 sofs[key] = float(counts[species]) / len(indices) 

179 

180 return sofs