Coverage for gpaw/response/matrix_elements.py: 100%

204 statements  

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

1from __future__ import annotations 

2from abc import ABC, abstractmethod 

3 

4import numpy as np 

5 

6from gpaw.sphere.integrate import spherical_truncation_function_collection 

7from gpaw.kpt_descriptor import KPointDescriptor 

8 

9from gpaw.response import timer 

10from gpaw.response.kspair import KohnShamKPointPair 

11from gpaw.response.pair import phase_shifted_fft_indices 

12from gpaw.response.site_paw import calculate_site_matrix_element_correction 

13from gpaw.response.localft import calculate_LSDA_Wxc, add_LSDA_trans_fxc 

14from gpaw.response.site_data import AtomicSiteData 

15 

16 

17class MatrixElement(ABC): 

18 """Data class for transitions distributed Kohn-Sham matrix elements.""" 

19 

20 def __init__(self, tblocks): 

21 self.tblocks = tblocks 

22 self.array = self.zeros() 

23 assert self.array.shape[0] == tblocks.blocksize 

24 

25 @abstractmethod 

26 def zeros(self): 

27 """Generate matrix element array with zeros.""" 

28 

29 @property 

30 def local_array_view(self): 

31 return self.array[:self.tblocks.nlocal] 

32 

33 def get_global_array(self): 

34 """Get the global (all gathered) matrix element.""" 

35 return self.tblocks.all_gather(self.array) 

36 

37 

38class MatrixElementCalculator(ABC): 

39 r"""Abstract base class for matrix element calculators. 

40 

41 In the PAW method, Kohn-Sham matrix elements, 

42 ˰ 

43 A_(nks,n'k's') = <ψ_nks|A|ψ_n'k's'> 

44 

45 can be evaluated in the space of pseudo waves using the pseudo operator 

46 __ __ 

47 ˷ ˰ \ \ ˷ ˰ ˷ ˰ ˷ ˷ 

48 A = A + / / |p_ai>[<φ_ai|A|φ_ai'> - <φ_ai|A|φ_ai'>]<p_ai'| 

49 ‾‾ ‾‾ 

50 a i,i' 

51 

52 to which effect, 

53 ˷ ˷ ˷ 

54 A_(nks,n'k's') = <ψ_nks|A|ψ_n'k's'> 

55 

56 This is an abstract base class for calculating such matrix elements for a 

57 number of band and spin transitions t=(n,s)->(n',s') for a given k-point 

58 pair k and k + q: 

59 

60 A_kt = A_(nks,n'k+qs') 

61 """ 

62 

63 def __init__(self, gs, context): 

64 self.gs = gs 

65 self.context = context 

66 

67 @timer('Calculate matrix element') 

68 def __call__(self, kptpair: KohnShamKPointPair, *args) -> MatrixElement: 

69 r"""Calculate the matrix element for all transitions t. 

70 

71 The calculation is split into a pseudo contribution and a PAW 

72 correction: 

73 ˷ 

74 A_kt = A_kt + ΔA_kt, 

75 

76 see [PRB 103, 245110 (2021)] for additional details and references. 

77 """ 

78 matrix_element = self.create_matrix_element(kptpair.tblocks, *args) 

79 self.add_pseudo_contribution(kptpair, matrix_element) 

80 self.add_paw_correction(kptpair, matrix_element) 

81 return matrix_element 

82 

83 @abstractmethod 

84 def create_matrix_element(self, tblocks, *args) -> MatrixElement: 

85 """Return a new MatrixElement instance.""" 

86 

87 def add_pseudo_contribution(self, kptpair, matrix_element): 

88 """Add the pseudo matrix element to an output array. 

89 

90 The pseudo matrix element is evaluated on the coarse real-space grid 

91 and integrated: 

92 

93 ˷ ˷ ˰ ˷ 

94 A_kt = <ψ_nks|A|ψ_n'k+qs'> 

95 

96 / ˷ ˰ ˷ 

97 = | dr ψ_nks^*(r) A ψ_n'k+qs'(r) 

98 / 

99 """ 

