Coverage for gpaw/response/mft.py: 99%

218 statements  

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

1from __future__ import annotations 

2 

3from abc import abstractmethod 

4import warnings 

5 

6import numpy as np 

7 

8from gpaw.typing import Vector 

9from gpaw.response import ResponseGroundStateAdaptable, ResponseContextInput 

10from gpaw.response.frequencies import ComplexFrequencyDescriptor 

11from gpaw.response.chiks import (ChiKSCalculator, RealAxisWarning, 

12 get_smat_components, smat, 

13 regularize_intraband_transitions) 

14from gpaw.response.localft import LocalFTCalculator, add_LSDA_Wxc 

15from gpaw.response.site_kernels import SiteKernels 

16from gpaw.response.site_data import AtomicSites 

17from gpaw.response.pair_integrator import PairFunction, PairFunctionIntegrator 

18from gpaw.response.pair_transitions import PairTransitions 

19from gpaw.response.matrix_elements import (SitePairDensityCalculator, 

20 SiteZeemanPairEnergyCalculator, 

21 SiteSpinPairEnergyCalculator) 

22 

23from ase.units import Hartree 

24 

25 

26class IsotropicExchangeCalculator: 

27 r"""Calculator class for the Heisenberg exchange constants 

28 

29 _ 2 

30 J^ab(q) = - ‾‾ B^(xc†) K^(a†)(q) χ_KS^('+-)(q) K^b(q) B^(xc) (1) 

31 V0 

32 

33 calculated for an isotropic system in a plane wave representation using 

34 the magnetic force theorem within second order perturbation theory, see 

35 [J. Phys.: Condens. Matter 35 (2023) 105802]. 

36 

37 Entering the formula for the isotropic exchange constant at wave vector q 

38 between sublattice a and b is the unit cell volume V0, the functional 

39 derivative of the (LDA) exchange-correlation energy with respect to the 

40 magnitude of the magnetization B^(xc), the sublattice site kernels K^a(q) 

41 and K^b(q) as well as the reactive part of the static transverse magnetic 

42 susceptibility of the Kohn-Sham system χ_KS^('+-)(q). 

43 

44 NB: To achieve numerical stability of the plane-wave implementation, we 

45 use instead the following expression to calculate exchange parameters: 

46 

47 ˷ 2 

48 J^ab(q) = - ‾‾ W_xc^(z†) K^(a†)(q) χ_KS^('+-)(q) K^b(q) W_xc^z (2) 

49 V0 

50 

51 We do this since B^(xc)(r) = -|W_xc^z(r)| is nonanalytic in points of space 

52 where the spin-polarization changes sign, why it is problematic to evaluate 

53 Eq. (1) numerically within a plane-wave representation. 

54 If the site partitionings only include spin-polarization of the same sign, 

55 Eqs. (1) and (2) should yield identical exchange parameters, but for 

56 antiferromagnetically aligned sites, the coupling constants differ by a 

57 sign. 

58 

59 The site kernels encode the partitioning of real space into sites of the 

60 Heisenberg model. This is not a uniquely defined procedure, why the user 

61 has to define them externally through the SiteKernels interface.""" 

62 

63 def __init__(self, 

64 chiks_calc: ChiKSCalculator, 

65 localft_calc: LocalFTCalculator): 

66 """Construct the IsotropicExchangeCalculator object.""" 

67 # Check that chiks has the assumed properties 

68 assumed_props = dict( 

69 gammacentered=True, 

70 nblocks=1 

71 ) 

72 for key, item in assumed_props.items(): 

73 assert getattr(chiks_calc, key) == item, \ 

74 f'Expected chiks.{key} == {item}. '\ 

75 f'Got: {getattr(chiks_calc, key)}' 

76 

77 self.chiks_calc = chiks_calc 

78 self.context = chiks_calc.context 

79 

80 # Check assumed properties of the LocalFTCalculator 

81 assert localft_calc.context is self.context 

82 assert localft_calc.gs is chiks_calc.gs 

83 self.localft_calc = localft_calc 

84 

85 # W_xc^z buffer 

86 self._Wxc_G = None 

87 

88 # χ_KS^('+-) buffer 

