Coverage for gpaw/xc/mgga.py: 90%

305 statements  

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

1from math import sqrt, pi 

2 

3import numpy as np 

4from scipy.special import eval_legendre 

5 

6from gpaw.xc.gga import (add_gradient_correction, gga_vars, 

7 GGARadialExpansion, GGARadialCalculator, 

8 get_gradient_ops, stress_gga_term) 

9from gpaw.xc.lda import (calculate_paw_correction, stress_integral, 

10 stress_lda_term) 

11from gpaw.xc.functional import XCFunctional 

12from gpaw.sphere.lebedev import weight_n 

13 

14 

15class MGGA(XCFunctional): 

16 orbital_dependent = True 

17 

18 def __init__(self, kernel, stencil=2): 

19 """Meta GGA functional.""" 

20 XCFunctional.__init__(self, kernel.name, kernel.type) 

21 self.kernel = kernel 

22 self.stencil_range = stencil 

23 self.fixed_ke = False 

24 

25 def set_grid_descriptor(self, gd): 

26 self.grad_v = get_gradient_ops(gd, self.stencil_range, np) 

27 XCFunctional.set_grid_descriptor(self, gd) 

28 

29 def get_setup_name(self): 

30 return 'PBE' 

31 

32 # This method exists on GGA class as well. Try to solve this 

33 # kind of problem when refactoring MGGAs one day. 

34 def get_description(self): 

35 return ('{} with {} nearest neighbor stencil' 

36 .format(self.name, self.stencil_range)) 

37 

38 def initialize(self, density, hamiltonian, wfs): 

39 self.wfs = wfs 

40 self.tauct = density.get_pseudo_core_kinetic_energy_density_lfc() 

41 self.tauct_G = None 

42 self.dedtaut_sG = None 

43 if ((not hasattr(hamiltonian, 'xc_redistributor')) 

44 or (hamiltonian.xc_redistributor is None)): 

45 self.restrict_and_collect = hamiltonian.restrict_and_collect 

46 self.distribute_and_interpolate = \ 

47 density.distribute_and_interpolate 

48 else: 

49 def _distribute_and_interpolate(in_xR, out_xR=None): 

50 tmp_xR = density.interpolate(in_xR) 

51 if hamiltonian.xc_redistributor.enabled: 

52 out_xR = hamiltonian.xc_redistributor.distribute(tmp_xR, 

53 out_xR) 

54 elif out_xR is None: 

55 out_xR = tmp_xR 

56 else: 

57 out_xR[:] = tmp_xR 

58 return out_xR 

59 

60 def _restrict_and_collect(in_xR, out_xR=None): 

61 if hamiltonian.xc_redistributor.enabled: 

62 in_xR = hamiltonian.xc_redistributor.collect(in_xR) 

63 return hamiltonian.restrict(in_xR, out_xR) 

64 self.restrict_and_collect = _restrict_and_collect 

65 self.distribute_and_interpolate = _distribute_and_interpolate 

66 

67 def set_positions(self, spos_ac): 

68 self.tauct.set_positions(spos_ac, self.wfs.atom_partition) 

69 if self.tauct_G is None: 

70 self.tauct_G = self.wfs.gd.empty() 

71 self.tauct_G[:] = 0.0 

72 self.tauct.add(self.tauct_G) 

73 

74 def calculate_impl(self, gd, n_sg, v_sg, e_g): 

75 sigma_xg, dedsigma_xg, gradn_svg = gga_vars(gd, self.grad_v, n_sg) 

76 self.process_mgga(e_g, n_sg, v_sg, sigma_xg, dedsigma_xg) 

77 add_gradient_correction(self.grad_v, gradn_svg, sigma_xg, 

78 dedsigma_xg, v_sg) 

79 

80 def fix_kinetic_energy_density(self, taut_sG): 

81 self.fixed_ke = True 

82 self._taut_gradv_init = False 

83 self._fixed_taut_sG = taut_sG.copy() 

84 

85 def process_mgga(self, e_g, nt_sg, v_sg, sigma_xg, dedsigma_xg): 

86 if self.fixed_ke: 

87 taut_sG = self._fixed_taut_sG 

88 if not self._taut_gradv_init: 

89 self._taut_gradv_init = True 

90 # ensure initialization for calculation potential 

91 self.wfs.calculate_kinetic_energy_density() 

92 else: 

93 taut_sG = self.wfs.calculate_kinetic_energy_density() 

94 

95 if taut_sG is None: 

