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

1"""Definition of the canonical annealing class.""" 

2 

3from ase import Atoms 

4from ase.data import chemical_symbols 

5from ase.units import kB 

6from typing import Dict, List 

7 

8from .. import DataContainer 

9from ..calculators.base_calculator import BaseCalculator 

10from .thermodynamic_base_ensemble import ThermodynamicBaseEnsemble 

11from .semi_grand_canonical_ensemble import get_chemical_potentials 

12from .canonical_annealing import available_cooling_functions 

13 

14 

15class SGCAnnealing(ThermodynamicBaseEnsemble): 

16 """Instances of this class allow one to carry out simulated annealing 

17 in the semi grand canonical ensemble, i.e. the temperature is varied in 

18 pre-defined fashion while the chemical potential is kept fixed. See 

19 :class:`mchammer.ensembles.SemiGrandCanonicalEnsemble` for more information 

20 about the ensemble. 

21 

22 The temperature control scheme is selected via the 

23 ``cooling_function`` keyword argument, while the initial and final 

24 temperature are set via the ``T_start`` and ``T_stop`` arguments. 

25 Several pre-defined temperature control schemes are available 

26 including `linear` and `exponential`. In the latter case the 

27 temperature varies logarithmatically as a function of the MC step, 

28 emulating the exponential temperature dependence of the atomic 

29 exchange rate encountered in many materials. It is also possible 

30 to provide a user defined cooling function via the keyword 

31 argument. This function must comply with the following function 

32 header:: 

33 

34 def cooling_function(step, T_start, T_stop, n_steps): 

35 T = ... # compute temperature 

36 return T 

37 

38 Here ``step`` refers to the current MC trial step. 

39 

40 Parameters 

41 ---------- 

42 structure : :class:`Atoms <ase.Atoms>` 

43 atomic configuration to be used in the Monte Carlo simulation; 

44 also defines the initial occupation vector 

45 chemical_potentials : Dict[str, float] 

46 chemical potential for each species :math:`\\mu_i`; the key 

47 denotes the species, the value specifies the chemical potential in 

48 units that are consistent with the underlying cluster expansion 

49 calculator : :class:`BaseCalculator <mchammer.calculators.ClusterExpansionCalculator>` 

50 calculator to be used for calculating the potential changes 

51 that enter the evaluation of the Metropolis criterion 

52 T_start : float 

53 temperature from which the annealing is started 

54 T_stop : float 

55 final temperature for annealing 

56 n_steps : int 

57 number of steps to take in the annealing simulation 

58 cooling_function : str/function 

59 to use the predefined cooling functions provide a string 

60 `linear` or `exponential`, otherwise provide a function 

61 boltzmann_constant : float 

62 Boltzmann constant :math:`k_B` in appropriate 

63 units, i.e. units that are consistent 

64 with the underlying cluster expansion 

65 and the temperature units [default: eV/K] 

66 user_tag : str 

67 human-readable tag for ensemble [default: None] 

68 random_seed : int 

69 seed for the random number generator used in the Monte Carlo 

70 simulation 

71 dc_filename : str 

72 name of file the data container associated with the ensemble 

73 will be written to; if the file exists it will be read, the 

74 data container will be appended, and the file will be 

75 updated/overwritten 

76 data_container_write_period : float 

77 period in units of seconds at which the data container is 

78 written to file; writing periodically to file provides both 

79 a way to examine the progress of the simulation and to back up 

80 the data [default: np.inf] 

81 ensemble_data_write_interval : int 

82 interval at which data is written to the data container; this 

83 includes for example the current value of the calculator 

84 (i.e. usually the energy) as well as ensembles specific fields 

85 such as temperature or the number of atoms of different species 

86 trajectory_write_interval : int 

87 interval at which the current occupation vector of the atomic 

88 configuration is written to the data container. 

89 sublattice_probabilities : List[float] 

90 probability for picking a sublattice when doing a random swap. 

91 This should be as long as the number of sublattices and should 

92 sum up to 1. 

93 

94 """ 

95 

96 def __init__(self, 

97 structure: Atoms, 

98 calculator: BaseCalculator, 

99 T_start: float, 

100 T_stop: float, 

101 n_steps: int, 

102 chemical_potentials: Dict[str, float], 

103 cooling_function: str = 'exponential', 

104 boltzmann_constant: float = kB, 

105 user_tag: str = None, 

106 random_seed: int = None, 

107 dc_filename: str = None, 

108 data_container: str = None, 

109 data_container_write_period: float = 600, 

110 ensemble_data_write_interval: int = None, 

111 trajectory_write_interval: int = None, 

112 sublattice_probabilities: List[float] = None) -> None: 