89 self._chiksr = None 

90 

91 def __call__(self, q_c, site_kernels: SiteKernels, txt=None): 

92 """Calculate the isotropic exchange constants for a given wavevector. 

93 

94 Parameters 

95 ---------- 

96 q_c : nd.array 

97 Wave vector q in relative coordinates 

98 site_kernels : SiteKernels 

99 Site kernels instance defining the magnetic sites of the crystal 

100 txt : str 

101 Separate file to store the chiks calculation output in (optional). 

102 If not supplied, the output will be written to the standard text 

103 output location specified when initializing chiks. 

104 

105 Returns 

106 ------- 

107 J_abp : nd.array (dtype=complex) 

108 Isotropic Heisenberg exchange constants between magnetic sites a 

109 and b for all the site partitions p given by the site_kernels. 

110 """ 

111 # Get ingredients 

112 Wxc_G = self.get_Wxc() 

113 chiksr = self.get_chiksr(q_c, txt=txt) 

114 qpd, chiksr_GG = chiksr.qpd, chiksr.array[0] # array = chiksr_zGG 

115 V0 = qpd.gd.volume 

116 

117 # Allocate an array for the exchange constants 

118 nsites = site_kernels.nsites 

119 J_pab = np.empty(site_kernels.shape + (nsites,), dtype=complex) 

120 

121 # Compute exchange coupling 

122 for J_ab, K_aGG in zip(J_pab, site_kernels.calculate(qpd)): 

123 for a in range(nsites): 

124 for b in range(nsites): 

125 J = np.conj(Wxc_G) @ np.conj(K_aGG[a]).T @ chiksr_GG \ 

126 @ K_aGG[b] @ Wxc_G 

127 J_ab[a, b] = - 2. * J / V0 

128 

129 # Transpose to have the partitions index last 

130 J_abp = np.transpose(J_pab, (1, 2, 0)) 

131 

132 return J_abp * Hartree # Convert from Hartree to eV 

133 

134 def get_Wxc(self): 

135 """Get B^(xc)_G from buffer.""" 

136 if self._Wxc_G is None: # Calculate if buffer is empty 

137 self._Wxc_G = self._calculate_Wxc() 

138 

139 return self._Wxc_G 

140 

141 def _calculate_Wxc(self): 

142 """Calculate the Fourier transform W_xc^z(G).""" 

143 # Create a plane wave descriptor encoding the plane wave basis. Input 

144 # q_c is arbitrary, since we are assuming that chiks.gammacentered == 1 

145 qpd0 = self.chiks_calc.get_pw_descriptor([0., 0., 0.]) 

146 

147 return self.localft_calc(qpd0, add_LSDA_Wxc) 

148 

149 def get_chiksr(self, q_c, txt=None): 

150 """Get χ_KS^('+-)(q) from buffer.""" 

151 q_c = np.asarray(q_c) 

152 

153 # Calculate if buffer is empty or a new q-point is given 

154 if self._chiksr is None or not np.allclose(q_c, self._chiksr.q_c): 

155 self._chiksr = self._calculate_chiksr(q_c, txt=txt) 

156 

157 return self._chiksr 

158 

159 def _calculate_chiksr(self, q_c, txt=None): 

160 r"""Use the ChiKSCalculator to calculate the reactive part of the 

161 static Kohn-Sham susceptibility χ_KS^('+-)(q). 

162 

163 First, the dynamic Kohn-Sham susceptibility 

164 

165 __ __ 

166 1 \ \ f_nk↑ - f_mk+q↓ 

167 χ_KS,GG'^+-(q,ω+iη) = ‾ / / ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 

168 V ‾‾ ‾‾ ħω - (ε_mk+q↓ - ε_nk↑) + iħη 

169 k n,m 

170 x n_nk↑,mk+q↓(G+q) n_mk+q↓,nk↑(-G'-q) 

171 

172 is calculated in the static limit ω=0 and without broadening η=0. Then, 

173 the reactive part (see [PRB 103, 245110 (2021)]) is extracted: 

174 

175 1 

176 χ_KS,GG'^(+-')(q,z) = ‾ [χ_KS,GG'^+-(q,z) + χ_KS,-G'-G^-+(-q,-z*)]. 

177 2 

178 """ 

