Coverage for /builds/kinetik161/ase/ase/optimize/gpmin/gpmin.py: 69.92%

123 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-12-10 11:04 +0000

1import warnings 

2 

3import numpy as np 

4from scipy.optimize import minimize 

5 

6from ase.io.jsonio import write_json 

7from ase.optimize.gpmin.gp import GaussianProcess 

8from ase.optimize.gpmin.kernel import SquaredExponential 

9from ase.optimize.gpmin.prior import ConstantPrior 

10from ase.optimize.optimize import Optimizer 

11from ase.parallel import world 

12 

13 

14class GPMin(Optimizer, GaussianProcess): 

15 def __init__(self, atoms, restart=None, logfile='-', trajectory=None, 

16 prior=None, kernel=None, master=None, noise=None, weight=None, 

17 scale=None, force_consistent=Optimizer._deprecated, 

18 batch_size=None, 

19 bounds=None, update_prior_strategy="maximum", 

20 update_hyperparams=False): 

21 """Optimize atomic positions using GPMin algorithm, which uses both 

22 potential energies and forces information to build a PES via Gaussian 

23 Process (GP) regression and then minimizes it. 

24 

25 Default behaviour: 

26 -------------------- 

27 The default values of the scale, noise, weight, batch_size and bounds 

28 parameters depend on the value of update_hyperparams. In order to get 

29 the default value of any of them, they should be set up to None. 

30 Default values are: 

31 

32 update_hyperparams = True 

33 scale : 0.3 

34 noise : 0.004 

35 weight: 2. 

36 bounds: 0.1 

37 batch_size: 1 

38 

39 update_hyperparams = False 

40 scale : 0.4 

41 noise : 0.005 

42 weight: 1. 

43 bounds: irrelevant 

44 batch_size: irrelevant 

45 

46 Parameters: 

47 ------------------ 

48 

49 atoms: Atoms object 

50 The Atoms object to relax. 

51 

52 restart: string 

53 JSON file used to store the training set. If set, file with 

54 such a name will be searched and the data in the file incorporated 

55 to the new training set, if the file exists. 

56 

57 logfile: file object or str 

58 If *logfile* is a string, a file with that name will be opened. 

59 Use '-' for stdout 

60 

61 trajectory: string 

62 File used to store trajectory of atomic movement. 

63 

64 master: boolean 

65 Defaults to None, which causes only rank 0 to save files. If 

66 set to True, this rank will save files. 

67 

68 prior: Prior object or None 

69 Prior for the GP regression of the PES surface 

70 See ase.optimize.gpmin.prior 

71 If *prior* is None, then it is set as the 

72 ConstantPrior with the constant being updated 

73 using the update_prior_strategy specified as a parameter 

74 

75 kernel: Kernel object or None 

76 Kernel for the GP regression of the PES surface 

77 See ase.optimize.gpmin.kernel 

78 If *kernel* is None the SquaredExponential kernel is used. 

79 Note: It needs to be a kernel with derivatives!!!!! 

80 

81 noise: float 

82 Regularization parameter for the Gaussian Process Regression. 

83 

84 weight: float 

85 Prefactor of the Squared Exponential kernel. 

86 If *update_hyperparams* is False, changing this parameter 

87 has no effect on the dynamics of the algorithm. 

88 

89 update_prior_strategy: string 

90 Strategy to update the constant from the ConstantPrior 

91 when more data is collected. It does only work when 

92 Prior = None 

93 

94 options: 

95 'maximum': update the prior to the maximum sampled energy 

96 'init' : fix the prior to the initial energy 

97 'average': use the average of sampled energies as prior 

98 

99 scale: float 

100 scale of the Squared Exponential Kernel 

101 

102 update_hyperparams: boolean 

103 Update the scale of the Squared exponential kernel 

104 every batch_size-th iteration by maximizing the 

105 marginal likelihood. 

106 

107 batch_size: int 

108 Number of new points in the sample before updating 

109 the hyperparameters. 

110 Only relevant if the optimizer is executed in update_hyperparams 

111 mode: (update_hyperparams = True) 

112 

113 bounds: float, 0<bounds<1 

114 Set bounds to the optimization of the hyperparameters. 

115 Let t be a hyperparameter. Then it is optimized under the 

116 constraint (1-bound)*t_0 <= t <= (1+bound)*t_0 

117 where t_0 is the value of the hyperparameter in the previous 

118 step. 

119 If bounds is False, no constraints are set in the optimization of 

120 the hyperparameters. 

121 

122 .. warning:: The memory of the optimizer scales as O(n²N²) where 

123 N is the number of atoms and n the number of steps. 

124 If the number of atoms is sufficiently high, this 

125 may cause a memory issue. 

126 This class prints a warning if the user tries to 

127 run GPMin with more than 100 atoms in the unit cell. 

128 """ 

129 # Warn the user if the number of atoms is very large 

130 if len(atoms) > 100: 

131 warning = ('Possible Memory Issue. There are more than ' 

132 '100 atoms in the unit cell. The memory ' 

133 'of the process will increase with the number ' 

134 'of steps, potentially causing a memory issue. ' 

135 'Consider using a different optimizer.') 

136 

137 warnings.warn(warning) 

138 

139 # Give it default hyperparameters 

140 if update_hyperparams: # Updated GPMin 

141 if scale is None: 