100 self._add_pseudo_contribution(*self.extract_pseudo_waves(kptpair), 

101 matrix_element=matrix_element) 

102 

103 def extract_pseudo_waves(self, kptpair): 

104 """Extract the pseudo wave functions for each k-point pair transition. 

105 """ 

106 ikpt1 = kptpair.ikpt1 

107 ikpt2 = kptpair.ikpt2 

108 

109 # Map the k-points from the irreducible part of the BZ to the BZ 

110 # k-point K (up to a reciprocal lattice vector) 

111 k1_c = self.gs.ibz2bz[kptpair.K1].map_kpoint() 

112 k2_c = self.gs.ibz2bz[kptpair.K2].map_kpoint() 

113 

114 # Fourier transform the periodic part of the pseudo waves to the coarse 

115 # real-space grid and map them to the BZ k-point K (up to the same 

116 # reciprocal lattice vector as above) 

117 ut1_hR = self.get_periodic_pseudo_waves(kptpair.K1, ikpt1) 

118 ut2_hR = self.get_periodic_pseudo_waves(kptpair.K2, ikpt2) 

119 

120 # Fold out the pseudo waves to the transition index 

121 ut1_mytR = ut1_hR[ikpt1.h_myt] 

122 ut2_mytR = ut2_hR[ikpt2.h_myt] 

123 

124 return k1_c, k2_c, ut1_mytR, ut2_mytR 

125 

126 def add_paw_correction(self, kptpair, matrix_element): 

127 r"""Add the matrix element PAW correction to an output array. 

128 

129 The PAW correction is calculated using the projector overlaps of the 

130 pseudo waves: 

131 __ __ 

132 \ \ ˷ ˷ ˷ ˷ 

133 ΔA_kt = / / <ψ_nks|p_ai> ΔA_aii' <p_ai'|ψ_n'k+qs'> 

134 ‾‾ ‾‾ 

135 a i,i' 

136 

137 where the PAW correction tensor is calculated on a radial grid inside 

138 each augmentation sphere of position R_a, using the atom-centered 

139 partial waves φ_ai(r): 

140 ˰ ˷ ˰ ˷ 

141 ΔA_aii' = <φ_ai|A|φ_ai'> - <φ_ai|A|φ_ai'> 

142 

143 / ˰ 

144 = | dr [φ_ai^*(r-R_a) A φ_ai'(r-R_a) 

145 / ˷ ˰ ˷ 

146 - φ_ai^*(r-R_a) A φ_ai'(r-R_a)] 

147 """ 

148 ikpt1 = kptpair.ikpt1 

149 ikpt2 = kptpair.ikpt2 

150 

151 # Map the projections from the irreducible part of the BZ to the BZ 

152 # k-point K 

153 P1h = self.gs.ibz2bz[kptpair.K1].map_projections(ikpt1.Ph) 

154 P2h = self.gs.ibz2bz[kptpair.K2].map_projections(ikpt2.Ph) 

155 

156 # Fold out the projectors to the transition index 

157 P1_amyti = ikpt1.projectors_in_transition_index(P1h) 

158 P2_amyti = ikpt2.projectors_in_transition_index(P2h) 

159 assert P1_amyti.atom_partition.comm.size == \ 

160 P2_amyti.atom_partition.comm.size == 1, \ 

161 'We need access to the projections of all atoms' 

162 

163 self._add_paw_correction(P1_amyti, P2_amyti, matrix_element) 

164 

165 @abstractmethod 

166 def _add_pseudo_contribution(self, k1_c, k2_c, ut1_mytR, ut2_mytR, 

167 matrix_element: MatrixElement): 

168 """Add pseudo contribution based on the pseudo waves in real space.""" 

169 

170 @abstractmethod 

171 def _add_paw_correction(self, P1_amyti, P2_amyti, 

172 matrix_element: MatrixElement): 

173 """Add paw correction based on the projector overlaps.""" 

174 

175 def get_periodic_pseudo_waves(self, K, ikpt): 

176 """FFT the Kohn-Sham orbitals to real space and map them from the 

177 irreducible k-point to the k-point in question.""" 