179 # Initiate new output file, if supplied 

180 if txt is not None: 

181 self.context.new_txt_and_timer(txt) 

182 

183 # Even though the Heisenberg exchange constants are difficult to 

184 # converge for metals, it does not really help to add finite broadening 

185 # of the susceptibility. Therefore, we bite the sour apple and always 

186 # evaluate the χ_KS on the real axis. 

187 zd = ComplexFrequencyDescriptor.from_array([0. + 0.j]) 

188 with warnings.catch_warnings(): 

189 warnings.simplefilter('ignore', category=RealAxisWarning) 

190 chiks = self.chiks_calc.calculate('+-', q_c, zd) 

191 if np.allclose(q_c, 0.): 

192 chiks.symmetrize_reciprocity() 

193 

194 # Take the reactive part 

195 chiksr = chiks.copy_reactive_part() 

196 

197 return chiksr 

198 

199 

200def calculate_single_particle_site_magnetization( 

201 gs: ResponseGroundStateAdaptable, 

202 sites: AtomicSites, 

203 context: ResponseContextInput = '-'): 

204 """Calculate the single-particle site magnetization. 

205 

206 Returns 

207 ------- 

208 sp_magmom_ap : np.ndarray 

209 Magnetic moment in μB of site a under partitioning p, calculated based 

210 on a single-particle sum rule. 

211 """ 

212 single_particle_calc = SingleParticleSiteMagnetizationCalculator( 

213 gs, sites, context=context) 

214 site_magnetization = single_particle_calc() 

215 return site_magnetization.array 

216 

217 

218def calculate_single_particle_site_zeeman_energy( 

219 gs: ResponseGroundStateAdaptable, 

220 sites: AtomicSites, 

221 context: ResponseContextInput = '-'): 

222 """Calculate the single-particle site Zeeman energy. 

223 

224 Returns 

225 ------- 

226 sp_EZ_ap : np.ndarray 

227 Local Zeeman energy in eV of site a under partitioning p, calculated 

228 based on a single-particle sum rule. 

229 """ 

230 single_particle_calc = SingleParticleSiteZeemanEnergyCalculator( 

231 gs, sites, context=context) 

232 site_zeeman_energy = single_particle_calc() 

233 return site_zeeman_energy.array * Hartree # Ha -> eV 

234 

235 

236def calculate_pair_site_magnetization( 

237 gs: ResponseGroundStateAdaptable, 

238 sites: AtomicSites, 

239 context: ResponseContextInput = '-', 

240 q_c=[0., 0., 0.], 

241 nbands: int | None = None, 

242 nblocks: int | str = 1): 

243 """Calculate the pair site magnetization. 

244 

245 Parameters 

246 ---------- 

247 q_c : Vector 

248 q-vector to evaluate the pair site magnetization for. 

249 nbands : int or None 

250 Number of bands to include in the band summation of the pair site 

251 magnetization. If nbands is None, it includes all bands. 

252 nblocks : int or str 

253 The workload is parallelized over k-points and band+spin transitions. 

254 The latter is divided into nblocks, integrating nprocessors / nblocks 

255 k-points at a time. 

256 

257 Returns 

258 ------- 

259 magmom_abp : np.ndarray 

260 Pair magnetization in μB of site a and b under partitioning p, 

261 calculated based on a two-particle sum rule. 

262 """ 

263 two_particle_calc = TwoParticleSiteMagnetizationCalculator( 

264 gs, sites, context=context, nbands=nbands, nblocks=nblocks) 

265 pair_site_magnetization = two_particle_calc(q_c) 

266 return pair_site_magnetization.array 

267 

268 

269def calculate_pair_site_zeeman_energy( 

270 gs: ResponseGroundStateAdaptable, 

271 sites: AtomicSites, 

272 context: ResponseContextInput = '-', 

273 q_c=[0., 0., 0.], 

274 nbands: int | None = None, 

275 nblocks: int | str = 1): 

