Coverage for gpaw/lcao/tools.py: 40%

425 statements  

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

1from time import localtime 

2import pickle 

3 

4import numpy as np 

5from ase.units import Ha 

6from ase.calculators.singlepoint import SinglePointCalculator 

7from ase.utils import IOContext 

8 

9from gpaw.utilities import pack_density 

10from gpaw.utilities.tools import tri2full 

11from gpaw.utilities.blas import rk, mmm, mmmx 

12from gpaw.basis_data import Basis 

13from gpaw.setup import types2atomtypes 

14from gpaw.coulomb import CoulombNEW as Coulomb 

15from gpaw.mpi import world, rank, serial_comm 

16from gpaw import GPAW 

17 

18 

19def get_bf_centers(atoms, basis=None): 

20 calc = atoms.calc 

21 if calc is None or isinstance(calc, SinglePointCalculator): 

22 symbols = atoms.get_chemical_symbols() 

23 basis_a = types2atomtypes(symbols, basis, 'dzp') 

24 nao_a = [Basis.find(symbol, type).nao 

25 for symbol, type in zip(symbols, basis_a)] 

26 else: 

27 if not calc.initialized: 

28 calc.initialize(atoms) 

29 nao_a = [calc.wfs.setups[a].nao for a in range(len(atoms))] 

30 pos_ic = [] 

31 for pos, nao in zip(atoms.get_positions(), nao_a): 

32 pos_ic.extend(pos[None].repeat(nao, 0)) 

33 return np.array(pos_ic) 

34 

35 

36def get_bfi(calc, a_list): 

37 """basis function indices from a list of atom indices. 

38 a_list: atom indices 

39 Use: get_bfi(calc, [0, 4]) gives the functions indices 

40 corresponding to atom 0 and 4""" 

41 bfs_list = [] 

42 for a in a_list: 

43 M = calc.wfs.basis_functions.M_a[a] 

44 bfs_list += range(M, M + calc.wfs.setups[a].nao) 

45 return bfs_list 

46 

47 

48def get_mulliken(calc, a_list): 

49 """Mulliken charges from a list of atom indices (a_list).""" 

50 Q_a = {} 

51 for a in a_list: 

52 Q_a[a] = 0.0 

53 for kpt in calc.wfs.kpt_u: 

54 S_MM = calc.wfs.S_qMM[kpt.q] 

55 nao = S_MM.shape[0] 

56 rho_MM = np.empty((nao, nao), calc.wfs.dtype) 

57 calc.wfs.calculate_density_matrix(kpt.f_n, kpt.C_nM, rho_MM) 

58 Q_M = np.dot(rho_MM, S_MM).diagonal() 

59 for a in a_list: 

60 M1 = calc.wfs.basis_functions.M_a[a] 

61 M2 = M1 + calc.wfs.setups[a].nao 

62 Q_a[a] += np.sum(Q_M[M1:M2]) 

63 return Q_a 

64 

65 

66def get_realspace_hs(h_skmm, s_kmm, bzk_kc, weight_k, 

67 R_c=(0, 0, 0), direction='x', 

68 symmetry={'enabled': False}): 

69 

70 from gpaw.symmetry import Symmetry 

71 from ase.dft.kpoints import get_monkhorst_pack_size_and_offset, \ 

72 monkhorst_pack 

73 

74 if symmetry['point_group']: 

75 raise NotImplementedError('Point group symmetry not implemented') 

76 

77 nspins, nk, nbf = h_skmm.shape[:3] 

78 dir = 'xyz'.index(direction) 

79 transverse_dirs = np.delete([0, 1, 2], [dir]) 

80 dtype = float 

81 if len(bzk_kc) > 1 or np.any(bzk_kc[0] != [0, 0, 0]): 

82 dtype = complex 

83 

84 kpts_grid, kpts_shift = get_monkhorst_pack_size_and_offset(bzk_kc) 

85 

86 # kpts in the transport direction 