96 taut_sG = self.wfs.gd.zeros(len(nt_sg)) 

97 

98 if 0: # taut_sG is None: 

99 # Below code disabled because it produces garbage in at least 

100 # some cases. 

101 # 

102 # See https://gitlab.com/gpaw/gpaw/issues/124 

103 # 

104 # Initialize with von Weizsaecker kinetic energy density: 

105 nt0_sg = nt_sg.copy() 

106 nt0_sg[nt0_sg < 1e-10] = np.inf 

107 taut_sg = sigma_xg[::2] / 8 / nt0_sg 

108 nspins = self.wfs.nspins 

109 taut_sG = self.wfs.gd.empty(nspins) 

110 for taut_G, taut_g in zip(taut_sG, taut_sg): 

111 self.restrict_and_collect(taut_g, taut_G) 

112 else: 

113 taut_sg = np.empty_like(nt_sg) 

114 

115 for taut_G, taut_g in zip(taut_sG, taut_sg): 

116 taut_G += 1.0 / self.wfs.nspins * self.tauct_G 

117 self.distribute_and_interpolate(taut_G, taut_g) 

118 

119 # bad = taut_sg < tautW_sg + 1e-11 

120 # taut_sg[bad] = tautW_sg[bad] 

121 

122 # m = 12.0 

123 # taut_sg = (taut_sg**m + (tautW_sg / 2)**m)**(1 / m) 

124 

125 dedtaut_sg = np.empty_like(nt_sg) 

126 self.kernel.calculate(e_g, nt_sg, v_sg, sigma_xg, dedsigma_xg, 

127 taut_sg, dedtaut_sg) 

128 

129 self.dedtaut_sG = self.wfs.gd.empty(self.wfs.nspins) 

130 self.ekin = 0.0 

131 for s in range(self.wfs.nspins): 

132 self.restrict_and_collect(dedtaut_sg[s], self.dedtaut_sG[s]) 

133 self.ekin -= self.wfs.gd.integrate( 

134 self.dedtaut_sG[s] * (taut_sG[s] - 

135 self.tauct_G / self.wfs.nspins)) 

136 

137 def stress_mgga_term(self, taut_sg, dedtaut_sg): 

138 nspins = len(taut_sg) 

139 tau_swG = self.wfs.calculate_kinetic_energy_density_crossterms() 

140 tau_cross_g = self.gd.empty() 

141 P = 0 

142 for taut_g, dedtaut_g in zip(taut_sg, dedtaut_sg): 

143 P -= stress_integral(self.gd, taut_g, dedtaut_g) 

144 stress_vv = P * np.eye(3) 

145 for s in range(nspins): 

146 w = 0 

147 for v1 in range(3): 

148 for v2 in range(v1, 3): 

149 self.distribute_and_interpolate( 

150 tau_swG[s, w], tau_cross_g) 

151 stress_contrib = stress_integral( 

152 self.gd, tau_cross_g, dedtaut_sg[s] 

153 ) 

154 stress_vv[v1, v2] -= stress_contrib 

155 if v1 != v2: 

156 stress_vv[v2, v1] -= stress_contrib 

157 w += 1 

158 self.dedtaut_sG = self.wfs.gd.empty(self.wfs.nspins) 

159 for s in range(self.wfs.nspins): 

160 self.restrict_and_collect(dedtaut_sg[s], self.dedtaut_sG[s]) 

161 return stress_vv 

162 

163 def stress_tensor_contribution(self, n_sg, skip_sum=False): 

164 sigma_xg, dedsigma_xg, gradn_svg = gga_vars(self.gd, self.grad_v, n_sg) 

165 taut_sG = self.wfs.calculate_kinetic_energy_density() 

166 if taut_sG is None: 

167 taut_sG = self.wfs.gd.zeros(len(n_sg)) 

168 taut_sg = np.empty_like(n_sg) 

169 for taut_G, taut_g in zip(taut_sG, taut_sg): 

170 taut_G += 1.0 / self.wfs.nspins * self.tauct_G 

171 self.distribute_and_interpolate(taut_G, taut_g) 

172 

173 nspins = len(n_sg) 

174 dedtaut_sg = np.empty_like(n_sg) 

175 v_sg = self.gd.zeros(nspins) 

176 e_g = self.gd.empty() 

177 self.kernel.calculate(e_g, n_sg, v_sg, sigma_xg, dedsigma_xg, 

178 taut_sg, dedtaut_sg) 

179 

180 stress_vv = stress_lda_term(self.gd, e_g, n_sg, v_sg) 