276 """Calculate the pair site Zeeman energy. 

277 

278 Parameters 

279 ---------- 

280 q_c : Vector 

281 q-vector to evaluate the pair site Zeeman energy for. 

282 nbands : int or None 

283 Number of bands to include in the band summation of the pair site 

284 Zeeman energy. If nbands is None, it includes all bands. 

285 nblocks : int or str 

286 The workload is parallelized over k-points and band+spin transitions. 

287 The latter is divided into nblocks, integrating nprocessors / nblocks 

288 k-points at a time. 

289 

290 Returns 

291 ------- 

292 EZ_abp : np.ndarray 

293 Local pair Zeeman energy in eV of site a and b under partitioning p, 

294 calculated based on a two-particle sum rule. 

295 """ 

296 two_particle_calc = TwoParticleSiteZeemanEnergyCalculator( 

297 gs, sites, context=context, nbands=nbands, nblocks=nblocks) 

298 pair_site_zeeman_energy = two_particle_calc(q_c) 

299 return pair_site_zeeman_energy.array * Hartree # Ha -> eV 

300 

301 

302def calculate_exchange_parameters( 

303 gs: ResponseGroundStateAdaptable, 

304 sites: AtomicSites, 

305 q_c: Vector, 

306 context: ResponseContextInput = '-', 

307 nbands: int | None = None, 

308 nblocks: int | str = 1): 

309 """Calculate the Heisenberg exchange parameters. 

310 

311 Parameters 

312 ---------- 

313 q_c : Vector 

314 q-vector to evaluate the pair site Zeeman energy for. 

315 nbands : int or None 

316 Number of bands to include in the band summation of the pair site 

317 Zeeman energy. If nbands is None, it includes all bands. 

318 nblocks : int or str 

319 The workload is parallelized over k-points and band+spin transitions. 

320 The latter is divided into nblocks, integrating nprocessors / nblocks 

321 k-points at a time. 

322 

323 Returns 

324 ------- 

325 J_abp : np.ndarray 

326 Heisenberg exchange parameters in eV of sites a and b under 

327 partitioning p. 

328 """ 

329 heisenberg_calc = HeisenbergExchangeCalculator( 

330 gs, sites, context=context, nbands=nbands, nblocks=nblocks) 

331 heisenberg_exchange = heisenberg_calc(q_c) 

332 return heisenberg_exchange.array 

333 

334 

335class SiteFunction(PairFunction): 

336 r"""Data object for single-particle site functions f_a. 

337 

338 A single-particle site function is understood as any function that can be 

339 constructed as a sum over the system eigenstates 

340 __ 

341 \ a 

342 f_a = / f 

343 ‾‾ α 

344 α 

345 

346 with site dependent weights f^a_α representing some projection onto a local 

347 (atomic) site. 

348 """ 

349 def __init__(self, sites: AtomicSites): 

350 self.sites = sites 

351 super().__init__(q_c=[0., 0., 0.]) # no crystal momentum transfer 

352 

353 @property 

354 def shape(self): 

355 return self.sites.shape 

356 

357 def zeros(self): 

358 return np.zeros(self.shape, dtype=float) 

359 

360 

361class SingleParticleSiteSumRuleCalculator(PairFunctionIntegrator): 

362 r"""Calculator for single-particle site sum rules. 

363 

364 For any site matrix element f^a_(nks,n'k's') of the Kohn-Sham system, one 

365 may define a single-particle site sum rule by its weighted trace 

366 __ __ 

367 1 \ \ 

368 f_a^μ = ‾‾‾ / / σ^μ_ss f_nks f^a_(nks,nks) 

369 N_k ‾‾ ‾‾ 

370 k n,s 

371 

372 where μ∊{0,z}. 

373 """ 

374 

375 def __init__(self, gs, sites, context='-'): 

376 super().__init__(gs, context, qsymmetry=False) 

377 self.transitions = self.get_band_and_spin_transitions() 

378 # Set up calculator for the f^a matrix element 

379 self.sites = sites 

380 self.matrix_element_calc = self.create_matrix_element_calculator() 

381 

382 @abstractmethod 

383 def create_matrix_element_calculator(self): 

384 """Create the desired site matrix element calculator.""" 

385 

386 @abstractmethod 