87 nkpts_p = kpts_grid[dir] 

88 bzk_p_kc = monkhorst_pack((nkpts_p, 1, 1))[:, 0] + kpts_shift[dir] 

89 weight_p_k = 1. / nkpts_p 

90 

91 # kpts in the transverse directions 

92 offset = np.zeros((3,)) 

93 offset[:len(transverse_dirs)] = kpts_shift[transverse_dirs] 

94 bzk_t_kc = monkhorst_pack( 

95 tuple(kpts_grid[transverse_dirs]) + (1, )) + offset 

96 if 'time_reversal' not in symmetry: 

97 symmetry['time_reversal'] = True 

98 if symmetry['time_reversal']: 

99 # XXX a somewhat ugly hack: 

100 # By default GPAW reduces inversion sym in the z direction. The steps 

101 # below assure reduction in the transverse dirs. 

102 # For now this part seems to do the job, but it may be written 

103 # in a smarter way in the future. 

104 symmetry = Symmetry([1], np.eye(3)) 

105 symmetry.prune_symmetries_atoms(np.zeros((1, 3))) 

106 ibzk_kc, ibzweight_k = symmetry.reduce(bzk_kc)[:2] 

107 ibzk_t_kc, weights_t_k = symmetry.reduce(bzk_t_kc)[:2] 

108 ibzk_t_kc = ibzk_t_kc[:, :2] 

109 nkpts_t = len(ibzk_t_kc) 

110 else: 

111 ibzk_kc = bzk_kc.copy() 

112 ibzk_t_kc = bzk_t_kc 

113 ibzk_t_kc = ibzk_t_kc[:, :2] 

114 nkpts_t = len(bzk_t_kc) 

115 weights_t_k = [1. / nkpts_t for k in range(nkpts_t)] 

116 

117 h_skii = np.zeros((nspins, nkpts_t, nbf, nbf), dtype) 

118 if s_kmm is not None: 

119 s_kii = np.zeros((nkpts_t, nbf, nbf), dtype) 

120 

121 tol = 7 

122 for j, k_t in enumerate(ibzk_t_kc): 

123 for k_p in bzk_p_kc: 

124 k = np.zeros((3,)) 

125 k[dir] = k_p 

126 k[transverse_dirs] = k_t 

127 kpoint_list = [list(np.round(k_kc, tol)) for k_kc in ibzk_kc] 

128 if list(np.round(k, tol)) not in kpoint_list: 

129 k = -k # inversion 

130 index = kpoint_list.index(list(np.round(k, tol))) 

131 h = h_skmm[:, index].conjugate() 

132 if s_kmm is not None: 

133 s = s_kmm[index].conjugate() 

134 k = -k 

135 else: # kpoint in the ibz 

136 index = kpoint_list.index(list(np.round(k, tol))) 

137 h = h_skmm[:, index] 

138 if s_kmm is not None: 

139 s = s_kmm[index] 

140 

141 c_k = np.exp(2.j * np.pi * np.dot(k, R_c)) * weight_p_k 

142 h_skii[:, j] += c_k * h 

143 if s_kmm is not None: 

144 s_kii[j] += c_k * s 

145 

146 if s_kmm is None: 

147 return ibzk_t_kc, weights_t_k, h_skii 

148 else: 

149 return ibzk_t_kc, weights_t_k, h_skii, s_kii 

150 

151 

152def remove_pbc(atoms, h, s=None, d=0, centers_ic=None, cutoff=None): 

153 if h.ndim > 2: 

154 raise KeyError('You have to run remove_pbc for each ' 

155 'spin/kpoint separately.') 

156 L = atoms.cell[d, d] 

157 nao = len(h) 

158 dtype = h.dtype 

159 if centers_ic is None: 

160 centers_ic = get_bf_centers(atoms) # requires an attached LCAO calc 

161 ni = len(centers_ic) 

162 if nao != ni: 

163 assert nao == 2 * ni 