178 ut_hR = self.gs.gd.empty(ikpt.nh, self.gs.dtype) 

179 for h, psit_G in enumerate(ikpt.psit_hG): 

180 ut_hR[h] = self.gs.ibz2bz[K].map_pseudo_wave( 

181 self.gs.global_pd.ifft(psit_G, ikpt.ik)) 

182 

183 return ut_hR 

184 

185 

186class PlaneWaveMatrixElement(MatrixElement): 

187 def __init__(self, tblocks, qpd): 

188 self.qpd = qpd 

189 super().__init__(tblocks) 

190 

191 def zeros(self): 

192 return self.qpd.zeros(self.tblocks.blocksize) 

193 

194 

195class PlaneWaveMatrixElementCalculator(MatrixElementCalculator): 

196 r"""Abstract base class for calculating plane-wave matrix elements. 

197 

198 Calculates the following plane-wave matrix element for a given local 

199 functional of the electron (spin-)density f[n](r) = f(n(r)) and k-point 

200 pair (k, k + q): 

201 

202 f_kt(G+q) = f_(nks,n'k+qs')(G+q) = <ψ_nks| e^-i(G+q)r f(r) |ψ_n'k+qs'> 

203 

204 / 

205 = | dr e^-i(G+q)r ψ_nks^*(r) ψ_n'k+qs'(r) f(r) 

206 / 

207 """ 

208 

209 def __init__(self, gs, context, 

210 rshelmax: int = -1, 

211 rshewmin: float | None = None): 

212 """Construct the PlaneWaveMatrixElementCalculator. 

213 

214 Parameters 

215 ---------- 

216 rshelmax : int 

217 The maximum index l (l < 6) to use in the expansion of f(r) into 

218 real spherical harmonics for the PAW correction. 

219 rshewmin : float or None 

220 If None, the f(r) will be fully expanded up to the chosen lmax. 

221 Given as a float (0 < rshewmin < 1), rshewmin indicates what 

222 coefficients to use in the expansion. If any (l,m) coefficient 

223 contributes with less than a fraction of rshewmin on average, it 

224 will not be included. 

225 """ 

226 super().__init__(gs, context) 

227 

228 # Expand local functional in real spherical harmonics around each atom 

229 rshe_a = [] 

230 for a, micro_setup in enumerate(self.gs.micro_setups): 

231 rshe, info_string = micro_setup.expand_function( 

232 self.add_f, lmax=rshelmax, wmin=rshewmin) 

233 self.print_rshe_info(a, info_string) 

234 rshe_a.append(rshe) 

235 self.rshe_a = rshe_a 

236 

237 # PAW correction tensor for a given q_c 

238 self._currentq_c = None 

239 self._F_aGii = None 

240 

241 @abstractmethod 

242 def add_f(gd, n_sx, f_x): 

243 """Add the local functional f(n(r)) to the f_x output array.""" 

244 

245 def print_rshe_info(self, a, info_string): 

246 """Print information about the functional expansion at atom a.""" 

247 info_string = f'RSHE of atom {a}:\n' + info_string 

248 self.context.print(info_string.replace('\n', '\n ') + '\n') 

249 

250 def initialize_paw_corrections(self, qpd): 

251 """Initialize the PAW corrections ahead of the actual calculation.""" 

252 self.get_paw_corrections(qpd) 

253 

254 def get_paw_corrections(self, qpd): 

255 """Get PAW corrections corresponding to a specific q-vector.""" 

256 if self._currentq_c is None \ 

257 or not np.allclose(qpd.q_c, self._currentq_c): 

258 with self.context.timer('Initialize PAW corrections'): 

259 self._F_aGii = self.gs.matrix_element_paw_corrections( 

260 qpd, self.rshe_a) 

261 self._currentq_c = qpd.q_c 

262 return self._F_aGii 

263 

264 @staticmethod 

265 def create_matrix_element(tblocks, qpd): 

266 return PlaneWaveMatrixElement(tblocks, qpd) 

267 

268 @timer('Calculate pseudo matrix element') 