181 stress_vv[:] += stress_gga_term(self.gd, sigma_xg, gradn_svg, 

182 dedsigma_xg) 

183 stress_vv[:] += self.stress_mgga_term(taut_sg, dedtaut_sg) 

184 

185 if not skip_sum: 

186 self.gd.comm.sum(stress_vv) 

187 return stress_vv 

188 

189 def apply_orbital_dependent_hamiltonian(self, kpt, psit_xG, 

190 Htpsit_xG, dH_asp=None): 

191 self.wfs.apply_mgga_orbital_dependent_hamiltonian( 

192 kpt, psit_xG, 

193 Htpsit_xG, dH_asp, 

194 self.dedtaut_sG[kpt.s]) 

195 

196 def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, 

197 addcoredensity=True, a=None): 

198 assert not hasattr(self, 'D_sp') 

199 self.D_sp = D_sp 

200 self.n = 0 

201 self.ae = True 

202 self.xcc = setup.xc_correction 

203 self.dEdD_sp = dEdD_sp 

204 

205 if self.xcc is not None and self.xcc.tau_npg is None: 

206 self.xcc.tau_npg, self.xcc.taut_npg = self.initialize_kinetic( 

207 self.xcc) 

208 

209 rcalc = self.create_mgga_radial_calculator() 

210 expansion = GGARadialExpansion(rcalc) 

211 # The damn thing uses too many 'self' variables to define a clean 

212 # integrator object. 

213 E = calculate_paw_correction(expansion, 

214 setup, D_sp, dEdD_sp, 

215 addcoredensity, a) 

216 del self.D_sp, self.n, self.ae, self.xcc, self.dEdD_sp 

217 return E 

218 

219 def create_mgga_radial_calculator(self): 

220 class MockKernel: 

221 def __init__(self, mgga): 

222 self.mgga = mgga 

223 

224 def calculate(self, e_g, n_sg, v_sg, sigma_xg, dedsigma_xg): 

225 self.mgga.mgga_radial(e_g, n_sg, v_sg, sigma_xg, dedsigma_xg) 

226 

227 return GGARadialCalculator(MockKernel(self)) 

228 

229 def mgga_radial(self, e_g, n_sg, v_sg, sigma_xg, dedsigma_xg): 

230 n = self.n 

231 nspins = len(n_sg) 

232 if self.ae: 

233 tau_pg = self.xcc.tau_npg[self.n] 

234 tauc_g = self.xcc.tauc_g / (sqrt(4 * pi) * nspins) 

235 sign = 1.0 

236 else: 

237 tau_pg = self.xcc.taut_npg[self.n] 

238 tauc_g = self.xcc.tauct_g / (sqrt(4 * pi) * nspins) 

239 sign = -1.0 

240 tau_sg = np.dot(self.D_sp, tau_pg) + tauc_g 

241 

242 if 0: # not self.ae: 

243 m = 12 

244 for tau_g, n_g, sigma_g in zip(tau_sg, n_sg, sigma_xg[::2]): 

245 tauw_g = sigma_g / 8 / n_g 

246 tau_g[:] = (tau_g**m + (tauw_g / 2)**m)**(1.0 / m) 

247 break 

248 

249 dedtau_sg = np.empty_like(tau_sg) 

250 self.kernel.calculate(e_g, n_sg, v_sg, sigma_xg, dedsigma_xg, 

251 tau_sg, dedtau_sg) 

252 if self.dEdD_sp is not None: 

253 self.dEdD_sp += (sign * weight_n[self.n] * 

254 np.inner(dedtau_sg * self.xcc.rgd.dv_g, tau_pg)) 

255 assert n == self.n 

256 self.n += 1 

257 if self.n == len(weight_n): 

258 self.n = 0 

259 self.ae = False 

260 

261 def add_forces(self, F_av): 

262 dF_av = self.tauct.dict(derivative=True) 

263 self.tauct.derivative(self.dedtaut_sG.sum(0), dF_av) 

264 for a, dF_v in dF_av.items(): 

265 F_av[a] += dF_v[0] / self.wfs.nspins 

266 

267 def estimate_memory(self, mem): 

268 bytecount = self.wfs.gd.bytecount() 

269 mem.subnode('MGGA arrays', (1 + self.wfs.nspins) * bytecount) 

270 

271 def initialize_kinetic(self, xcc): 

272 nii = xcc.nii 

273 nn = len(xcc.rnablaY_nLv) 

274 ng = len(xcc.phi_jg[0]) 