164 centers_ic = np.vstack((centers_ic, centers_ic)) 

165 centers_ic[ni:, d] += L 

166 if cutoff is None: 

167 cutoff = L - 1e-3 

168 elif cutoff is None: 

169 cutoff = 0.5 * L - 1e-3 

170 pos_i = centers_ic[:, d] 

171 for i in range(nao): 

172 dpos_i = abs(pos_i - pos_i[i]) 

173 mask_i = (dpos_i < cutoff).astype(dtype) 

174 h[i, :] *= mask_i 

175 h[:, i] *= mask_i 

176 if s is not None: 

177 s[i, :] *= mask_i 

178 s[:, i] *= mask_i 

179 

180 

181def dump_hamiltonian(filename, atoms, direction=None, Ef=None): 

182 h_skmm, s_kmm = get_lcao_hamiltonian(atoms.calc) 

183 if direction is not None: 

184 d = 'xyz'.index(direction) 

185 for s in range(atoms.calc.nspins): 

186 for k in range(atoms.calc.nkpts): 

187 if s == 0: 

188 remove_pbc(atoms, h_skmm[s, k], s_kmm[k], d) 

189 else: 

190 remove_pbc(atoms, h_skmm[s, k], None, d) 

191 

192 if atoms.calc.master: 

193 with open(filename, 'wb') as fd: 

194 pickle.dump((h_skmm, s_kmm), fd, 2) 

195 atoms_data = {'cell': atoms.cell, 'positions': atoms.positions, 

196 'numbers': atoms.numbers, 'pbc': atoms.pbc} 

197 

198 pickle.dump(atoms_data, fd, 2) 

199 calc_data = {'weight_k': atoms.calc.weight_k, 

200 'ibzk_kc': atoms.calc.ibzk_kc} 

201 

202 pickle.dump(calc_data, fd, 2) 

203 

204 world.barrier() 

205 

206 

207def dump_hamiltonian_parallel(filename, atoms, direction=None, Ef=None): 

208 raise DeprecationWarning( 

209 'Please use dump_hamiltonian_and_overlap() instead.') 

210 

211 

212def dump_hamiltonian_and_overlap(filename, atoms, direction=None): 

213 """ 

214 Dump the lcao representation of H and S to file(s) beginning 

215 with filename. If direction is x, y or z, the periodic boundary 

216 conditions will be removed in the specified direction. 

217 

218 Note: 

219 H and S are parallized over spin and k-points and 

220 is for now dumped into a number of pickle files. This 

221 may be changed into a dump to a single file in the future. 

222 

223 """ 

224 if direction is not None: 

225 d = 'xyz'.index(direction) 

226 

227 calc = atoms.calc 

228 wfs = calc.wfs 

229 nao = wfs.setups.nao 

230 nq = len(wfs.kpt_u) // wfs.nspins 

231 H_qMM = np.empty((wfs.nspins, nq, nao, nao), wfs.dtype) 

232 calc_data = {'k_q': {}, 

233 'skpt_qc': np.empty((nq, 3)), 

234 'weight_q': np.empty(nq)} 

235 

236 S_qMM = wfs.S_qMM 

237 

238 for kpt in wfs.kpt_u: 

239 calc_data['skpt_qc'][kpt.q] = calc.wfs.kd.ibzk_kc[kpt.k] 

240 calc_data['weight_q'][kpt.q] = calc.wfs.kd.weight_k[kpt.k] 

241 calc_data['k_q'][kpt.q] = kpt.k 

242 H_MM = wfs.eigensolver.calculate_hamiltonian_matrix(calc.hamiltonian, 

243 wfs, 

244 kpt) 

245 

246 H_qMM[kpt.s, kpt.q] = H_MM 

247 

248 tri2full(H_qMM[kpt.s, kpt.q]) 

249 if kpt.s == 0: 

250 tri2full(S_qMM[kpt.q]) 

251 if direction is not None: 