269 def _add_pseudo_contribution(self, k1_c, k2_c, ut1_mytR, ut2_mytR, 

270 matrix_element: PlaneWaveMatrixElement): 

271 r"""Add the pseudo matrix element to the output array. 

272 

273 The pseudo matrix element is evaluated on the coarse real-space grid 

274 and FFT'ed to reciprocal space, 

275 

276 ˷ / ˷ ˷ 

277 f_kt(G+q) = | dr e^-i(G+q)r ψ_nks^*(r) ψ_n'k+qs'(r) f(r) 

278 /V0 

279 ˷ ˷ 

280 = FFT_G[e^-iqr ψ_nks^*(r) ψ_n'k+qs'(r) f(r)] 

281 

282 where the Kohn-Sham orbitals are normalized to the unit cell and the 

283 functional f[n](r+R)=f(r) is lattice periodic. 

284 """ 

285 qpd = matrix_element.qpd 

286 # G: reciprocal space 

287 f_mytG = matrix_element.local_array_view 

288 # R: real space 

289 ft_mytR = self._evaluate_pseudo_matrix_element(ut1_mytR, ut2_mytR) 

290 

291 # Get the FFT indices corresponding to the pair density Fourier 

292 # transform ˷ ˷ 

293 # FFT_G[e^(-i[k+q-k']r) u_nks^*(r) u_n'k's'(r)] 

294 # This includes a (k,k')-dependent phase, since k2_c only is required 

295 # to equal k1_c + qpd.q_c modulo a reciprocal lattice vector. 

296 Q_G = phase_shifted_fft_indices(k1_c, k2_c, qpd) 

297 

298 # Add the desired plane-wave components of the FFT'ed pseudo matrix 

299 # element to the output array 

300 for f_G, ft_R in zip(f_mytG, ft_mytR): 

301 f_G[:] += qpd.fft(ft_R, 0, Q_G) * self.gs.gd.dv 

302 

303 @timer('Evaluate pseudo matrix element') 

304 def _evaluate_pseudo_matrix_element(self, ut1_mytR, ut2_mytR): 

305 """Evaluate the pseudo matrix element in real-space.""" 

306 # Evaluate the pseudo pair density ˷ ˷ 

307 nt_mytR = ut1_mytR.conj() * ut2_mytR # u_nks^*(r) u_n'k's'(r) 

308 

309 # Evaluate the local functional f(n(r)) on the coarse real-space grid 

310 # NB: Here we assume that f(r) is sufficiently smooth to be represented 

311 # on a regular grid (unlike the wave functions). 

312 n_sR, gd = self.gs.get_all_electron_density(gridrefinement=1) 

313 f_R = gd.zeros() 

314 self.add_f(gd, n_sR, f_R) 

315 

316 return nt_mytR * f_R[np.newaxis] 

317 

318 @timer('Calculate the matrix-element PAW corrections') 

319 def _add_paw_correction(self, P1_amyti, P2_amyti, 

320 matrix_element: PlaneWaveMatrixElement): 

321 r"""Add the matrix-element PAW correction to the output array. 

322 

323 The correction is calculated from 

324 __ __ 

325 \ \ ˷ ˷ ˷ ˷ 

326 Δf_kt(G+q) = / / <ψ_nks|p_ai><p_ai'|ψ_n'k+qs'> F_aii'(G+q) 

327 ‾‾ ‾‾ 

328 a i,i' 

329 

330 where the matrix-element PAW correction tensor is given by: 

331 

332 / 

333 F_aii'(G+q) = | dr e^-i(G+q)r [φ_ai^*(r-R_a) φ_ai'(r-R_a) 

334 / ˷ ˷ 

335 - φ_ai^*(r-R_a) φ_ai'(r-R_a)] f[n](r) 

336 """ 

337 f_mytG = matrix_element.local_array_view 

338 F_aGii = self.get_paw_corrections(matrix_element.qpd) 

339 for a, F_Gii in enumerate(F_aGii): 

340 # Make outer product of the projector overlaps 

