Coverage for gpaw/directmin/fdpw/pz_localization.py: 50%

229 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2025-07-20 00:19 +0000

1""" 

2Optimization of orbitals 

3among occupied states only 

4 

5""" 

6 

7from gpaw.directmin.tools import get_n_occ, get_indices, expm_ed 

8from gpaw.directmin.sd_etdm import LBFGS_P 

9from gpaw.directmin.ls_etdm import StrongWolfeConditions as SWC 

10from ase.units import Hartree 

11import numpy as np 

12import time 

13 

14 

15class PZLocalization: 

16 

17 def __init__(self, odd_pot, wfs, maxiter=50, g_tol=5.0e-4): 

18 

19 self.odd_pot = odd_pot 

20 self.n_kps = wfs.kd.nibzkpts 

21 self.g_tol = g_tol / Hartree 

22 self.dtype = wfs.dtype 

23 self.get_en_and_grad_iters = 0 

24 self.method = 'LBFGS' 

25 self.line_search_method = 'AwcSwc' 

26 self.max_iter_line_search = 6 

27 self.n_counter = maxiter 

28 self.eg_count = 0 

29 self.total_eg_count = 0 

30 self.run_count = 0 

31 self.U_k = {} 

32 self.Unew_k = {} 

33 self.e_total = 0.0 

34 self.n_occ = {} 

35 for kpt in wfs.kpt_u: 

36 k = self.n_kps * kpt.s + kpt.q 

37 self.n_occ[k] = get_n_occ(kpt)[0] 

38 dim1 = self.n_occ[k] 

39 dim2 = self.n_occ[k] 

40 self.U_k[k] = np.eye(dim1, dtype=self.dtype) 

41 self.Unew_k[k] = np.eye(dim2, dtype=self.dtype) 

42 

43 def get_energy_and_gradients(self, a_k, wfs): 

44 """ 

45 Energy E = E[A]. Gradients G_ij[A] = dE/dA_ij 

46 Returns E[A] and G[A] at psi = exp(A).T kpt.psi 

47 :param a_k: A 

48 :return: 

49 """ 

50 

51 g_k = {} 

52 self.e_total = 0.0 

53 

54 self.kappa = 0.0 

55 for kpt in wfs.kpt_u: 

56 k = self.n_kps * kpt.s + kpt.q 

57 dim2 = self.Unew_k[k].shape[0] 

58 if dim2 == 0: 

59 g_k[k] = np.zeros_like(a_k[k]) 

60 continue 

61 wfs.timer.start('Unitary matrix') 

62 u_mat, evecs, evals = expm_ed(a_k[k], evalevec=True) 

63 wfs.timer.stop('Unitary matrix') 

64 self.Unew_k[k] = u_mat.copy() 

65 kpt.psit_nG[:dim2] = \ 

66 np.tensordot(u_mat.T, self.psit_knG[k][:dim2], axes=1) 

67 # calc projectors 

68 wfs.pt.integrate(kpt.psit_nG, kpt.P_ani, kpt.q) 

69 

70 del u_mat 

71 

72 wfs.timer.start('Energy and gradients') 

73 if self.odd_pot.name == 'ER_SIC': 

74 g_k[k], e_sic, kappa1 = \ 

75 self.odd_pot.get_energy_and_gradients_inner_loop( 

76 wfs, kpt, a_k[k], evals, evecs) 

77 else: 

78 g_k[k], e_sic, kappa1 = \ 

79 self.odd_pot.get_energy_and_gradients_inner_loop( 

80 wfs, kpt, a_k[k], evals, evecs, exstate=False) 

81 wfs.timer.stop('Energy and gradients') 

82 if kappa1 > self.kappa: 

83 self.kappa = kappa1 

84 self.e_total += e_sic 

85 

86 self.kappa = wfs.kd.comm.max_scalar(self.kappa) 

87 self.e_total = wfs.kd.comm.sum_scalar(self.e_total) 