252 remove_pbc(atoms, H_qMM[kpt.s, kpt.q], S_qMM[kpt.q], d) 

253 else: 

254 if direction is not None: 

255 remove_pbc(atoms, H_qMM[kpt.s, kpt.q], None, d) 

256 

257 if wfs.gd.comm.rank == 0: 

258 with open(filename + '%i.pckl' % wfs.kd.comm.rank, 'wb') as fd: 

259 H_qMM *= Ha 

260 pickle.dump((H_qMM, S_qMM), fd, 2) 

261 pickle.dump(calc_data, fd, 2) 

262 

263 

264def get_lcao_hamiltonian(calc): 

265 """Return H_skMM, S_kMM on master, (None, None) on slaves. H is in eV.""" 

266 from gpaw.new.ase_interface import ASECalculator 

267 if not isinstance(calc, ASECalculator): 

268 return old_get_lcao_hamiltonian(calc) 

269 dft = calc.dft 

270 ibzwfs = dft.ibzwfs 

271 ham = dft.scf_loop.hamiltonian 

272 matcalc = ham.create_hamiltonian_matrix_calculator(dft.potential) 

273 nM = ham.basis.Mmax 

274 nK = len(ibzwfs.ibz) 

275 H_skMM = np.zeros((ibzwfs.nspins, nK, nM, nM), ibzwfs.dtype) 

276 S_kMM = np.zeros((nK, nM, nM), ibzwfs.dtype) 

277 for wfs in ibzwfs: 

278 H_MM = matcalc.calculate_matrix(wfs) 

279 H_skMM[wfs.spin, wfs.k] = H_MM.data * Ha 

280 S_MM = wfs.S_MM 

281 if wfs.spin == 0: 

282 S_kMM[wfs.k] = S_MM.data 

283 ibzwfs.kpt_comm.sum(H_skMM) 

284 ibzwfs.kpt_comm.sum(S_kMM) 

285 if rank == 0: 

286 return H_skMM, S_kMM 

287 return None, None 

288 

289 

290def old_get_lcao_hamiltonian(calc): 

291 if calc.wfs.S_qMM is None: 

292 calc.wfs.set_positions(calc.spos_ac) 

293 dtype = calc.wfs.dtype 

294 NM = calc.wfs.eigensolver.nao 

295 Nk = calc.wfs.kd.nibzkpts 

296 Ns = calc.wfs.nspins 

297 

298 S_kMM = np.zeros((Nk, NM, NM), dtype) 

299 H_skMM = np.zeros((Ns, Nk, NM, NM), dtype) 

300 for kpt in calc.wfs.kpt_u: 

301 H_MM = calc.wfs.eigensolver.calculate_hamiltonian_matrix( 

302 calc.hamiltonian, calc.wfs, kpt) 

303 if kpt.s == 0: 

304 S_kMM[kpt.k] = calc.wfs.S_qMM[kpt.q] 

305 tri2full(S_kMM[kpt.k]) 

306 H_skMM[kpt.s, kpt.k] = H_MM * Ha 

307 tri2full(H_skMM[kpt.s, kpt.k]) 

308 calc.wfs.kd.comm.sum(S_kMM, 0) 

309 calc.wfs.kd.comm.sum(H_skMM, 0) 

310 if rank == 0: 

311 return H_skMM, S_kMM 

312 else: 

313 return None, None 

314 

315 

316def get_lead_lcao_hamiltonian(calc, direction='x'): 

317 H_skMM, S_kMM = get_lcao_hamiltonian(calc) 

318 if rank == 0: 

319 return lead_kspace2realspace(H_skMM, S_kMM, 

320 bzk_kc=calc.wfs.kd.bzk_kc, 

321 weight_k=calc.wfs.kd.weight_k, 

322 direction=direction, 

323 symmetry=calc.parameters.symmetry) 

324 else: 

325 return None, None, None, None 

326 

327 

328def lead_kspace2realspace(h_skmm, s_kmm, bzk_kc, weight_k, direction='x', 

329 symmetry={'point_group': False}): 