341 P1ccP2_mytii = P1_amyti[a].conj()[..., np.newaxis] \ 

342 * P2_amyti[a][:, np.newaxis] 

343 # Sum over partial wave indices and add correction to the output 

344 f_mytG[:] += np.einsum('tij, Gij -> tG', P1ccP2_mytii, F_Gii) 

345 

346 

347class NewPairDensityCalculator(PlaneWaveMatrixElementCalculator): 

348 r"""Class for calculating pair densities 

349 

350 n_kt(G+q) = n_(nks,n'k+qs')(G+q) = <ψ_nks| e^-i(G+q)r |ψ_n'k+qs'> 

351 """ 

352 

353 def __init__(self, gs, context): 

354 super().__init__(gs, context, 

355 # Expanding f(r) = 1 in real spherical harmonics only 

356 # involves l = 0 

357 rshelmax=0) 

358 

359 def add_f(self, gd, n_sx, f_x): 

360 f_x[:] += 1. 

361 

362 def print_rshe_info(self, *args): 

363 # The expansion in spherical harmonics is trivial (l = 0), so there is 

364 # no need to print anything 

365 pass 

366 

367 

368class TransversePairPotentialCalculator(PlaneWaveMatrixElementCalculator): 

369 r"""Calculator for the transverse magnetic pair potential. 

370 

371 The transverse magnetic pair potential is a plane-wave matrix element 

372 where the local functional is the transverse LDA kernel: 

373 

374 W^⟂_kt(G+q) = W^⟂_(nks,n'k+qs')(G+q) 

375 

376 = <ψ_nks| e^-i(G+q)r f_LDA^-+(r) |ψ_n'k+qs'> 

377 """ 

378 

379 def add_f(self, gd, n_sx, f_x): 

380 return add_LSDA_trans_fxc(gd, n_sx, f_x, fxc='ALDA') 

381 

382 

383class SiteMatrixElement(MatrixElement): 

384 def __init__(self, tblocks, q_c, sites): 

385 self.q_c = q_c 

386 self.sites = sites 

387 super().__init__(tblocks) 

388 

389 def zeros(self): 

390 return np.zeros( 

391 (self.tblocks.blocksize, len(self.sites), self.sites.npartitions), 

392 dtype=complex) 

393 

394 

395class SiteMatrixElementCalculator(MatrixElementCalculator): 

396 r"""Class for calculating site matrix elements. 

397 

398 The site matrix elements are defined as the expectation value of any local 

399 functional of the electron density f[n](r) = f(n(r)), evaluated on a given 

400 site a for every site partitioning p. The sites are defined in terms of 

401 smooth truncation functions Θ(r∊Ω_ap), interpolating smoothly between unity 

402 for positions inside the spherical site volume and zero outside it: 

403 

404 f^ap_kt = f^ap_(nks,n'k+qs') = <ψ_nks|Θ(r∊Ω_ap)f(r)|ψ_n'k+qs'> 

405 

406 / 

407 = | dr Θ(r∊Ω_ap) f(r) ψ_nks^*(r) ψ_n'k+qs'(r) 

408 / 

409 

410 For details, see [publication in preparation]. 

411 """ 

412 

413 def __init__(self, gs, context, sites, 

414 rshelmax: int = -1, 

415 rshewmin: float | None = None): 

416 """Construct the SiteMatrixElementCalculator. 

417 

418 Parameters 

419 ---------- 

420 rshelmax : int 

421 The maximum index l (l < 6) to use in the expansion of f(r) into 

422 real spherical harmonics for the PAW correction. 

423 rshewmin : float or None 

424 If None, the f(r) will be fully expanded up to the chosen lmax. 

425 Given as a float (0 < rshewmin < 1), rshewmin indicates what 

426 coefficients to use in the expansion. If any (l,m) coefficient 

427 contributes with less than a fraction of rshewmin on average, it 

428 will not be included. 

429 """ 

430 super().__init__(gs, context) 

431 self.sites = sites 

432 self.site_data = AtomicSiteData(self.gs, sites) 

433 

434 # Expand local functional in real spherical harmonics around each site 