387 def get_pauli_matrix(self): 

388 """Get the desired Pauli matrix σ^μ.""" 

389 

390 def get_band_and_spin_transitions(self): 

391 """Set up all intraband transitions (n,s)->(n,s).""" 

392 nocc2 = self.gs.nocc2 

393 n_n = list(range(nocc2)) 

394 n_t = np.array(n_n + n_n) 

395 s_t = np.array([0] * nocc2 + [1] * nocc2) 

396 return PairTransitions(n1_t=n_t, n2_t=n_t, s1_t=s_t, s2_t=s_t) 

397 

398 def __call__(self): 

399 site_function = SiteFunction(sites=self.sites) 

400 self._integrate(site_function, self.transitions) 

401 return site_function 

402 

403 def add_integrand(self, kptpair, weight, site_function): 

404 r"""Add the integrand of the outer k-point integral. 

405 

406 The integrand is given by (see gpaw.response.pair_integrator) 

407 __ 

408 \ 

409 (...)_k = V0 / σ^μ_ss f_nks f^a_(nks,nks) 

410 ‾‾ 

411 n,s 

412 """ 

413 # Calculate matrix elements 

414 site_matrix_element = self.matrix_element_calc( 

415 kptpair, site_function.q_c) 

416 assert site_matrix_element.tblocks.blockcomm.size == 1 

417 f_tap = site_matrix_element.get_global_array() 

418 

419 # Since we only use diagonal site matrix elements, corresponding 

420 # to the expectation value of the real functions Θ(r∊Ω_ap) and f(r), 

421 # f^a_(nks,nks) = <ψ_nks|Θ(r∊Ω_ap)f(r)|ψ_nks>, 

422 # the matrix elements are real 

423 assert np.allclose(f_tap.imag, 0.) 

424 f_tap = f_tap.real 

425 

426 # Calculate Pauli matrix factors and multiply the occupations 

427 sigma = self.get_pauli_matrix() 

428 sigma_t = sigma[kptpair.transitions.s1_t, kptpair.transitions.s2_t] 

429 f_t = kptpair.get_all(kptpair.ikpt1.f_myt) 

430 sigmaf_t = sigma_t * f_t 

431 

432 # Calculate and add integrand 

433 site_function.array[:] += self.gs.volume * weight * np.einsum( 

434 't, tap -> ap', sigmaf_t, f_tap) 

435 

436 

437class SingleParticleSiteMagnetizationCalculator( 

438 SingleParticleSiteSumRuleCalculator): 

439 r"""Calculator for the single-particle site magnetization sum rule. 

440 

441 The site magnetization is calculated from the site pair density: 

442 __ __ 

443 1 \ \ 

444 n_a^z = ‾‾‾ / / σ^z_ss f_nks n^a_(nks,nks) 

445 N_k ‾‾ ‾‾ 

446 k n,s 

447 """ 

448 def create_matrix_element_calculator(self): 

449 return SitePairDensityCalculator(self.gs, self.context, self.sites) 

450 

451 def get_pauli_matrix(self): 

452 return smat('z') 

453 

454 

455class SingleParticleSiteZeemanEnergyCalculator( 

456 SingleParticleSiteMagnetizationCalculator): 

457 r"""Calculator for the single-particle site Zeeman energy sum rule. 

458 __ __ 

459 1 \ \ 

460 E_a^Z = ‾‾‾ / / σ^z_ss f_nks E^(Z,a)_(nks,nks) 

461 N_k ‾‾ ‾‾ 

462 k n,s 

463 """ 

464 def create_matrix_element_calculator(self): 

465 return SiteZeemanPairEnergyCalculator( 

466 self.gs, self.context, self.sites, rshewmin=1e-8) 

467 

468 

469class SitePairFunction(PairFunction): 