275 

276 tau_npg = np.zeros((nn, nii, ng)) 

277 taut_npg = np.zeros((nn, nii, ng)) 

278 create_kinetic(xcc, nn, xcc.phi_jg, tau_npg) 

279 create_kinetic(xcc, nn, xcc.phit_jg, taut_npg) 

280 return tau_npg, taut_npg 

281 

282 

283def create_kinetic(xcc, ny, phi_jg, tau_ypg): 

284 r"""Short title here. 

285 

286 kinetic expression is:: 

287 

288 __ __ 

289 tau_s = 1/2 Sum_{i1,i2} D(s,i1,i2) \/phi_i1 . \/phi_i2 +tauc_s 

290 

291 here the orbital dependent part is calculated:: 

292 

293 __ __ 

294 \/phi_i1 . \/phi_i2 = 

295 __ __ 

296 \/YL1.\/YL2 phi_j1 phi_j2 +YL1 YL2 dphi_j1 dphi_j2 

297 ------ ------ 

298 dr dr 

299 __ __ 

300 \/YL1.\/YL2 [y] = Sum_c A[L1,c,y] A[L2,c,y] / r**2 

301 

302 """ 

303 nj = len(phi_jg) 

304 dphidr_jg = np.zeros(np.shape(phi_jg)) 

305 for j in range(nj): 

306 phi_g = phi_jg[j] 

307 xcc.rgd.derivative(phi_g, dphidr_jg[j]) 

308 

309 # Second term: 

310 for y in range(ny): 

311 i1 = 0 

312 p = 0 

313 Y_L = xcc.Y_nL[y] 

314 for j1, l1, L1 in xcc.jlL: 

315 for j2, l2, L2 in xcc.jlL[i1:]: 

316 c = Y_L[L1] * Y_L[L2] 

317 temp = c * dphidr_jg[j1] * dphidr_jg[j2] 

318 tau_ypg[y, p, :] += temp 

319 p += 1 

320 i1 += 1 

321 # first term 

322 for y in range(ny): 

323 i1 = 0 

324 p = 0 

325 rnablaY_Lv = xcc.rnablaY_nLv[y, :xcc.Lmax] 

326 Ax_L = rnablaY_Lv[:, 0] 

327 Ay_L = rnablaY_Lv[:, 1] 

328 Az_L = rnablaY_Lv[:, 2] 

329 for j1, l1, L1 in xcc.jlL: 

330 for j2, l2, L2 in xcc.jlL[i1:]: 

331 temp = (Ax_L[L1] * Ax_L[L2] + Ay_L[L1] * Ay_L[L2] + 

332 Az_L[L1] * Az_L[L2]) 

333 temp *= phi_jg[j1] * phi_jg[j2] 

334 temp[1:] /= xcc.rgd.r_g[1:]**2 

335 temp[0] = temp[1] 

336 tau_ypg[y, p, :] += temp 

337 p += 1 

338 i1 += 1 

339 tau_ypg *= 0.5 

340 

341 

342class PurePython2DMGGAKernel: 

343 def __init__(self, name, pars=None): 

344 self.name = name 

345 self.pars = pars 

346 self.type = 'MGGA' 

347 assert self.pars is not None 

348 

349 def calculate(self, e_g, n_sg, dedn_sg, 

350 sigma_xg, dedsigma_xg, 

351 tau_sg, dedtau_sg): 

352 

353 e_g[:] = 0. 

354 dedsigma_xg[:] = 0. 

355 dedtau_sg[:] = 0. 

356 

357 # spin-paired: 

358 if len(n_sg) == 1: 

359 n = n_sg[0] 

360 n[n < 1e-20] = 1e-40 

361 sigma = sigma_xg[0] 

362 sigma[sigma < 1e-20] = 1e-40 

363 tau = tau_sg[0] 

364 tau[tau < 1e-20] = 1e-40 

365 

366 # exchange 

367 e_x = twodexchange(n, sigma, tau, self.pars) 

368 e_g[:] += e_x * n 

369 

370 # spin-polarized: 

371 else: 

372 n = n_sg 

373 n[n < 1e-20] = 1e-40 

374 sigma = sigma_xg 

375 sigma[sigma < 1e-20] = 1e-40 

376 tau = tau_sg 

377 tau[tau < 1e-20] = 1e-40 

378 

379 # The spin polarized version is handle using the exact spin scaling 

380 # Ex[n1, n2] = (Ex[2*n1] + Ex[2*n2])/2 

