Coverage for gpaw/response/modelinteraction.py: 95%

105 statements  

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

1import numpy as np 

2from ase.units import Ha 

3from gpaw.mpi import world 

4from gpaw.response import ResponseContext 

5from gpaw.response.coulomb_kernels import CoulombKernel 

6from gpaw.response.screened_interaction import initialize_w_calculator 

7from gpaw.response import timer 

8from gpaw.response.pw_parallelization import Blocks1D 

9from gpaw.response.pair import KPointPairFactory 

10from gpaw.wannier90 import read_uwan 

11from gpaw.response.screened_interaction import GammaIntegrationMode 

12 

13 

14def ibz2bz_map(qd): 

15 """ Maps each k in BZ to corresponding k in IBZ. """ 

16 out_map = [[] for _ in range(qd.nibzkpts)] 

17 for iK in range(qd.nbzkpts): 

18 ik = qd.bz2ibz_k[iK] 

19 out_map[ik].append(iK) 

20 return out_map 

21 

22 

23def initialize_w_model(chi0calc, truncation=None, 

24 integrate_gamma=GammaIntegrationMode('sphere'), 

25 q0_correction=False, txt='w_model.out', 

26 eta=None, world=world, timer=None): 

27 """ Helper function to initialize ModelInteraction 

28 

29 Parameters 

30 ---------- 

31 chi0calc: Chi0Calculator 

32 truncation: str 

33 Coulomb truncation scheme. Can be either 2D, 1D, 0D or None. 

34 integrate_gamma: GammaIntegrationMode 

35 q0_correction: bool 

36 Analytic correction to the q=0 contribution applicable to 2D 

37 systems. 

38 txt: str 

39 Filename of output files. 

40 world: MPI comm 

41 timer: ResponseContext timer 

42 """ 

43 gs = chi0calc.gs 

44 wcontext = ResponseContext(txt=txt, 

45 comm=world, timer=timer) 

46 coulomb = CoulombKernel.from_gs(gs, truncation=truncation) 

47 if eta is not None: 

48 eta /= Ha 

49 wcalc = initialize_w_calculator(chi0calc, 

50 wcontext, 

51 coulomb=coulomb, 

52 xc='RPA', 

53 integrate_gamma=integrate_gamma, 

54 q0_correction=q0_correction, 

55 eta=eta) 

56 return ModelInteraction(wcalc) 

57 

58 

59class ModelInteraction: 

60 

61 def __init__(self, wcalc): 

62 """Class to compute interaction matrix in Wannier basis 

63 

64 Parameters 

65 ---------- 

66 wcalc: WCalculator 

67 """ 

68 

69 self.wcalc = wcalc 

70 self.gs = wcalc.gs 

71 self.context = self.wcalc.context 

72 self.qd = self.wcalc.qd 

73 

74 @timer('Calculate W in Wannier') 

75 def calc_in_Wannier(self, chi0calc, Uwan_mnk, bandrange, spin=0): 

76 """Calculates the screened interaction matrix in Wannier basis. 

77 NOTE: Does not work with SOC! 

78 

79 Parameters: 

80 ---------- 

81 chi0calc: Chi0Calculator 

82 Uwan_mnk: cmplx or str 

83 Wannier transformation matrix. if str name of wannier90 output 

84 m: Wannier index, n: bandindex, k: k-index 

85 bandrange: int 

86 range of bands that the wannier functions were constructed from 

87 spin: int 

88 spin index (0 or 1) 

89 

90 Documentation 

91 ------------- 

92 

93 W_n1,n2;n3,n4(R=0, w) = 

94 <w^*_{n1,R=0} w_{n2, R=0} | W(r,r',w) |w^*_{n3,R=0} w_{n4, R=0} > 

95 

96 w_{n R} = V/(2pi)^3 int_{BZ} dk e^{-kR} psi^w_{nk} 

97 psi^w_{nk} = sum_m U_nm(k) psi^{KS}_{mk} 

98 

99 In this implementation we compute W_n1,n2;n3,n4(R=0) efficiently 

100 by expressing it in terms of the reduced Wannier density matrices 

101 

102 A_{n1,n2,q} = sum_{k} rhowan^{n1,k}_{n2, k+q} 

103 

104 where the Wannier density matrix is calculated from the usual 

105 density matrix 

106 rho^{m1,k}_{m2 k+q} = < psi_{m1,k} | e^{i(q+G)r} | psi_{m2, k+q} > 

107 and the Wannier transformation matrices U_{nm}(k) as 

108 

109 rhowan^{n1, k}_{n2, k+q} = 

110 = sum_{m1, m5} U^*_{n1,m1}(k) U_{n2, m2}(k+q) rho^{m1,k}_{m2 k+q} 

111 """ 