330 """Convert a k-dependent Hamiltonian to tight-binding onsite and coupling. 

331 

332 For each transverse k-point: 

333 Convert k-dependent (in transport direction) Hamiltonian 

334 representing a lead to a real space tight-binding Hamiltonian 

335 of double size representing two principal layers and the coupling between. 

336 """ 

337 

338 dir = 'xyz'.index(direction) 

339 if symmetry['point_group']: 

340 raise NotImplementedError 

341 

342 R_c = [0, 0, 0] 

343 ibz_t_kc, weight_t_k, h_skii, s_kii = get_realspace_hs( 

344 h_skmm, s_kmm, bzk_kc, weight_k, R_c, direction, symmetry) 

345 

346 R_c[dir] = 1 

347 h_skij, s_kij = get_realspace_hs( 

348 h_skmm, s_kmm, bzk_kc, weight_k, R_c, direction, symmetry)[-2:] 

349 

350 nspins, nk, nbf = h_skii.shape[:-1] 

351 

352 h_skmm = np.zeros((nspins, nk, 2 * nbf, 2 * nbf), h_skii.dtype) 

353 s_kmm = np.zeros((nk, 2 * nbf, 2 * nbf), h_skii.dtype) 

354 h_skmm[:, :, :nbf, :nbf] = h_skmm[:, :, nbf:, nbf:] = h_skii 

355 h_skmm[:, :, :nbf, nbf:] = h_skij 

356 h_skmm[:, :, nbf:, :nbf] = h_skij.swapaxes(2, 3).conj() 

357 

358 s_kmm[:, :nbf, :nbf] = s_kmm[:, nbf:, nbf:] = s_kii 

359 s_kmm[:, :nbf, nbf:] = s_kij 

360 s_kmm[:, nbf:, :nbf] = s_kij.swapaxes(1, 2).conj() 

361 

362 return ibz_t_kc, weight_t_k, h_skmm, s_kmm 

363 

364 

365def zeta_pol(basis): 

366 """Get number of zeta func. and polarization func. indices in Basis.""" 

367 zeta = [] 

368 pol = [] 

369 for bf in basis.bf_j: 

370 if 'polarization' in bf.type: 

371 pol.append(2 * bf.l + 1) 

372 else: 

373 zeta.append(2 * bf.l + 1) 

374 zeta = sum(zeta) 

375 pol = sum(pol) 

376 assert zeta + pol == basis.nao 

377 return zeta, pol 

378 

379 

380def basis_subset(symbol, largebasis, smallbasis): 

381 """Title. 

382 

383 Determine which basis function indices from ``largebasis`` are also 

384 present in smallbasis. 

385 """ 

386 blarge = Basis.find(symbol, largebasis) 

387 zeta_large, pol_large = zeta_pol(blarge) 

388 

389 bsmall = Basis.find(symbol, smallbasis) 

390 zeta_small, pol_small = zeta_pol(bsmall) 

391 

392 assert zeta_small <= zeta_large 

393 assert pol_small <= pol_large 

394 

395 insmall = np.zeros(blarge.nao, bool) 

396 insmall[:zeta_small] = True 

397 insmall[zeta_large:zeta_large + pol_small] = True 

398 return insmall 

399 

400 

401def basis_subset2(symbols, largebasis='dzp', smallbasis='sz'): 

402 """Same as basis_subset, but for an entire list of atoms.""" 

403 largebasis = types2atomtypes(symbols, largebasis, default='dzp') 

404 smallbasis = types2atomtypes(symbols, smallbasis, default='sz') 

405 mask = [] 

406 for symbol, large, small in zip(symbols, largebasis, smallbasis): 

407 mask.extend(basis_subset(symbol, large, small)) 

408 return np.asarray(mask, bool) 

409 

410 

411def collect_orbitals(a_xo, coords, root=0): 