381 na = 2.0 * n[0] 

382 nb = 2.0 * n[1] 

383 

384 e2na_x = twodexchange(na, 4. * sigma[0], 2. * tau[0], self.pars) 

385 e2nb_x = twodexchange(nb, 4. * sigma[2], 2. * tau[1], self.pars) 

386 ea_x = e2na_x * na 

387 eb_x = e2nb_x * nb 

388 

389 e_g[:] += (ea_x + eb_x) / 2.0 

390 

391 

392def twodexchange(n, sigma, tau, pars): 

393 # parameters for 2 Legendre polynomials 

394 parlen_i = int(pars[0]) 

395 parlen_j = pars[2 + 2 * parlen_i] 

396 assert parlen_i == parlen_j 

397 pars_i = pars[1:2 + 2 * parlen_i] 

398 pars_j = pars[3 + 2 * parlen_i:] 

399 trans_i = pars_i[0] 

400 trans_j = pars_j[0] 

401 orders_i, coefs_i = np.split(pars_i[1:], 2) 

402 orders_j, coefs_j = np.split(pars_j[1:], 2) 

403 assert len(coefs_i) == len(orders_i) 

404 assert len(coefs_j) == len(orders_j) 

405 assert len(orders_i) == len(orders_j) 

406 

407 # product Legendre expansion of Fx(s, alpha) 

408 e_x_ueg, rs = ueg_x(n) 

409 Fx = LegendreFx2(n, rs, sigma, tau, 

410 trans_i, orders_i, coefs_i, trans_j, orders_j, coefs_j) 

411 return e_x_ueg * Fx 

412 

413 

414def LegendreFx2(n, rs, sigma, tau, 

415 trans_i, orders_i, coefs_i, trans_j, orders_j, coefs_j): 

416 # Legendre polynomial basis expansion in 2D 

417 

418 # reduced density gradient in transformation t1(s) 

419 C2 = 0.26053088059892404 

420 s2 = sigma * (C2 * np.divide(rs, n))**2. 

421 x_i = transformation(s2, trans_i) 

422 assert x_i.all() >= -1.0 and x_i.all() <= 1.0 

423 

424 # kinetic energy density parameter alpha in transformation t2(s) 

425 alpha = get_alpha(n, sigma, tau) 

426 x_j = transformation(alpha, trans_j) 

427 assert x_j.all() >= -1.0 and x_j.all() <= 1.0 

428 

429 # product exchange enhancement factor 

430 Fx_i = legendre_polynomial(x_i, orders_i, coefs_i) 

431 # print(Fx_i);asdf 

432 Fx_j = legendre_polynomial(x_j, orders_j, coefs_j) 

433 Fx = Fx_i * Fx_j 

434 return Fx 

435 

436 

437def transformation(x, t): 

438 if t > 0: 

439 tmp = t + x 

440 x = 2.0 * np.divide(x, tmp) - 1.0 

441 elif int(t) == -1: 

442 tmp1 = (1.0 - x**2.0)**3.0 

443 tmp2 = (1.0 + x**3.0 + x**6.0) 

444 x = -1.0 * np.divide(tmp1, tmp2) 

445 else: 

446 raise KeyError('transformation %i unknown!' % t) 

447 return x 

448 

449 

450def get_alpha(n, sigma, tau): 

451 # tau LSDA 

452 aux = (3. / 10.) * (3.0 * np.pi * np.pi)**(2. / 3.) 

453 tau_lsda = aux * n**(5. / 3.) 

454 

455 # von Weisaecker 

456 ind = (n != 0.).nonzero() 

457 gdms = np.maximum(sigma, 1e-40) # |nabla rho|^2 

458 tau_w = np.zeros(np.shape(n)) 

459 tau_w[ind] = np.maximum(np.divide(gdms[ind], 8.0 * n[ind]), 1e-40) 

460 

461 # z and alpha 

462 tau_ = np.maximum(tau_w, tau) 

463 alpha = np.divide(tau_ - tau_w, tau_lsda) 

464 assert alpha.all() >= 0.0 

465 return alpha 

466 

467 

468def ueg_x(n): 

469 C0I = 0.238732414637843 

470 C1 = -0.45816529328314287 

471 rs = (C0I / n)**(1 / 3.) 

472 ex = C1 / rs 

473 return ex, rs 

474 

475 

476def legendre_polynomial(x, orders, coefs): 

477 assert len(orders) == len(coefs) == 1 

478 return eval_legendre(orders[0], x) * coefs[0]