142 scale = 0.3 

143 if noise is None: 

144 noise = 0.004 

145 if weight is None: 

146 weight = 2. 

147 if bounds is None: 

148 self.eps = 0.1 

149 elif bounds is False: 

150 self.eps = None 

151 else: 

152 self.eps = bounds 

153 if batch_size is None: 

154 self.nbatch = 1 

155 else: 

156 self.nbatch = batch_size 

157 else: # GPMin without updates 

158 if scale is None: 

159 scale = 0.4 

160 if noise is None: 

161 noise = 0.001 

162 if weight is None: 

163 weight = 1. 

164 if bounds is not None: 

165 warning = ('The parameter bounds is of no use ' 

166 'if update_hyperparams is False. ' 

167 'The value provided by the user ' 

168 'is being ignored.') 

169 warnings.warn(warning, UserWarning) 

170 if batch_size is not None: 

171 warning = ('The parameter batch_size is of no use ' 

172 'if update_hyperparams is False. ' 

173 'The value provided by the user ' 

174 'is being ignored.') 

175 warnings.warn(warning, UserWarning) 

176 

177 # Set the variables to something anyways 

178 self.eps = False 

179 self.nbatch = None 

180 

181 self.strategy = update_prior_strategy 

182 self.update_hp = update_hyperparams 

183 self.function_calls = 1 

184 self.force_calls = 0 

185 self.x_list = [] # Training set features 

186 self.y_list = [] # Training set targets 

187 

188 Optimizer.__init__(self, atoms, restart, logfile, 

189 trajectory, master, force_consistent) 

190 if prior is None: 

191 self.update_prior = True 

192 prior = ConstantPrior(constant=None) 

193 else: 

194 self.update_prior = False 

195 

196 if kernel is None: 

197 kernel = SquaredExponential() 

198 GaussianProcess.__init__(self, prior, kernel) 

199 self.set_hyperparams(np.array([weight, scale, noise])) 

200 

201 def acquisition(self, r): 

202 e = self.predict(r) 

203 return e[0], e[1:] 

204 

205 def update(self, r, e, f): 

206 """Update the PES 

207 

208 Update the training set, the prior and the hyperparameters. 

209 Finally, train the model 

210 """ 

211 # update the training set 

212 self.x_list.append(r) 

213 f = f.reshape(-1) 

214 y = np.append(np.array(e).reshape(-1), -f) 

215 self.y_list.append(y) 

216 

217 # Set/update the constant for the prior 

218 if self.update_prior: 

219 if self.strategy == 'average': 

220 av_e = np.mean(np.array(self.y_list)[:, 0]) 

221 self.prior.set_constant(av_e) 

222 elif self.strategy == 'maximum': 

223 max_e = np.max(np.array(self.y_list)[:, 0]) 

224 self.prior.set_constant(max_e) 

225 elif self.strategy == 'init': 

226 self.prior.set_constant(e) 

227 self.update_prior = False 

228 

229 # update hyperparams 

230 if (self.update_hp and self.function_calls % self.nbatch == 0 and 

231 self.function_calls != 0): 

232 self.fit_to_batch() 

233 

234 # build the model 

235 self.train(np.array(self.x_list), np.array(self.y_list)) 

236 

237 def relax_model(self, r0): 

238 result = minimize(self.acquisition, r0, method='L-BFGS-B', jac=True) 

239 if result.success: 

240 return result.x 

241 else: 

242 self.dump() 

243 raise RuntimeError("The minimization of the acquisition function " 

244 "has not converged") 

245 

246 def fit_to_batch(self): 

247 """Fit hyperparameters keeping the ratio noise/weight fixed""" 

248 ratio = self.noise / self.kernel.weight 

249 self.fit_hyperparameters(np.array(self.x_list), 

250 np.array(self.y_list), eps=self.eps) 

251 self.noise = ratio * self.kernel.weight 

252 

253 def step(self, f=None): 

254 optimizable = self.optimizable 

255 if f is None: 

256 f = optimizable.get_forces() 

257 

258 r0 = optimizable.get_positions().reshape(-1) 

259 e0 = optimizable.get_potential_energy() 

260 self.update(r0, e0, f) 

261 

262 r1 = self.relax_model(r0) 

263 optimizable.set_positions(r1.reshape(-1, 3)) 

264 e1 = optimizable.get_potential_energy() 

265 f1 = optimizable.get_forces() 

266 self.function_calls += 1 

267 self.force_calls += 1 

268 count = 0 

269 while e1 >= e0: 

270 self.update(r1, e1, f1) 

271 r1 = self.relax_model(r0) 

272 

273 optimizable.set_positions(r1.reshape(-1, 3)) 

274 e1 = optimizable.get_potential_energy() 

275 f1 = optimizable.get_forces() 

276 self.function_calls += 1 

277 self.force_calls += 1 

278 if self.converged(f1): 

279 break 

280 

281 count += 1 

282 if count == 30: 

283 raise RuntimeError("A descent model could not be built") 

284 self.dump() 

285 

286 def dump(self): 

287 """Save the training set""" 

288 if world.rank == 0 and self.restart is not None: 

289 with open(self.restart, 'wb') as fd: 

290 write_json(fd, (self.x_list, self.y_list)) 

291 

292 def read(self): 

293 self.x_list, self.y_list = self.load()