412 """Collect array distributed over orbitals to root-CPU. 

413 

414 Input matrix has last axis distributed amongst CPUs, 

415 return is None on slaves, and the collected array on root. 

416 

417 The distribution can be uneven amongst CPUs. The list coords gives the 

418 number of values for each CPU. 

419 """ 

420 a_xo = np.ascontiguousarray(a_xo) 

421 if world.size == 1: 

422 return a_xo 

423 

424 # All slaves send their piece to ``root``: 

425 # There can be several sends before the corresponding receives 

426 # are posted, so use syncronous send here 

427 if world.rank != root: 

428 world.ssend(a_xo, root, 112) 

429 return None 

430 

431 # On root, put the subdomains from the slaves into the big array 

432 # for the whole domain on root: 

433 xshape = a_xo.shape[:-1] 

434 Norb2 = sum(coords) # total number of orbital indices 

435 a_xO = np.empty(xshape + (Norb2,), a_xo.dtype) 

436 o = 0 

437 for rankx, norb in enumerate(coords): 

438 if rankx != root: 

439 tmp_xo = np.empty(xshape + (norb,), a_xo.dtype) 

440 world.receive(tmp_xo, rankx, 112) 

441 a_xO[..., o:o + norb] = tmp_xo 

442 else: 

443 a_xO[..., o:o + norb] = a_xo 

444 o += norb 

445 return a_xO 

446 

447 

448def makeU(gpwfile='grid.gpw', orbitalfile='w_wG__P_awi.pckl', 

449 rotationfile='eps_q__U_pq.pckl', tolerance=1e-5, 

450 writeoptimizedpairs=False, dppname='D_pp.pckl', S_w=None): 

451 

452 # S_w: None or diagonal of overlap matrix. In the latter case 

453 # the optimized and truncated pair orbitals are obtained from 

454 # normalized (to 1) orbitals. 

455 # 

456 # Tolerance is used for truncation of optimized pairorbitals 

457 # calc = GPAW(gpwfile, txt=None) 

458 from gpaw import GPAW 

459 from gpaw.mpi import world 

460 calc = GPAW(gpwfile, txt='pairorb.txt') # XXX 

461 gd = calc.wfs.gd 

462 setups = calc.wfs.setups 

463 myatoms = calc.density.D_asp.keys() 

464 del calc 

465 

466 # Load orbitals on master and distribute to slaves 

467 if world.rank == 0: 

468 with open(orbitalfile, 'rb') as fd: 

469 wglobal_wG, P_awi = pickle.load(fd) 

470 Nw = len(wglobal_wG) 

471 print('Estimated total (serial) mem usage: %0.3f GB' % ( 

472 np.prod(gd.N_c) * Nw**2 * 8 / 1024.**3)) 

473 else: 

474 wglobal_wG = None 

475 Nw = 0 

476 Nw = gd.comm.sum_scalar(Nw) # distribute Nw to all nodes 

477 w_wG = gd.empty(n=Nw) 

478 gd.distribute(wglobal_wG, w_wG) 

479 del wglobal_wG 

480 

481 # Make pairorbitals 

482 f_pG = gd.zeros(n=Nw**2) 

483 for p, (w1, w2) in enumerate(np.ndindex(Nw, Nw)): 

484 np.multiply(w_wG[w1], w_wG[w2], f_pG[p]) 

485 del w_wG 

486 assert f_pG.flags.contiguous 

487 # Make pairorbital overlap (lower triangle only) 

488 D_pp = np.zeros((Nw**2, Nw**2)) 

489 rk(gd.dv, f_pG, 0., D_pp) 

490 

491 # Add atomic corrections to pairorbital overlap 

492 for a in myatoms: 

493 if setups[a].type != 'ghost': 

494 P_pp = np.array([pack_density(np.outer(P_awi[a][w1], P_awi[a][w2])) 

495 for w1, w2 in np.ndindex(Nw, Nw)]) 

496 I4_pp = setups[a].four_phi_integrals() 