88 self.eg_count += 1 

89 self.total_eg_count += 1 

90 

91 return self.e_total, g_k 

92 

93 def evaluate_phi_and_der_phi(self, a_k, p_k, alpha, wfs, dens, 

94 phi=None, g_k=None): 

95 """ 

96 phi = f(x_k + alpha_k*p_k) 

97 der_phi = grad f(x_k + alpha_k*p_k) cdot p_k 

98 :return: phi, der_phi, grad f(x_k + alpha_k*p_k) 

99 """ 

100 if phi is None or g_k is None: 

101 x_k = {k: a_k[k] + alpha * p_k[k] for k in a_k.keys()} 

102 phi, g_k = \ 

103 self.get_energy_and_gradients(x_k, wfs) 

104 del x_k 

105 else: 

106 pass 

107 

108 der_phi = 0.0 

109 for k in p_k.keys(): 

110 il1 = get_indices(p_k[k].shape[0]) 

111 der_phi += np.dot(g_k[k][il1].conj(), p_k[k][il1]).real 

112 

113 der_phi = wfs.kd.comm.sum_scalar(der_phi) 

114 

115 return phi, der_phi, g_k 

116 

117 def get_search_direction(self, a_k, g_k, wfs): 

118 

119 # structure of vector is 

120 # (x_1_up, x_2_up,..,y_1_up, y_2_up,.., 

121 # x_1_down, x_2_down,..,y_1_down, y_2_down,.. ) 

122 

123 a = {} 

124 g = {} 

125 

126 for k in a_k.keys(): 

127 il1 = get_indices(a_k[k].shape[0]) 

128 a[k] = a_k[k][il1] 

129 g[k] = g_k[k][il1] 

130 

131 p = self.sd.update_data(wfs, a, g, mode='lcao') 

132 del a, g 

133 

134 p_k = {} 

135 for k in p.keys(): 

136 p_k[k] = np.zeros_like(a_k[k]) 

137 il1 = get_indices(a_k[k].shape[0]) 

138 p_k[k][il1] = p[k] 

139 # make it skew-hermitian 

140 ind_l = np.tril_indices(p_k[k].shape[0], -1) 

141 p_k[k][(ind_l[1], ind_l[0])] = -p_k[k][ind_l].conj() 

142 del p 

143 

144 return p_k 

145 

146 def run(self, e_ks, wfs, dens, log, outer_counter=0, 

147 small_random=True, randvalue=0.01, seed=None): 

148 

149 log = log 

150 self.run_count += 1 

151 self.counter = 0 

152 self.eg_count = 0 

153 # initial things 

154 self.c_knm = {} 

155 self.psit_knG = {} 

156 

157 rng = np.random.default_rng(seed) 

158 

159 for kpt in wfs.kpt_u: 

160 k = self.n_kps * kpt.s + kpt.q 

161 dim1 = self.U_k[k].shape[0] 

162 self.psit_knG[k] = np.tensordot( 

163 self.U_k[k].T, kpt.psit_nG[:dim1], axes=1) 

164 

165 a_k = {} 

166 for kpt in wfs.kpt_u: 

167 k = self.n_kps * kpt.s + kpt.q 

168 d = self.Unew_k[k].shape[0] 

169 if self.run_count == 1 and self.dtype == complex \ 

170 and small_random: 

171 a = randvalue * rng.random((d, d)) * 1.0j 

172 a = a - a.T.conj() 

173 wfs.gd.comm.broadcast(a, 0) 

174 a_k[k] = a 

175 else: 

176 a_k[k] = np.zeros(shape=(d, d), dtype=self.dtype) 

177 

178 self.sd = LBFGS_P(memory=20) 

179 self.ls = SWC( 

180 self.evaluate_phi_and_der_phi, 

181 searchdirtype=self.method, use_descent_and_awc=True, 

182 max_iter=self.max_iter_line_search) 

183 

184 threelasten = [] 