470 r"""Data object for site pair functions. 

471 

472 A site pair function is understood as any function that can be written on 

473 the form of a pair function, 

474 __ 

475 \ ab 

476 pf_ab(q) = / pf δ_{q,q_{α',α}} 

477 ‾‾ αα' 

478 α,α' 

479 

480 with site-dependent pair function weights pf^(ab)_{αα'}. 

481 

482 Typically, the site pair function will be related to a more general lattice 

483 periodic pair function pf(r,r') = pf(r+R,r'+R), which can be written in 

484 terms of its lattice Fourier transform 

485 

486 V0 / 

487 pf(r,r') = ‾‾‾‾‾‾ | dq pf(r,r',q) 

488 (2π)^D / 

489 BZ 

490 where 

491 __ 

492 \ iq⋅R 

493 pf(r,r',q) = / e pf(r,r'+R) 

494 ‾‾ 

495 R 

496 

497 The site-projected lattice Fourier transform then constitutes a site pair 

498 function: 

499 

500 // 

501 pf_ab(q) = || drdr' Θ(r∊Ω_a) pf(r,r',q) Θ(r'∊Ω_b) 

502 // 

503 """ 

504 def __init__(self, q_c: Vector, sites: AtomicSites): 

505 self.sites = sites 

506 super().__init__(q_c) 

507 

508 @property 

509 def shape(self): 

510 nsites = len(self.sites) 

511 npartitions = self.sites.npartitions 

512 return nsites, nsites, npartitions 

513 

514 def zeros(self): 

515 return np.zeros(self.shape, dtype=complex) 

516 

517 

518class SitePairFunctionCalculator(PairFunctionIntegrator): 

519 r"""Calculator for site-projected pair functions. 

520 

521 In the Kohn-Sham system, site-projected pair functions are constructed 

522 straight-forwardly as a sum over Kohn-Sham eigenstate transitions, 

523 __ __ __ 

524 1 \ \ \ / 

525 pf_ab(q) = ‾‾‾ / / / | σ^μ_ss' σ^ν_s's w_(ε_nks,ε_n'k+qs') 

526 N_k ‾‾ ‾‾ ‾‾ \ \ 

527 k n,n' s,s' × f^a_(nks,n'k+qs') g^b_(n'k+qs',nks) | 

528 / 

529 

530 summing up the site-projected matrix elements f^a and f^b, weighted by 

531 Pauli-like 2x2 spin-matrices σ^μ and σ^ν and some function 

532 w_(ε_nks,ε_n'k+qs') of the Kohn-Sham eigenvalues. 

533 """ 

534 def __init__(self, 

535 gs: ResponseGroundStateAdaptable, 

536 sites: AtomicSites, 

537 context: ResponseContextInput = '-', 

538 nbands: int | None = None, 

539 nblocks: int | str = 1): 

540 """Construct the two-particle site sum rule calculator.""" 

541 super().__init__(gs, context, 

542 # Disable q-symmetry for now. To enable it, we need 

543 # to implement site pair function symmetrization. 

544 qsymmetry=False, 

545 nblocks=nblocks) 

546 self.nbands = nbands 

547 self.bandsummation = 'double' 

548 self.transitions = self.get_band_and_spin_transitions() 

549 

550 # Set up calculators for the f^a and g^b matrix elements 

551 self.sites = sites 

552 mecalc1, mecalc2 = self.create_matrix_element_calculators() 

553 self.matrix_element_calc1 = mecalc1 

554 self.matrix_element_calc2 = mecalc2 

555 

556 @abstractmethod 

557 def create_matrix_element_calculators(self): 

558 """Create the desired site matrix element calculators.""" 

559 

560 @abstractmethod 

561 def get_spincomponent(self): 

562 """Define how to rotate the spins via the spin component (μν).""" 

563 

564 @abstractmethod 

565 def calculate_eigenvalue_dependent_weights(self, kptpair): 

566 """Calculate w_(ε_nks,ε_n'k+qs') for band and spin transitions myt.""" 

567 

568 def get_band_and_spin_transitions(self): 

569 return super().get_band_and_spin_transitions( 

570 self.get_spincomponent(), 

571 nbands=self.nbands, bandsummation=self.bandsummation) 

572 

573 def __call__(self, q_c): 

574 """Calculate the site pair function for a given wave vector q_c.""" 

575 self.context.print(self.get_info_string(q_c)) 

576 site_pair_function = SitePairFunction(q_c, self.sites) 

577 self._integrate(site_pair_function, self.transitions) 

578 return site_pair_function 