497 A = np.zeros((len(I4_pp), len(P_pp))) 

498 mmm(1.0, I4_pp, 'N', P_pp, 'T', 0.0, A) 

499 mmm(1.0, P_pp, 'N', A, 'N', 1.0, D_pp) 

500 # D_pp += np.dot(P_pp, np.dot(I4_pp, P_pp.T)) 

501 

502 # Summ all contributions to master 

503 gd.comm.sum(D_pp, 0) 

504 

505 if world.rank == 0: 

506 if S_w is not None: 

507 print('renormalizing pairorb overlap matrix (D_pp)') 

508 S2 = np.sqrt(S_w) 

509 for pa, (wa1, wa2) in enumerate(np.ndindex(Nw, Nw)): 

510 for pb, (wb1, wb2) in enumerate(np.ndindex(Nw, Nw)): 

511 D_pp[pa, pb] /= S2[wa1] * S2[wa2] * S2[wb1] * S2[wb2] 

512 

513 D_pp.dump(dppname) 

514 # XXX if the diagonalization below (on MASTER only) 

515 # fails, then one can always restart the stuff 

516 # below using only the stored D_pp matrix 

517 

518 # Determine eigenvalues and vectors on master only 

519 eps_q, U_pq = np.linalg.eigh(D_pp, UPLO='L') 

520 del D_pp 

521 indices = np.argsort(-eps_q.real) 

522 eps_q = np.ascontiguousarray(eps_q.real[indices]) 

523 U_pq = np.ascontiguousarray(U_pq[:, indices]) 

524 

525 # Truncate 

526 indices = eps_q > tolerance 

527 U_pq = np.ascontiguousarray(U_pq[:, indices]) 

528 eps_q = np.ascontiguousarray(eps_q[indices]) 

529 

530 # Dump to file 

531 with open(rotationfile, 'wb') as fd: 

532 pickle.dump((eps_q, U_pq), fd, 2) 

533 

534 if writeoptimizedpairs is not False: 

535 assert world.size == 1 # works in parallel if U and eps are broadcast 

536 Uisq_qp = (U_pq / np.sqrt(eps_q)).T.copy() 

537 g_qG = gd.zeros(n=len(eps_q)) 

538 mmmx(1.0, Uisq_qp, 'N', f_pG, 'N', 0.0, g_qG) 

539 g_qG = gd.collect(g_qG) 

540 if world.rank == 0: 

541 P_app = {a: np.array([pack_density(np.outer(P_wi[w1], P_wi[w2]), 

542 tolerance=1e3) 

543 for w1, w2 in np.ndindex(Nw, Nw)]) 

544 for a, P_wi in P_awi.items()} 

545 P_aqp = {a: np.dot(Uisq_qp, Px_pp) for a, Px_pp in P_app.items()} 

546 with open(writeoptimizedpairs, 'wb') as fd: 

547 pickle.dump((g_qG, P_aqp), fd, 2) 

548 

549 

550def makeV(gpwfile='grid.gpw', orbitalfile='w_wG__P_awi.pckl', 

551 rotationfile='eps_q__U_pq.pckl', coulombfile='V_qq.pckl', 

552 log='V_qq.log', fft=False): 

553 

554 with IOContext() as io: 

555 log = io.openfile(log, comm=serial_comm) 

556 _makeV(gpwfile=gpwfile, orbitalfile=orbitalfile, 

557 rotationfile=rotationfile, coulombfile=coulombfile, 

558 log=log, fft=fft) 

559 

560 

561def _makeV(gpwfile, orbitalfile, rotationfile, coulombfile, log, fft): 

562 # Extract data from files 

563 calc = GPAW(gpwfile, txt=None, communicator=serial_comm) 

564 coulomb = Coulomb(calc.wfs.gd, calc.wfs.setups, calc.spos_ac, fft) 

565 with open(orbitalfile, 'rb') as fd: 

566 w_wG, P_awi = pickle.load(fd) 