185 # get initial energy and gradients 

186 self.e_total, g_k = self.get_energy_and_gradients(a_k, wfs) 

187 threelasten.append(self.e_total) 

188 g_max = g_max_norm(g_k, wfs) 

189 if g_max < self.g_tol: 

190 for kpt in wfs.kpt_u: 

191 k = self.n_kps * kpt.s + kpt.q 

192 dim1 = self.U_k[k].shape[0] 

193 kpt.psit_nG[:dim1] = np.tensordot( 

194 self.U_k[k].conj(), self.psit_knG[k], axes=1) 

195 # calc projectors 

196 wfs.pt.integrate(kpt.psit_nG, kpt.P_ani, kpt.q) 

197 

198 dim2 = self.Unew_k[k].shape[0] 

199 if dim1 == dim2: 

200 self.U_k[k] = self.U_k[k] @ self.Unew_k[k] 

201 else: 

202 u_oo = self.Unew_k[k] 

203 u_ov = np.zeros(shape=(dim2, dim1 - dim2), 

204 dtype=self.dtype) 

205 u_vv = np.eye(dim1 - dim2, dtype=self.dtype) 

206 unew = np.vstack([np.hstack([u_oo, u_ov]), 

207 np.hstack([u_ov.T, u_vv])]) 

208 self.U_k[k] = self.U_k[k] @ unew 

209 

210 del self.psit_knG 

211 del self.c_knm 

212 if outer_counter is None: 

213 return self.e_total, self.counter 

214 else: 

215 return self.e_total, outer_counter 

216 

217 # stuff which are needed for minim. 

218 phi_0 = self.e_total 

219 phi_old = None 

220 der_phi_old = None 

221 phi_old_2 = None 

222 der_phi_old_2 = None 

223 

224 outer_counter += 1 

225 if log is not None: 

226 log_f(log, self.counter, self.kappa, e_ks, self.e_total, 

227 outer_counter, g_max) 

228 

229 alpha = 1.0 

230 not_converged = True 

231 while not_converged: 

232 

233 # calculate search direction fot current As and Gs 

234 p_k = self.get_search_direction(a_k, g_k, wfs) 

235 

236 # calculate derivative along the search direction 

237 phi_0, der_phi_0, g_k = \ 

238 self.evaluate_phi_and_der_phi(a_k, p_k, 

239 0.0, wfs, dens, 

240 phi=phi_0, g_k=g_k) 

241 if self.counter > 1: 

242 phi_old = phi_0 

243 der_phi_old = der_phi_0 

244 

245 # choose optimal step length along the search direction 

246 # also get energy and gradients for optimal step 

247 alpha, phi_0, der_phi_0, g_k = \ 

248 self.ls.step_length_update( 

249 a_k, p_k, wfs, dens, 

250 phi_0=phi_0, der_phi_0=der_phi_0, 

251 phi_old=phi_old_2, der_phi_old=der_phi_old_2, 

252 alpha_max=3.0, alpha_old=alpha) 

253 

254 # broadcast data is gd.comm > 1 

255 if wfs.gd.comm.size > 1: 

256 alpha_phi_der_phi = np.array([alpha, phi_0, 

257 der_phi_0]) 

258 wfs.gd.comm.broadcast(alpha_phi_der_phi, 0) 

259 alpha = alpha_phi_der_phi[0] 

260 phi_0 = alpha_phi_der_phi[1] 

261 der_phi_0 = alpha_phi_der_phi[2] 

262 for kpt in wfs.kpt_u: 

263 k = self.n_kps * kpt.s + kpt.q 

264 if self.n_occ[k] == 0: 

265 continue 

266 wfs.gd.comm.broadcast(g_k[k], 0) 

267 

268 phi_old_2 = phi_old 

269 der_phi_old_2 = der_phi_old 

270 

271 if alpha > 1.0e-10: 

272 # calculate new matrices at optimal step lenght 