579 

580 def add_integrand(self, kptpair, weight, site_pair_function): 

581 r"""Add the site pair function integrand of the outer k-point integral. 

582 

583 The integrand is given by (see gpaw.response.pair_integrator) 

584 __ __ 

585 \ \ / 

586 (...)_k = V0 / / | σ^μ_ss' σ^ν_s's w_(ε_nks,ε_n'k+qs') 

587 ‾‾ ‾‾ \ \ 

588 n,n' s,s' × f^a_(nks,n'k+qs') g^b_(n'k+qs',nks) | 

589 / 

590 

591 where V0 is the cell volume. 

592 """ 

593 # Calculate the product of site matrix elements 

594 q_c = site_pair_function.q_c 

595 matrix_element1 = self.matrix_element_calc1(kptpair, q_c) 

596 if self.matrix_element_calc2 is self.matrix_element_calc1: 

597 matrix_element2 = matrix_element1 

598 else: 

599 matrix_element2 = self.matrix_element_calc2(kptpair, q_c) 

600 f_mytap = matrix_element1.local_array_view 

601 g_mytap = matrix_element2.local_array_view 

602 fgcc_mytabp = f_mytap[:, :, np.newaxis] * g_mytap.conj()[:, np.newaxis] 

603 

604 # Sum over local transitions, weighted by the spin matrices and 

605 # eigenvalue-dependent weights 

606 scomps_myt = get_smat_components( 

607 self.get_spincomponent(), *kptpair.get_local_spin_indices()) 

608 weps_myt = self.calculate_eigenvalue_dependent_weights(kptpair) 

609 x_myt = scomps_myt * weps_myt # σ^μ_ss' σ^ν_s's w_(ε_nks,ε_n'k+qs') 

610 integrand_abp = np.einsum('t, tabp -> abp', x_myt, fgcc_mytabp) 

611 # Sum over distributed transitions 

612 kptpair.tblocks.blockcomm.sum(integrand_abp) 

613 # Add integrand to output array 

614 site_pair_function.array[:] += self.gs.volume * weight * integrand_abp 

615 

616 def get_info_string(self, q_c): 

617 """Get information about the calculation""" 

618 info_list = ['', 

619 'Calculating site pair function with:' 

620 f' q_c: [{q_c[0]}, {q_c[1]}, {q_c[2]}]', 

621 self.get_band_and_transitions_info_string( 

622 self.nbands, len(self.transitions)), 

623 '', 

624 self.get_basic_info_string()] 

625 return '\n'.join(info_list) 

626 

627 

628class TwoParticleSiteSumRuleCalculator(SitePairFunctionCalculator): 

629 r"""Calculator for two-particle site sum rules. 

630 

631 For any set of site matrix elements f^a and g^b, one may define a two- 

632 particle site sum rule, 

633 __ __ __ 

634 1 \ \ \ / 

635 ̄x_ab(q) = ‾‾‾ / / / | σ^μ_ss' σ^ν_s's (f_nks - f_n'k+qs') 

636 N_k ‾‾ ‾‾ ‾‾ \ \ 

637 k n,n' s,s' × f^a_(nks,n'k+qs') g^b_(n'k+qs',nks) | 

638 / 

639 

640 that is, with eigenvalue-dependent weights 

641 

642 w_(ε_nks,ε_n'k+qs') = f_nks - f_n'k+qs' 

643 """ 

644 @staticmethod 

645 def calculate_eigenvalue_dependent_weights(kptpair): 

646 return kptpair.ikpt1.f_myt - kptpair.ikpt2.f_myt # df_myt 

647 

648 

649class TwoParticleSiteMagnetizationCalculator(TwoParticleSiteSumRuleCalculator): 

650 r"""Calculator for the two-particle site magnetization sum rule. 

651 

652 The site magnetization can be calculated from the site pair densities via 

653 the following sum rule [publication in preparation]: 

654 __ __ 

655 1 \ \ 

656 ̄n_ab^z(q) = ‾‾‾ / / (f_nk↑ - f_mk+q↓) n^a_(nk↑,mk+q↓) n^b_(mk+q↓,nk↑) 

657 N_k ‾‾ ‾‾ 

658 k n,m 

659 

660 = δ_(a,b) n_a^z 

661 

662 This is directly related to the sum rule of the χ^(+-) spin component of 

663 the four-component susceptibility tensor. 

664 """ 

