Coverage for gpaw/lcao/el_ph.py: 8%

239 statements  

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

1import pickle 

2import numpy as np 

3from os.path import isfile 

4 

5from ase.parallel import parprint 

6from ase.units import Bohr, Ha 

7 

8from gpaw.mpi import world 

9from gpaw.utilities import unpack_hermitian 

10from gpaw.lcao.projected_wannier import dots 

11from gpaw.utilities.tools import tri2full 

12from gpaw.lfc import LocalizedFunctionsCollection as LFC 

13 

14 

15"""This module is used to calculate the electron-phonon coupling matrix, 

16 expressed in terms of GPAW LCAO orbitals. 

17 

18 This module is not maintained and possibly broken. 

19 Use gpaw/elph/* instead. 

20 """ 

21 

22 

23class ElectronPhononCouplingMatrix: 

24 r"""Class for calculating the electron-phonon coupling matrix, defined 

25 by the electron phonon interaction. 

26 

27 :: 

28 

29 __ _____ 

30 \ l cc / h cc 

31 H = ) M c c /------ ( b + b ), 

32 el-ph /_ ij i j \/ 2 W l l 

33 l,ij l 

34 

35 where the electron phonon coupling matrix is given by:: 

36 

37 l ___ 

38 M = < i | \ / V * v |j> 

39 ij 'u eff l 

40 

41 """ 

42 

43 def __init__(self, atoms, indices=None, name='v', delta=0.005, nfree=2, 

44 derivativemethod='tci'): 

45 assert nfree in [2, 4] 

46 self.nfree = nfree 

47 self.delta = delta 

48 

49 if indices is None: 

50 indices = range(len(self.atoms)) 

51 self.calc = atoms.calc 

52 self.atoms = atoms 

53 self.indices = np.asarray(indices) 

54 self.name = name 

55 self.p0 = self.atoms.positions.copy() 

56 

57 self.derivativemethod = derivativemethod 

58 if derivativemethod == 'grid': 

59 self.get_dP_aMix = get_grid_dP_aMix 

60 elif derivativemethod == 'grid2': 

61 self.get_dP_aMix = get_grid2_dP_aMix 

62 elif derivativemethod == 'tci': 

63 self.get_dP_aMix = get_tci_dP_aMix 

64 else: 

65 raise ValueError('derivativemethod must be grid, grid2, or tci') 

66 

67 def run(self): 

68 if not isfile(self.name + '.eq.pckl'): 

69 

70 self.calc.calculate(self.atoms) 

71 Vt_G = self.calc.get_effective_potential(pad=False) 

72 Vt_G = self.calc.wfs.gd.collect(Vt_G, broadcast=True) / Ha 

73 dH_asp = self.calc.hamiltonian.dH_asp 

74 setups = self.calc.wfs.setups 

75 nspins = self.calc.wfs.nspins 

76 gd_comm = self.calc.wfs.gd.comm 

77 

78 alldH_asp = {} 

79 for a, setup in enumerate(setups): 

80 ni = setup.ni 

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

82 tmpdH_sp = np.zeros((nspins, nii)) 

83 if a in dH_asp: 

84 tmpdH_sp[:] = dH_asp[a] 

85 gd_comm.sum(tmpdH_sp) 

86 alldH_asp[a] = tmpdH_sp 

87 

88 forces = self.atoms.get_forces() 

89 self.calc.write('eq.gpw') 

90 

91 world.barrier() 

92 if world.rank == 0: 

93 vd = open(self.name + '.eq.pckl', 'wb') 

94 fd = open('vib.eq.pckl', 'wb') 

95 pickle.dump((Vt_G, alldH_asp), vd, 2) 

96 pickle.dump(forces, fd) 

97 vd.close() 

98 fd.close() 

99 world.barrier() 

100 

101 p = self.atoms.positions.copy() 

102 for a in self.indices: 

103 for j in range(3): 

104 for sign in [-1, 1]: 

105 for ndis in range(1, self.nfree / 2 + 1): 

106 name = '.%d%s%s.pckl' % (a, 'xyz'[j], 

107 ndis * ' +-'[sign]) 

108 if isfile(self.name + name): 

109 continue 

110 self.atoms.positions[a, j] = (p[a, j] + 

111 sign * ndis * self.delta) 

112 self.calc.calculate(self.atoms) 