273 a_k = {k: a_k[k] + alpha * p_k[k] for k in a_k.keys()} 

274 g_max = g_max_norm(g_k, wfs) 

275 

276 # output 

277 self.counter += 1 

278 if outer_counter is not None: 

279 outer_counter += 1 

280 if log is not None: 

281 log_f( 

282 log, self.counter, self.kappa, e_ks, phi_0, 

283 outer_counter, g_max) 

284 

285 not_converged = \ 

286 g_max > self.g_tol and \ 

287 self.counter < self.n_counter 

288 

289 threelasten.append(phi_0) 

290 if len(threelasten) > 2: 

291 threelasten = threelasten[-3:] 

292 if threelasten[0] < threelasten[1] < \ 

293 threelasten[2]: 

294 if log is not None: 

295 log('Could not converge, leave the loop', 

296 flush=True) 

297 break 

298 else: 

299 break 

300 

301 if log is not None: 

302 log('INNER LOOP FINISHED.\n') 

303 log('Total number of e/g calls:' + str(self.eg_count)) 

304 

305 for kpt in wfs.kpt_u: 

306 k = self.n_kps * kpt.s + kpt.q 

307 dim1 = self.U_k[k].shape[0] 

308 kpt.psit_nG[:dim1] = np.tensordot( 

309 self.U_k[k].conj(), self.psit_knG[k][:dim1], 

310 axes=1) 

311 # calc projectors 

312 wfs.pt.integrate(kpt.psit_nG, kpt.P_ani, kpt.q) 

313 dim2 = self.Unew_k[k].shape[0] 

314 if dim1 == dim2: 

315 self.U_k[k] = self.U_k[k] @ self.Unew_k[k] 

316 else: 

317 u_oo = self.Unew_k[k] 

318 u_ov = np.zeros(shape=(dim2, dim1 - dim2), 

319 dtype=self.dtype) 

320 u_vv = np.eye(dim1 - dim2, dtype=self.dtype) 

321 unew = np.vstack([np.hstack([u_oo, u_ov]), 

322 np.hstack([u_ov.T, u_vv])]) 

323 self.U_k[k] = self.U_k[k] @ unew 

324 

325 del self.psit_knG 

326 del self.c_knm 

327 if outer_counter is None: 

328 return self.e_total, self.counter 

329 else: 

330 return self.e_total, outer_counter 

331 

332 

333def log_f(log, niter, kappa, e_ks, e_sic, outer_counter=None, g_max=np.inf): 

334 

335 t = time.localtime() 

336 

337 if niter == 0: 

338 header0 = '\nINNER LOOP:\n' 

339 header = ' Kohn-Sham SIC' \ 

340 ' Total \n' \ 

341 ' time energy: energy:' \ 

342 ' energy: Error: G_max:' 

343 log(header0 + header) 

344 

345 if outer_counter is not None: 

346 niter = outer_counter 

347 

348 log('iter: %3d %02d:%02d:%02d ' % 

349 (niter, 

350 t[3], t[4], t[5] 

351 ), end='') 

352 log('%11.6f %11.6f %11.6f %11.1e %11.1e' % 

353 (Hartree * e_ks, 

354 Hartree * e_sic, 

355 Hartree * (e_ks + e_sic), 

356 kappa, 

357 Hartree * g_max), end='') 

358 log(flush=True) 

359 

360 

361def g_max_norm(g_k, wfs): 

362 # get maximum of gradients 

363 n_kps = wfs.kd.nibzkpts 

364 

365 max_norm = [] 

366 for kpt in wfs.kpt_u: 

367 k = n_kps * kpt.s + kpt.q 

368 dim = g_k[k].shape[0] 

369 if dim == 0: 

370 max_norm.append(0.0) 

371 else: 

372 max_norm.append(np.max(np.absolute(g_k[k]))) 

373 max_norm = np.max(np.asarray(max_norm)) 

374 g_max = wfs.world.max_scalar(max_norm) 

375 

376 return g_max