665 def create_matrix_element_calculators(self): 

666 site_pair_density_calc = SitePairDensityCalculator( 

667 self.gs, self.context, self.sites) 

668 return site_pair_density_calc, site_pair_density_calc 

669 

670 def get_spincomponent(self): 

671 return '+-' 

672 

673 

674class TwoParticleSiteZeemanEnergyCalculator( 

675 TwoParticleSiteMagnetizationCalculator): 

676 r"""Calculator for the two-particle site Zeeman energy sum rule. 

677 

678 The site Zeeman energy can be calculated from the site pair density and 

679 site Zeeman pair energy via the following sum rule [publication in 

680 preparation]: 

681 __ __ 

682 ˍ 1 \ \ / 

683 E_ab^Z(q) = ‾‾‾ / / | (f_nk↑ - f_mk+q↓) 

684 N_k ‾‾ ‾‾ \ \ 

685 k n,m × E^(Z,a)_(nk↑,mk+q↓) n^b_(mk+q↓,nk↑) | 

686 / 

687 = δ_(a,b) E_a^Z 

688 """ 

689 def create_matrix_element_calculators(self): 

690 site_zeeman_pair_energy_calc = SiteZeemanPairEnergyCalculator( 

691 self.gs, self.context, self.sites, rshewmin=1e-8) 

692 site_pair_density_calc = SitePairDensityCalculator( 

693 self.gs, self.context, self.sites) 

694 return site_zeeman_pair_energy_calc, site_pair_density_calc 

695 

696 

697class HeisenbergExchangeCalculator(SitePairFunctionCalculator): 

698 r"""Calculator for the site-projected Heisenberg exchange. 

699 

700 The Heisenberg exchange parameters can be calculated as a function of the 

701 wave vector q, by projecting the exchange field J(r,r') onto a series of 

702 magnetic sites. The site pair function which follows is given by 

703 __ __ / 

704 _ 2 \ \ | f_nk↑ - f_mk+q↓ 

705 J_ab(q) = - ‾‾‾ / / | ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 

706 N_k ‾‾ ‾‾ \ ε_nk↑ - ε_mk+q↓ \ 

707 k n,m | 

708 × d^(xc,a)_(nk↑,mk+q↓) d^(xc,b)_(mk+q↓,nk↑) | 

709 / 

710 where d^(xc,a) is the site spin pair energy, see [publication in 

711 preparation] and [J. Phys.: Condens. Matter 35 (2023) 105802]. 

712 """ 

713 def __call__(self, q_c): 

714 out = super().__call__(q_c) 

715 if np.allclose(q_c, 0.): 

716 # Symmetrize reciprocity [J^ab(q)]^*=J^ab(-q) 

717 J_abp = out.array 

718 out.array[:] = (J_abp + J_abp.conj()) / 2. 

719 out.array *= Hartree # Ha -> eV 

720 return out 

721 

722 def create_matrix_element_calculators(self): 

723 mcalc = SiteSpinPairEnergyCalculator( 

724 self.gs, self.context, self.sites, rshewmin=1e-8) 

725 return mcalc, mcalc 

726 

727 def get_spincomponent(self): 

728 return '+-' 

729 

730 @staticmethod 

731 def calculate_eigenvalue_dependent_weights(kptpair): 

732 """Calculate the eigenvalue-dependent weights. 

733 

734 Calculates 

735 

736 f_nks - f_mk+qs' f_mk+qs' - f_nks 

737 ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ = ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 

738 ε_nks - ε_mk+qs' ε_mk+qs' - ε_nks 

739 

740 weighted by a prefactor of -2. 

741 """ 

742 nom_myt = kptpair.df_myt # df = (f_n'k's' - f_nks) 

743 denom_myt = kptpair.deps_myt # dε = (ε_n'k's' - ε_nks) 

744 regularize_intraband_transitions(denom_myt, kptpair) 

745 return -2 * nom_myt / denom_myt