435 rshe_a = [] 

436 for a, micro_setup in enumerate(self.site_data.micro_setup_a): 

437 rshe, info_string = micro_setup.expand_function( 

438 self.add_f, lmax=rshelmax, wmin=rshewmin) 

439 self.print_rshe_info(a, info_string) 

440 rshe_a.append(rshe) 

441 self.rshe_a = rshe_a 

442 

443 # PAW correction tensor 

444 self._F_apii = None 

445 

446 @abstractmethod 

447 def add_f(self, gd, n_sx, f_x): 

448 """Add the local functional f(n(r)) to the f_x output array.""" 

449 

450 def print_rshe_info(self, a, info_string): 

451 """Print information about the expansion at site a.""" 

452 A = self.sites.A_a[a] # Atomic index of site a 

453 info_string = f'RSHE of site {a} (atom {A}):\n' + info_string 

454 self.context.print(info_string.replace('\n', '\n ') + '\n') 

455 

456 def get_paw_correction_tensor(self): 

457 if self._F_apii is None: 

458 self._F_apii = self.calculate_paw_correction_tensor() 

459 return self._F_apii 

460 

461 def calculate_paw_correction_tensor(self): 

462 """Calculate the site matrix element correction tensor F_ii'^ap.""" 

463 F_apii = [] 

464 for rshe, A, rc_p, lambd_p in zip( 

465 self.rshe_a, self.sites.A_a, 

466 self.sites.rc_ap, self.site_data.lambd_ap): 

467 # Calculate the PAW correction 

468 pawdata = self.gs.pawdatasets.by_atom[A] 

469 F_apii.append(calculate_site_matrix_element_correction( 

470 pawdata, rshe, rc_p, self.site_data.drcut, lambd_p)) 

471 

472 return F_apii 

473 

474 def create_matrix_element(self, tblocks, q_c): 

475 return SiteMatrixElement(tblocks, q_c, self.sites) 

476 

477 @timer('Calculate pseudo site matrix element') 

478 def _add_pseudo_contribution(self, k1_c, k2_c, ut1_mytR, ut2_mytR, 

479 matrix_element: SiteMatrixElement): 

480 """Add the pseudo site matrix element to the output array. 

481 

482 The pseudo matrix element is evaluated on the coarse real-space grid 

483 and integrated together with the smooth truncation function, 

484 

485 ˷ / ˷ ˷ 

486 f^ap_kt = | dr Θ(r∊Ω_ap) f(r) ψ_nks^*(r) ψ_n'k+qs'(r) 

487 / 

488 

489 where the Kohn-Sham orbitals are normalized to the unit cell. 

490 """ 

491 # Construct pseudo waves with Bloch phases 

492 r_Rc = np.transpose(self.gs.ibz2bz.r_cR, # scaled grid coordinates 

493 (1, 2, 3, 0)) 

494 psit1_mytR = np.exp(2j * np.pi * r_Rc @ k1_c)[np.newaxis] * ut1_mytR 

495 psit2_mytR = np.exp(2j * np.pi * r_Rc @ k2_c)[np.newaxis] * ut2_mytR 

496 # Calculate real-space pair densities ñ_kt(r) 

497 nt_mytR = psit1_mytR.conj() * psit2_mytR 

498 # Evaluate the local functional f(n(r)) on the coarse real-space grid 

499 # NB: Here we assume that f(r) is sufficiently smooth to be represented 

500 # on a regular grid (unlike the wave functions). 

501 n_sR, gd = self.gs.get_all_electron_density(gridrefinement=1) 

502 f_R = gd.zeros() 

503 self.add_f(gd, n_sR, f_R) 

504 

505 # Set up spherical truncation function collection on the coarse 

506 # real-space grid with a KPointDescriptor including only the q-point. 

507 qd = KPointDescriptor([matrix_element.q_c]) 

508 stfc = spherical_truncation_function_collection( 

509 self.gs.gd, self.site_data.spos_ac, 

510 self.sites.rc_ap, self.site_data.drcut, self.site_data.lambd_ap, 

511 kd=qd, dtype=complex) 