113 Vt_G = self.calc.get_effective_potential(pad=False) 

114 Vt_G = self.calc.wfs.gd.collect(Vt_G, 

115 broadcast=True) / Ha 

116 dH_asp = self.calc.hamiltonian.dH_asp 

117 

118 alldH_asp = {} 

119 for a2, setup in enumerate(setups): 

120 ni = setup.ni 

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

122 tmpdH_sp = np.zeros((nspins, nii)) 

123 if a2 in dH_asp: 

124 tmpdH_sp[:] = dH_asp[a2] 

125 gd_comm.sum(tmpdH_sp) 

126 alldH_asp[a2] = tmpdH_sp 

127 

128 forces = self.atoms.get_forces() 

129 world.barrier() 

130 if world.rank == 0: 

131 vd = open(self.name + name, 'wb') 

132 fd = open('vib' + name, 'wb') 

133 pickle.dump((Vt_G, alldH_asp), vd) 

134 pickle.dump(forces, fd) 

135 vd.close() 

136 fd.close() 

137 world.barrier() 

138 self.atoms.positions[a, j] = p[a, j] 

139 self.atoms.set_positions(p) 

140 

141 def get_gradient(self): 

142 """Calculates gradient""" 

143 nx = len(self.indices) * 3 

144 veqt_G, dHeq_asp = pickle.load(open(self.name + '.eq.pckl', 'rb')) 

145 gpts = veqt_G.shape 

146 dvt_Gx = np.zeros(gpts + (nx, )) 

147 ddH_aspx = {} 

148 for a, dH_sp in dHeq_asp.items(): 

149 ddH_aspx[a] = np.empty(dH_sp.shape + (nx,)) 

150 

151 x = 0 

152 for a in self.indices: 

153 for i in range(3): 

154 name = '%s.%d%s' % (self.name, a, 'xyz'[i]) 

155 vtm_G, dHm_asp = pickle.load(open(name + '-.pckl', 'rb')) 

156 vtp_G, dHp_asp = pickle.load(open(name + '+.pckl', 'rb')) 

157 

158 if self.nfree == 4: 

159 vtmm_G, dHmm_asp = pickle.load( 

160 open(name + '--.pckl', 'rb')) 

161 vtpp_G, dHpp_asp = pickle.load( 

162 open(name + '++.pckl', 'rb')) 

163 dvtdx_G = (-vtpp_G + 8.0 * vtp_G - 

164 8 * vtm_G + vtmm_G) / (12 * self.delta / Bohr) 

165 dvt_Gx[:, :, :, x] = dvtdx_G 

166 for atom, ddH_spx in ddH_aspx.items(): 

167 ddH_aspx[atom][:, :, x] = ( 

168 -dHpp_asp[atom] 

169 + 8.0 * dHp_asp[atom] 

170 - 8.0 * dHm_asp[atom] 

171 + dHmm_asp[atom]) / (12 * self.delta / Bohr) 

172 else: # nfree = 2 

173 dvtdx_G = (vtp_G - vtm_G) / (2 * self.delta / Bohr) 

174 dvt_Gx[..., x] = dvtdx_G 

175 for atom, ddH_spx in ddH_aspx.items(): 

176 ddH_aspx[atom][:, :, x] = ( 

177 dHp_asp[atom] 

178 - dHm_asp[atom]) / (2 * self.delta / Bohr) 

179 x += 1 

180 return dvt_Gx, ddH_aspx 

181 

182 def get_M(self, modes, q=0): 

183 r"""Calculate el-ph coupling matrix for given modes(s). 

184 

185 XXX: 

186 kwarg "q=0" is an ugly hack for k-points. 

187 There shuold be loops over q! 

188 

189 Note that modes must be given as a dictionary with mode 

190 frequencies in eV and corresponding mode vectors in units 

191 of 1/sqrt(amu), where amu = 1.6605402e-27 Kg is an atomic mass unit. 

192 In short frequencies and mode vectors must be given in ase units. 

193 

194 :: 

195 

196 d d ~ 

197 < w | -- v | w' > = < w | -- v | w'> 

198 dP dP 

199 

200 _ 

201 \ ~a d . ~a 

202 + ) < w | p > -- /_\H < p | w' > 

203 /_ i dP ij j 

204 a,ij 

205 

206 _ 

207 \ d ~a . ~a 

208 + ) < w | -- p > /_\H < p | w' > 

209 /_ dP i ij j 

210 a,ij 

211 

212 _ 

213 \ ~a . d ~a 

214 + ) < w | p > /_\H < -- p | w' > 

215 /_ i ij dP j 

216 a,ij 

217 

218 """ 