112 

113 ibz2bz = ibz2bz_map(self.gs.kd) 

114 

115 # Create pair_factory with nblocks = 1 

116 pair_factory = KPointPairFactory(self.gs, self.context) 

117 pair_calc = pair_factory.pair_calculator() 

118 

119 if isinstance(Uwan_mnk, str): 

120 # read w90 transformation matrix from file 

121 seed = Uwan_mnk 

122 Uwan_mnk, nk, nwan, nband = read_uwan(seed, 

123 self.gs.kd, 

124 dis=False) 

125 if bandrange[1] - bandrange[0] > nband: 

126 Uwan_mnk, nk, nwan, nband = read_uwan(seed, 

127 self.gs.kd, 

128 dis=True) 

129 else: 

130 nk = Uwan_mnk.shape[2] 

131 nband = Uwan_mnk.shape[1] 

132 nwan = Uwan_mnk.shape[0] 

133 

134 assert nk == self.gs.kd.nbzkpts 

135 assert bandrange[1] - bandrange[0] == nband 

136 wd = chi0calc.wd 

137 nfreq = len(wd) 

138 

139 # Find frequency range for block distributed arrays 

140 blockdist = chi0calc.chi0_body_calc.get_blockdist() 

141 blocks1d = Blocks1D(blockdist.blockcomm, nfreq) 

142 mynfreq = blocks1d.nlocal 

143 self.intrablockcomm = blockdist.intrablockcomm 

144 

145 # Variable that will store the screened interaction in Wannier basis 

146 Wwan_wijkl = np.zeros([mynfreq, nwan, nwan, nwan, nwan], dtype=complex) 

147 

148 for iq, q_c in enumerate(self.gs.kd.ibzk_kc): 

149 self.context.print('iq = ', iq, '/', self.gs.kd.nibzkpts) 

150 # Calculate chi0 and W for IBZ k-point q 

151 self.context.print('calculating chi0...') 

152 self.context.timer.start('chi0') 

153 chi0 = chi0calc.calculate(q_c) 

154 self.context.timer.stop('chi0') 

155 qpd = chi0.qpd 

156 self.context.print('calculating W_wGG...') 

157 W_wGG = self.wcalc.calculate_W_wGG(chi0, 

158 fxc_mode='GW', 

159 only_correlation=False) 

160 pawcorr = chi0calc.chi0_body_calc.pawcorr 

161 

162 self.context.print('Projecting to localized Wannier basis...') 

163 # Loop over all equivalent k-points 

164 for iQ in ibz2bz[iq]: 

165 Q_c = self.wcalc.qd.bzk_kc[iQ] 

166 assert self.wcalc.qd.where_is_q(Q_c, 

167 self.wcalc.qd.bzk_kc) == iQ 

168 

169 A_mnG = self.get_reduced_wannier_density_matrix(spin, 

170 Q_c, 

171 iq, 

172 bandrange, 

173 pawcorr, 

174 qpd, 

175 pair_calc, 

176 pair_factory, 

177 Uwan_mnk) 