512 

513 # Integrate Θ(r∊Ω_ap) f(r) ñ_kt(r) 

514 ntlocal = nt_mytR.shape[0] 

515 ft_amytp = {a: np.empty((ntlocal, self.sites.npartitions), 

516 dtype=complex) 

517 for a in range(len(self.sites))} 

518 stfc.integrate(nt_mytR * f_R[np.newaxis], ft_amytp, q=0) 

519 

520 # Add integral to output array 

521 f_mytap = matrix_element.local_array_view 

522 for a in range(len(self.sites)): 

523 f_mytap[:, a] += ft_amytp[a] 

524 

525 @timer('Calculate site matrix element PAW correction') 

526 def _add_paw_correction(self, P1_Amyti, P2_Amyti, 

527 matrix_element: SiteMatrixElement): 

528 r"""Add the site matrix element PAW correction to the output array. 

529 

530 For every site a, we only need a PAW correction for that site itself, 

531 __ 

532 \ ˷ ˷ ˷ ˷ 

533 Δf^ap_kt = / <ψ_nks|p_ai> F_apii' <p_ai'|ψ_n'k+qs'> 

534 ‾‾ 

535 i,i' 

536 

537 where F_apii' is the site matrix element correction tensor. 

538 """ 

539 f_mytap = matrix_element.local_array_view 

540 F_apii = self.get_paw_correction_tensor() 

541 for a, (A, F_pii) in enumerate(zip( 

542 self.sites.A_a, F_apii)): 

543 # Make outer product of the projector overlaps 

544 P1ccP2_mytii = P1_Amyti[A].conj()[..., np.newaxis] \ 

545 * P2_Amyti[A][:, np.newaxis] 

546 # Sum over partial wave indices and add correction to the output 

547 f_mytap[:, a] += np.einsum('tij, pij -> tp', P1ccP2_mytii, F_pii) 

548 

549 

550class SitePairDensityCalculator(SiteMatrixElementCalculator): 

551 """Class for calculating site pair densities. 

552 

553 The site pair density corresponds to a site matrix element with f(r) = 1: 

554 

555 n^ap_(nks,n'k+qs') = <ψ_nks|Θ(r∊Ω_ap)|ψ_n'k+qs'> 

556 """ 

557 

558 def __init__(self, gs, context, sites): 

559 super().__init__(gs, context, sites, 

560 # Expanding f(r) = 1 in real spherical harmonics only 

561 # involves l = 0 

562 rshelmax=0) 

563 

564 def add_f(self, gd, n_sx, f_x): 

565 f_x[:] += 1. 

566 

567 def print_rshe_info(self, *args): 

568 # The expansion in spherical harmonics is trivial (l = 0), so there is 

569 # no need to print anything 

570 pass 

571 

572 

573class SiteZeemanPairEnergyCalculator(SiteMatrixElementCalculator): 

574 """Class for calculating site Zeeman pair energies. 

575 

576 The site Zeeman pair energy is defined as the site matrix element with 

577 f(r) = - W_xc^z(r): 

578 

579 E^(Z,ap)_(nks,n'k+qs') = - <ψ_nks|Θ(r∊Ω_ap)W_xc^z(r)|ψ_n'k+qs'> 

580 """ 

581 def add_f(self, gd, n_sx, f_x): 

582 f_x[:] += - calculate_LSDA_Wxc(gd, n_sx) 

583 

584 

585class SiteSpinPairEnergyCalculator(SiteMatrixElementCalculator): 

586 """Class for calculating site spin pair energies. 

587 

588 The site spin pair energy is defined as the site matrix element with 

589 f(r) = B^xc(r) = - |W_xc^z(r)|: 

590 

591 d^(xc,ap)_(nks,n'k+qs') = - <ψ_nks|Θ(r∊Ω_ap)|W_xc^z(r)||ψ_n'k+qs'> 

592 """ 

593 def add_f(self, gd, n_sx, f_x): 

594 f_x[:] += - np.abs(calculate_LSDA_Wxc(gd, n_sx))