567 with open(rotationfile, 'rb') as fd: 

568 eps_q, U_pq = pickle.load(fd) 

569 del calc 

570 

571 # Make rotation matrix divided by sqrt of norm 

572 Nq = len(eps_q) 

573 Ni = len(w_wG) 

574 Uisq_iqj = (U_pq / np.sqrt(eps_q)).reshape( 

575 Ni, Ni, Nq).swapaxes(1, 2).copy() 

576 del eps_q, U_pq 

577 

578 # Determine number of opt. pairorb on each cpu 

579 Ncpu = world.size 

580 nq, R = divmod(Nq, Ncpu) 

581 nq_r = nq * np.ones(Ncpu, int) 

582 if R > 0: 

583 nq_r[-R:] += 1 

584 

585 # Determine number of opt. pairorb on this cpu 

586 nq1 = nq_r[world.rank] 

587 q1end = nq_r[:world.rank + 1].sum() 

588 q1start = q1end - nq1 

589 V_qq = np.zeros((Nq, nq1), float) 

590 

591 def make_optimized(qstart, qend): 

592 g_qG = np.zeros((qend - qstart,) + w_wG.shape[1:], float) 

593 P_aqp = {} 

594 for a, P_wi in P_awi.items(): 

595 ni = P_wi.shape[1] 

596 nii = ni * (ni + 1) // 2 

597 P_aqp[a] = np.zeros((qend - qstart, nii), float) 

598 for w1, w1_G in enumerate(w_wG): 

599 U = Uisq_iqj[w1, qstart: qend].copy() 

600 mmmx(1, U, 'N', w1_G * w_wG, 'N', 1, g_qG) 

601 for a, P_wi in P_awi.items(): 

602 P_wp = np.array([pack_density(np.outer(P_wi[w1], P_wi[w2])) 

603 for w2 in range(Ni)]) 

604 mmm(1., U, 'N', P_wp, 'N', 1.0, P_aqp[a]) 

605 return g_qG, P_aqp 

606 

607 g1_qG, P1_aqp = make_optimized(q1start, q1end) 

608 for block, nq2 in enumerate(nq_r): 

609 if block == world.rank: 

610 g2_qG, P2_aqp = g1_qG, P1_aqp 

611 q2start, q2end = q1start, q1end 

612 else: 

613 q2end = nq_r[:block + 1].sum() 

614 q2start = q2end - nq2 

615 g2_qG, P2_aqp = make_optimized(q2start, q2end) 

616 

617 for q1, q2 in np.ndindex(nq1, nq2): 

618 P1_ap = {a: P_qp[q1] for a, P_qp in P1_aqp.items()} 

619 P2_ap = {a: P_qp[q2] for a, P_qp in P2_aqp.items()} 

620 V_qq[q2 + q2start, q1] = coulomb.calculate(g1_qG[q1], g2_qG[q2], 

621 P1_ap, P2_ap) 

622 if q2 == 0 and world.rank == 0: 

623 T = localtime() 

624 log.write( 

625 'Block %i/%i is %4.1f percent done at %02i:%02i:%02i\n' % 

626 (block + 1, world.size, 

627 100.0 * q1 / nq1, T[3], T[4], T[5])) 

628 log.flush() 

629 

630 # Collect V_qq array on master node 

631 if world.rank == 0: 

632 T = localtime() 

633 log.write('Starting collect at %02i:%02i:%02i\n' % ( 

634 T[3], T[4], T[5])) 

635 log.flush() 

636 

637 V_qq = collect_orbitals(V_qq, coords=nq_r, root=0) 

638 if world.rank == 0: 

639 # V can be slightly asymmetric due to numerics 

640 V_qq = 0.5 * (V_qq + V_qq.T) 

641 V_qq.dump(coulombfile) 

642 

643 T = localtime() 

644 log.write('Finished at %02i:%02i:%02i\n' % ( 

645 T[3], T[4], T[5])) 

646 log.flush()