219 

220 modes1 = modes.copy() 

221 # convert to atomic units 

222 amu = 1.6605402e-27 # atomic unit mass [Kg] 

223 me = 9.1093897e-31 # electron mass [Kg] 

224 modes = {} 

225 for k in modes1.keys(): 

226 modes[k / Ha] = modes1[k] / np.sqrt(amu / me) 

227 

228 dvt_Gx, ddH_aspx = self.get_gradient() 

229 

230 from gpaw import restart 

231 atoms, calc = restart('eq.gpw', txt=None) 

232 if calc.wfs.S_qMM is None: 

233 calc.initialize(atoms) 

234 calc.initialize_positions(atoms) 

235 

236 wfs = calc.wfs 

237 nao = wfs.setups.nao 

238 bfs = wfs.basis_functions 

239 dtype = wfs.dtype 

240 spin = 0 # XXX 

241 

242 M_lii = {} 

243 parprint('Starting gradient of pseudo part') 

244 for f, mode in modes.items(): 

245 mo = [] 

246 M_ii = np.zeros((nao, nao), dtype) 

247 for a in self.indices: 

248 mo.append(mode[a]) 

249 mode = np.asarray(mo).flatten() 

250 dvtdP_G = np.dot(dvt_Gx, mode) 

251 bfs.calculate_potential_matrix(dvtdP_G, M_ii, q=q) 

252 tri2full(M_ii, 'L') 

253 M_lii[f] = M_ii 

254 parprint('Finished gradient of pseudo part') 

255 

256 P_aqMi = calc.wfs.P_aqMi 

257 # Add the term 

258 # _ 

259 # \ ~a d . ~a 

260 # ) < w | p > -- /_\H < p | w' > 

261 # /_ i dP ij j 

262 # a,ij 

263 

264 Ma_lii = {} 

265 for f, mode in modes.items(): 

266 Ma_lii[f] = np.zeros_like(M_lii.values()[0]) 

267 

268 parprint('Starting gradient of dH^a part') 

269 for f, mode in modes.items(): 

270 mo = [] 

271 for a in self.indices: 

272 mo.append(mode[a]) 

273 mode = np.asarray(mo).flatten() 

274 

275 for a, ddH_spx in ddH_aspx.items(): 

276 ddHdP_sp = np.dot(ddH_spx, mode) 

277 ddHdP_ii = unpack_hermitian(ddHdP_sp[spin]) 

278 Ma_lii[f] += dots(P_aqMi[a][q], ddHdP_ii, P_aqMi[a][q].T) 

279 parprint('Finished gradient of dH^a part') 

280 

281 parprint('Starting gradient of projectors part') 

282 dP_aMix = self.get_dP_aMix(calc.spos_ac, wfs, q) 

283 parprint('Finished gradient of projectors part') 

284 

285 dH_asp = pickle.load(open('v.eq.pckl', 'rb'))[1] 

286 

287 Mb_lii = {} 

288 for f, mode in modes.items(): 

289 Mb_lii[f] = np.zeros_like(M_lii.values()[0]) 

290 

291 for f, mode in modes.items(): 

292 for a, dP_Mix in dP_aMix.items(): 

293 dPdP_Mi = np.dot(dP_Mix, mode[a]) 

294 dH_ii = unpack_hermitian(dH_asp[a][spin]) 

295 dPdP_MM = dots(dPdP_Mi, dH_ii, P_aqMi[a][q].T) 

296 Mb_lii[f] -= dPdP_MM + dPdP_MM.T 

297 # XXX The minus sign here is quite subtle. 

298 # It is related to how the derivative of projector 

299 # functions in GPAW is calculated. 

300 # More thorough explanations, anyone...? 

301 

302 # Units of M_lii are Hartree/(Bohr * sqrt(m_e)) 

303 for mode in M_lii.keys(): 

304 M_lii[mode] += Ma_lii[mode] + Mb_lii[mode] 

305 

306 # conversion to eV. The prefactor 1 / sqrt(hb^2 / 2 * hb * f) 

307 # has units Bohr * sqrt(me) 