178 if self.qd.time_reversal_k[iQ]: 

179 # TR corresponds to complex conjugation 

180 A_mnG = A_mnG.conj() 

181 

182 Wwan_wijkl += np.einsum('ijk,lkm,pqm->lipjq', 

183 A_mnG.conj(), 

184 W_wGG, 

185 A_mnG, 

186 optimize='optimal') 

187 

188 # factor from three BZ summations and taking from Hartree to eV 

189 # and volume factor from matrix element in PW basis 

190 factor = Ha / self.gs.kd.nbzkpts**3 / self.gs.volume 

191 Wwan_wijkl *= factor 

192 self.context.write_timer() 

193 

194 return wd.omega_w * Ha, blocks1d.all_gather(Wwan_wijkl) 

195 

196 @timer('get_reduced_wannier_density_matrix') 

197 def get_reduced_wannier_density_matrix(self, spin, Q_c, iq, bandrange, 

198 pawcorr, qpd, pair_calc, 

199 pair_factory, Uwan_mnk): 

200 """ 

201 Returns sum_k sum_(m1,m2) U_{n1m1}* U_{n2m2} rho^{m1 k}_{m2 k-q}(G) 

202 where rho is the usual density matrix and U are wannier tranformation 

203 matrices. 

204 """ 

205 nG = qpd.ngmax 

206 nwan = Uwan_mnk.shape[0] 

207 A_mnG = np.zeros([nwan, nwan, nG], dtype=complex) 

208 

209 # Parallell sum over k-points 

210 for iK1 in self.myKrange(): 

211 kpt1 = pair_factory.get_k_point(spin, iK1, 

212 bandrange[0], 

213 bandrange[1]) 

214 iK2 = self.gs.kd.find_k_plus_q(Q_c, [kpt1.K])[0] 

215 kpt2 = pair_factory.get_k_point(spin, iK2, 

216 bandrange[0], 

217 bandrange[1]) 

218 

219 # Calculate density matrix 

220 rholoc, iqloc, sign = self.get_density_matrix(kpt1, 

221 kpt2, 

222 pawcorr, 

223 qpd, 

224 pair_calc) 

225 assert iqloc == iq 

226 

227 # Rotate to Wannier basis and sum to get reduced Wannier 

228 # density matrix A 

229 A_mnG += np.einsum('ia,jb,abG->ijG', 

230 Uwan_mnk[:, :, iK1].conj(), 

231 Uwan_mnk[:, :, iK2], 

232 rholoc) 

233 self.intrablockcomm.sum(A_mnG) 

234 return A_mnG 

235 

236 def get_density_matrix(self, kpt1, kpt2, pawcorr, qpd, pair_calc): 

237 from gpaw.response.g0w0 import QSymmetryOp, get_nmG 

238 

239 symop, iq = QSymmetryOp.get_symop_from_kpair( 

240 self.gs.kd, self.qd, kpt1, kpt2) 

241 nG = qpd.ngmax 

242 mypawcorr, I_G = symop.apply_symop_q( 

243 qpd, pawcorr, kpt1, kpt2) 

244 

245 rho_mnG = np.zeros((len(kpt1.eps_n), len(kpt2.eps_n), nG), 

246 complex) 

247 for m in range(len(rho_mnG)): 

248 rho_mnG[m] = get_nmG(kpt1, kpt2, mypawcorr, m, qpd, I_G, pair_calc) 

249 

250 return rho_mnG, iq, symop.sign 

251 

252 def myKrange(self, rank=None): 

253 if rank is None: 

254 rank = self.intrablockcomm.rank 

255 nK = self.gs.kd.nbzkpts 

256 myKsize = -(-nK // self.intrablockcomm.size) 

257 myKrange = range(rank * myKsize, 

258 min((rank + 1) * myKsize, nK)) 

259 # myKsize = len(myKrange) 

260 return myKrange