113 

114 self._ensemble_parameters = dict(n_steps=n_steps) 

115 

116 self._chemical_potentials = get_chemical_potentials(chemical_potentials) 

117 

118 # add chemical potentials to ensemble parameters 

119 self._chemical_potentials = get_chemical_potentials(chemical_potentials) 

120 for atnum, chempot in self.chemical_potentials.items(): 

121 mu_sym = 'mu_{}'.format(chemical_symbols[atnum]) 

122 self._ensemble_parameters[mu_sym] = chempot 

123 

124 super().__init__( 

125 structure=structure, 

126 calculator=calculator, 

127 user_tag=user_tag, 

128 random_seed=random_seed, 

129 dc_filename=dc_filename, 

130 data_container=data_container, 

131 data_container_class=DataContainer, 

132 data_container_write_period=data_container_write_period, 

133 ensemble_data_write_interval=ensemble_data_write_interval, 

134 trajectory_write_interval=trajectory_write_interval, 

135 boltzmann_constant=boltzmann_constant) 

136 

137 self._temperature = T_start 

138 self._T_start = T_start 

139 self._T_stop = T_stop 

140 self._n_steps = n_steps 

141 

142 self._ground_state_candidate = self.configuration.structure 

143 self._ground_state_candidate_potential = self.calculator.calculate_total( 

144 occupations=self.configuration.occupations) 

145 

146 # setup cooling function 

147 if isinstance(cooling_function, str): 

148 available = sorted(available_cooling_functions.keys()) 

149 if cooling_function not in available: 149 ↛ 150line 149 didn't jump to line 150, because the condition on line 149 was never true

150 raise ValueError( 

151 'Select from the available cooling_functions {}'.format(available)) 

152 self._cooling_function = available_cooling_functions[cooling_function] 

153 elif callable(cooling_function): 153 ↛ 156line 153 didn't jump to line 156, because the condition on line 153 was never false

154 self._cooling_function = cooling_function 

155 else: 

156 raise TypeError('cooling_function must be either str or a function') 

157 

158 if sublattice_probabilities is None: 158 ↛ 161line 158 didn't jump to line 161, because the condition on line 158 was never false

159 self._flip_sublattice_probabilities = self._get_flip_sublattice_probabilities() 

160 else: 

161 self._flip_sublattice_probabilities = sublattice_probabilities 

162 

163 @property 

164 def chemical_potentials(self) -> Dict[int, float]: 

165 """ 

166 chemical potentials :math:`\\mu_i` (see parameters section above) 

167 """ 

168 return self._chemical_potentials 

169 

170 @property 

171 def temperature(self) -> float: 

172 """ Current temperature """ 

173 return self._temperature 

174 

175 @property 

176 def estimated_ground_state(self): 

177 """ Structure with lowest observed potential during run """ 

178 return self._ground_state_candidate.copy() 

179 

180 @property 

181 def estimated_ground_state_potential(self): 

182 """ Lowest observed potential during run """ 

183 return self._ground_state_candidate_potential 

184 

185 def run(self): 

186 """ Runs the annealing. """ 

187 if self.step >= self._n_steps: 

188 raise Exception('Annealing has already finished') 

189 super().run(self._n_steps - self.step) 

190 

191 def _do_trial_step(self) -> int: 

192 """ Carries out one Monte Carlo trial step. """ 

193 self._temperature = self._cooling_function( 

194 self.step, self._T_start, self._T_stop, self._n_steps) 

195 sublattice_index = self.get_random_sublattice_index(self._flip_sublattice_probabilities) 

196 return self.do_sgc_flip( 

197 sublattice_index=sublattice_index, chemical_potentials=self.chemical_potentials) 

198 

199 def _get_ensemble_data(self) -> Dict: 

200 """Returns the data associated with the ensemble. For the 

201 CanonicalAnnealing this specifically includes the temperature. 

202 """ 

203 data = super()._get_ensemble_data() 

204 

205 # species counts 

206 data.update(self._get_species_counts()) 

207 

208 data['temperature'] = self.temperature 

209 if data['potential'] < self._ground_state_candidate_potential: 

210 self._ground_state_candidate_potential = data['potential'] 

211 self._ground_state_candidate = self.configuration.structure 

212 return data