308 M_lii_1 = M_lii.copy() 

309 M_lii = {} 

310 

311 for f in M_lii_1.keys(): 

312 M_lii[f * Ha] = M_lii_1[f] * Ha / np.sqrt(2 * f) 

313 

314 return M_lii 

315 

316 

317##################################################### 

318# XXX grid and grid 2 sometimes gives random numbers, 

319# XXX sometimes even nan! 

320##################################################### 

321 

322def get_grid_dP_aMix(spos_ac, wfs, q): # XXXXXX q 

323 nao = wfs.setups.nao 

324 C_MM = np.identity(nao, dtype=wfs.dtype) 

325 # XXX In the future use the New Two-Center integrals 

326 # to evaluate this 

327 dP_aMix = {} 

328 for a, setup in enumerate(wfs.setups): 

329 ni = 0 

330 dP_Mix = np.zeros((nao, setup.ni, 3)) 

331 pt = LFC(wfs.gd, [setup.pt_j], 

332 wfs.kd.comm, dtype=wfs.dtype, forces=True) 

333 spos1_ac = [spos_ac[a]] 

334 pt.set_k_points(wfs.ibzk_qc) 

335 pt.set_positions(spos1_ac) 

336 for b, setup_b in enumerate(wfs.setups): 

337 nao = setup_b.nao 

338 phi_MG = wfs.gd.zeros(nao, wfs.dtype) 

339 phi_MG = wfs.gd.collect(phi_MG, broadcast=False) 

340 wfs.basis_functions.lcao_to_grid(C_MM[ni:ni + nao], phi_MG, q) 

341 dP_bMix = pt.dict(len(phi_MG), derivative=True) 

342 pt.derivative(phi_MG, dP_bMix, q=q) 

343 dP_Mix[ni:ni + nao] = dP_bMix[0] 

344 ni += nao 

345 parprint(f'projector grad. doing atoms ({a}, {b}) ') 

346 

347 dP_aMix[a] = dP_Mix 

348 return dP_aMix 

349 

350 

351def get_grid2_dP_aMix(spos_ac, wfs, q, *args, **kwargs): # XXXXXX q 

352 nao = wfs.setups.nao 

353 C_MM = np.identity(nao, dtype=wfs.dtype) 

354 bfs = wfs.basis_functions 

355 phi_MG = wfs.gd.zeros(nao, wfs.dtype) 

356 bfs.lcao_to_grid(C_MM, phi_MG, q) 

357 setups = wfs.setups 

358 pt = LFC(wfs.gd, [setup.pt_j for setup in setups], 

359 wfs.kd.comm, dtype=wfs.dtype, forces=True) 

360 pt.set_k_points(wfs.ibzk_qc) 

361 pt.set_positions(spos_ac) 

362 dP_aMix = pt.dict(len(phi_MG), derivative=True) 

363 pt.derivative(phi_MG, dP_aMix, q=q) 

364 return dP_aMix 

365 

366 

367def get_tci_dP_aMix(spos_ac, wfs, q, *args, **kwargs): 

368 # container for spline expansions of basis function-projector pairs 

369 # (note to self: remember to conjugate/negate because of that) 

370 from gpaw.lcao.overlap import ManySiteDictionaryWrapper, \ 

371 TwoCenterIntegralCalculator, NewTwoCenterIntegrals 

372 

373 if not isinstance(wfs.tci, NewTwoCenterIntegrals): 

374 raise RuntimeError('Please remember --gpaw=usenewtci=True') 

375 

376 dP_aqxMi = {} 

377 nao = wfs.setups.nao 

378 nq = len(wfs.ibzk_qc) 

379 for a, setup in enumerate(wfs.setups): 

380 dP_aqxMi[a] = np.zeros((nq, 3, nao, setup.ni), wfs.dtype) 

381 

382 calc = TwoCenterIntegralCalculator(wfs.ibzk_qc, derivative=True) 

383 expansions = ManySiteDictionaryWrapper(wfs.tci.P_expansions, dP_aqxMi) 

384 calc.calculate(wfs.tci.atompairs, [expansions], [dP_aqxMi]) 

385 

386 dP_aMix = {} 

387 for a in dP_aqxMi: 

388 dP_aMix[a] = dP_aqxMi[a].transpose(0, 2, 3, 1).copy()[q] # XXX q 

389 return dP_aMix