Coverage for gpaw/test/response/test_site_kernels.py: 100%

270 statements  

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

1"""Test the site kernel calculation functionality of the response code""" 

2 

3# General modules 

4import pytest 

5import numpy as np 

6import scipy.special as sc 

7 

8# Script modules 

9from ase.build import bulk 

10 

11from gpaw import GPAW, PW 

12from gpaw.response.site_kernels import (SphericalSiteKernels, 

13 CylindricalSiteKernels, 

14 ParallelepipedicSiteKernels, 

15 sinc, 

16 spherical_geometry_factor, 

17 cylindrical_geometry_factor, 

18 parallelepipedic_geometry_factor) 

19from gpaw.response.pair_functions import get_pw_coordinates 

20 

21 

22# ---------- Actual tests ---------- # 

23 

24 

25pytestmark = pytest.mark.kspair 

26 

27 

28@pytest.mark.ci 

29def test_spherical_kernel(rng): 

30 """Check the numerics of the spherical kernel""" 

31 # ---------- Inputs ---------- # 

32 

33 # Relative wave vector lengths to check (relative to 1/rc) 

34 Qrel_Q = np.array([0., 

35 np.pi / 2, 

36 np.pi]) 

37 

38 # Expected results (assuming |Q| * rc = 1.) 

39 Vsphere = 4. * np.pi / 3. 

40 test_K_Q = Vsphere * np.array([1., 

41 3 / (np.pi / 2)**3., 

42 3 / (np.pi)**2.]) 

43 

44 # Spherical radii to check 

45 nr = 5 

46 rc_r = rng.random(nr) 

47 

48 # Wave vector directions to check 

49 nd = 41 

50 Q_dv = 2. * rng.random((nd, 3)) - 1. 

51 Q_dv /= np.linalg.norm(Q_dv, axis=1)[:, np.newaxis] # normalize 

52 

53 # ---------- Script ---------- # 

54 

55 # Set up wave vectors 

56 Q_Qdv = Qrel_Q[:, np.newaxis, np.newaxis] * Q_dv[np.newaxis, ...] 

57 

58 for rc in rc_r: 

59 # Calculate site centered geometry factor with rescaled wave vector 

60 K_Qd = spherical_geometry_factor(Q_Qdv / rc, rc) 

61 # Check against expected result 

62 assert np.allclose(K_Qd, rc**3. * test_K_Q[:, np.newaxis]) 

63 

64 

65@pytest.mark.ci 

66def test_cylindrical_kernel(rng): 

67 """Check the numerics of the spherical kernel""" 

68 # ---------- Inputs ---------- # 

69 

70 # Relative wave vectors (relative to 1/rc) in radial direction 

71 Qrhorel_Q1 = np.array([0.] + list(sc.jn_zeros(1, 4))) # Roots of J1(x) 

72 

73 # Relative wave vectors (relative to 2/hc) in cylindrical direction 

74 Qzrel_Q2 = list(np.pi * np.arange(5)) # Roots of sin(x) 

75 Qzrel_Q2 += list(np.pi * np.arange(4) + np.pi / 2) # Extrema of sin(x) 

76 Qzrel_Q2 = np.array(Qzrel_Q2) 

77 

78 # Expected results for roots of J1 (assuming rc=1. and hc=2.) 

79 Vcylinder = 2. * np.pi 

80 nQ2 = 13 # Choose random Q_z r_z 

81 test_Krho_Q1 = np.array([1., 0., 0., 0., 0.]) 

82 Qzrand_Q2 = 10. * rng.random(nQ2) 

83 sinc_zrand_Q2 = np.sin(Qzrand_Q2) / Qzrand_Q2 

84 test_Krho_Q1Q2 = Vcylinder * test_Krho_Q1[:, np.newaxis]\ 

85 * sinc_zrand_Q2[np.newaxis, :] 

86 

87 # Expected results for roots and extrema of sin (assuming rc=1. and hc=2.) 

88 nQ1 = 15 # Choose random Q_ρ h_c 

89 test_Kz_Q2 = [1., 0., 0., 0., 0.] # Nodes in sinc(Q_z h_c) 

90 test_Kz_Q2 += list(np.array([1., -1., 1., -1.]) / Qzrel_Q2[5:]) # Extrema 

91 test_Kz_Q2 = np.array(test_Kz_Q2) 

92 Qrhorand_Q1 = 10. * rng.random(nQ1) 

93 J1term_rhorand_Q1 = 2. * sc.jv(1, Qrhorand_Q1) / Qrhorand_Q1 

94 test_Kz_Q1Q2 = Vcylinder * J1term_rhorand_Q1[:, np.newaxis]\ 

95 * test_Kz_Q2[np.newaxis, :] 

96 

97 # Cylinder radii to check 

98 nr = 5 

99 rc_r = 3. * rng.random(nr) 

100 

101 # Cylinder height to check 

102 nh = 3 

103 hc_h = 4. * rng.random(nh) 

104 

105 # Cylindrical axes to check 

106 nc = 7 

107 ez_cv = 2. * rng.random((nc, 3)) - 1. 

108 ez_cv /= np.linalg.norm(ez_cv, axis=1)[:, np.newaxis] 

109 

110 # Wave vector directions in-plane to check. Generated through the cross 

111 # product of a random direction with the cylindrical axis 

112 nd = 11 

113 Qrho_dv = 2. * rng.random((nd, 3)) - 1. 

114 Qrho_cdv = np.cross(Qrho_dv[np.newaxis, ...], ez_cv[:, np.newaxis, :]) 

115 Qrho_cdv /= np.linalg.norm(Qrho_cdv, axis=-1)[..., np.newaxis] # normalize 

116 

117 # ---------- Script ---------- # 

118 

119 for rc in rc_r: 

120 for hc in hc_h: 

121 # Set up wave vectors for radial tests 

122 Qrho_cdQ1v = Qrhorel_Q1[np.newaxis, np.newaxis, :, np.newaxis]\ 

123 * Qrho_cdv[..., np.newaxis, :] / rc 

124 Qrho_cQ2v = Qzrand_Q2[np.newaxis, :, np.newaxis]\ 

125 * ez_cv[:, np.newaxis, :] / (hc / 2.) 

126 Qrho_cdQ1Q2v = Qrho_cdQ1v[..., np.newaxis, :]\ 

127 + Qrho_cQ2v[:, np.newaxis, np.newaxis, ...] 

128 

129 # Set up wave vectors for cylindrical tests 

130 Qz_cdQ1v = Qrhorand_Q1[np.newaxis, np.newaxis, :, np.newaxis]\ 

131 * Qrho_cdv[..., np.newaxis, :] / rc 

132 Qz_cQ2v = Qzrel_Q2[np.newaxis, :, np.newaxis]\ 

133 * ez_cv[:, np.newaxis, :] / (hc / 2.) 

134 Qz_cdQ1Q2v = Qz_cdQ1v[..., np.newaxis, :]\ 

135 + Qz_cQ2v[:, np.newaxis, np.newaxis, ...] 

136 

137 # Test one cylindrical direction at a time 

138 for ez_v, Qrho_dQ1Q2v, Qz_dQ1Q2v in zip(ez_cv, 

139 Qrho_cdQ1Q2v, Qz_cdQ1Q2v): 

140 # Calculate geometry factors 

141 Krho_dQ1Q2 = cylindrical_geometry_factor(Qrho_dQ1Q2v, 

142 ez_v, rc, hc) 

143 Kz_dQ1Q2 = cylindrical_geometry_factor(Qz_dQ1Q2v, 

144 ez_v, rc, hc) 

145 

146 # Check against expected result 

147 assert np.allclose(Krho_dQ1Q2, rc**2. * hc / 2. 

148 * test_Krho_Q1Q2[np.newaxis, ...], 

149 atol=1.e-8) 

150 assert np.allclose(Kz_dQ1Q2, rc**2. * hc / 2. 

151 * test_Kz_Q1Q2[np.newaxis, ...], 

152 atol=1.e-8) 

153 

154 

155@pytest.mark.ci 

156def test_parallelepipedic_kernel(rng): 

157 """Check the numerics of the parallelepipedic site kernel.""" 

158 # ---------- Inputs ---------- # 

159 

160 # Relative wave vectors to check and corresponding sinc(x/2) 

161 Qrel_Q = np.pi * np.arange(5) 

162 sinchalf_Q = np.array([1., 2. / np.pi, 0., - 2. / (3. * np.pi), 0.]) 

163 

164 # Random parallelepipedic cell vectors to check 

165 nC = 9 

166 cell_Ccv = 2. * rng.random((nC, 3, 3)) - 1. 

167 volume_C = np.abs(np.linalg.det(cell_Ccv)) 

168 # Normalize the cell volume 

169 cell_Ccv /= (volume_C**(1 / 3))[:, np.newaxis, np.newaxis] 

170 

171 # Transverse wave vector components to check. Generated through the cross 

172 # product of a random direction with the first cell axis. 

173 v0_Cv = cell_Ccv[:, 0, :].copy() 

174 v0_C = np.linalg.norm(v0_Cv, axis=-1) # Length of primary vector 

175 v0n_Cv = v0_Cv / v0_C[:, np.newaxis] # Normalize 

176 nd = 11 

177 Q_dv = 2. * rng.random((nd, 3)) - 1. 

178 Q_dv[0, :] = np.array([0., 0., 0.]) # Check also parallel Q-vector 

179 Q_Cdv = np.cross(Q_dv[np.newaxis, ...], v0_Cv[:, np.newaxis, :]) 

180 

181 # Volumes to test 

182 nV = 7 

183 Vparlp_V = 10. * rng.random(nV) 

184 

185 # ---------- Script ---------- # 

186 

187 # Rescale cell 

188 cell_CVcv = cell_Ccv[:, np.newaxis, ...]\ 

189 * (Vparlp_V**(1 / 3.))[np.newaxis, :, np.newaxis, np.newaxis] 

190 

191 # Rescale primary vector to let Q.a follow Qrel 

192 Qrel_CQ = Qrel_Q[np.newaxis, :] / v0_C[:, np.newaxis] 

193 Qrel_CVQ = Qrel_CQ[:, np.newaxis, :]\ 

194 / (Vparlp_V**(1 / 3.))[np.newaxis, :, np.newaxis] 

195 # Generate Q-vectors 

196 Q_CVdQv = Qrel_CVQ[..., np.newaxis, :, np.newaxis]\ 

197 * v0n_Cv[:, np.newaxis, np.newaxis, np.newaxis, :]\ 

198 + Q_Cdv[:, np.newaxis, :, np.newaxis, :] 

199 

200 # Generate test values 

201 sinchalf_CVdQ = sinc(np.sum(cell_CVcv[..., np.newaxis, np.newaxis, 1, :] 

202 * Q_CVdQv, axis=-1) / 2)\ 

203 * sinc(np.sum(cell_CVcv[..., np.newaxis, np.newaxis, 2, :] 

204 * Q_CVdQv, axis=-1) / 2) 

205 test_Theta_CVdQ = Vparlp_V[np.newaxis, :, np.newaxis, np.newaxis]\ 

206 * sinchalf_Q[np.newaxis, np.newaxis, np.newaxis, :]\ 

207 * sinchalf_CVdQ 

208 

209 for Q_VdQv, test_Theta_VdQ, cell_Vcv in zip(Q_CVdQv, test_Theta_CVdQ, 

210 cell_CVcv): 

211 for Q_dQv, test_Theta_dQ, cell_cv in zip(Q_VdQv, test_Theta_VdQ, 

212 cell_Vcv): 

213 for _ in range(3): # Check that primary axis can be anywhere 

214 # Slide the cell axis indices 

215 cell_cv[:, :] = cell_cv[[2, 0, 1], :] 

216 # Calculate geometry factors 

217 Theta_dQ = parallelepipedic_geometry_factor(Q_dQv, cell_cv) 

218 

219 # Check against expected results 

220 assert np.allclose(Theta_dQ, test_Theta_dQ, atol=1.e-8) 

221 

222 

223@pytest.mark.ci 

224def test_Co_hcp_site_kernels(): 

225 """Check that the site kernel interface works on run-time inputs.""" 

226 # ---------- Inputs ---------- # 

227 

228 # Part 1: Generate plane wave representation (PWDescriptor) 

229 # Atomic configuration 

230 a = 2.5071 

231 c = 4.0695 

232 mm = 1.6 

233 # Ground state settings 

234 xc = 'LDA' 

235 kpts = 4 

236 pw = 200 

237 # Response settings 

238 ecut = 50. 

239 gammacentered = False 

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

241 qpm_qc = [[0., 0., 1 / 4.], 

242 [0., 0., -1. / 4.]] 

243 

244 # Part 2: Calculate site kernels 

245 # Define partitions to try 

246 rc_pa = np.array([[1., 2.], [2., 3.]]) # radii in Å 

247 hc_pa = np.array([[2., 3.], [3., 4.]]) # heights in Å 

248 ez_pav = np.array([[[0., 0., 1.], [1., 0., 0.]], 

249 [[0., 0., 1.], [0., 0., 1.]]]) 

250 cell_acv = np.array([[[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]], 

251 [[1., 0., 0.], [0., 1., 0.], [0., 0., 2.]]]) 

252 cell_pacv = np.append(cell_acv, cell_acv * 2).reshape(2, 2, 3, 3) 

253 

254 # Part 3: Check the calculated kernels 

255 

256 # ---------- Script ---------- # 

257 

258 # Part 1: Generate plane wave representation (PWDescriptor) 

259 atoms = bulk('Co', 'hcp', a=a, c=c) 

260 atoms.set_initial_magnetic_moments([mm, mm]) 

261 

262 calc = GPAW(xc=xc, 

263 spinpol=True, 

264 mode=PW(pw), 

265 kpts={'size': (kpts, kpts, kpts), 

266 'gamma': True} 

267 ) 

268 

269 # Perform inexpensive calculator initialization 

270 calc.initialize(atoms) 

271 

272 qpd0 = get_pw_descriptor(atoms, calc, q_c, 

273 ecut=ecut, 

274 gammacentered=gammacentered) 

275 

276 # Part 2: Calculate site kernels 

277 positions = atoms.get_positions() 

278 

279 # Generate spherical site kernels instances 

280 # Normally 

281 sph_sitekernels = SphericalSiteKernels(positions, rc_pa) 

282 # Separately as sum of site kernels 

283 sph_sitekernels0 = SphericalSiteKernels(positions[:1], rc_pa[:, :1]) 

284 sph_sitekernels1 = SphericalSiteKernels(positions[1:], rc_pa[:, 1:]) 

285 sph_sitekernels_sum = sph_sitekernels0 + sph_sitekernels1 

286 # Seperately as appended partitions 

287 sph_sitekernelsp0 = SphericalSiteKernels(positions, rc_pa[:1, :]) 

288 sph_sitekernelsp1 = SphericalSiteKernels(positions, rc_pa[1:, :]) 

289 sph_sitekernels_app = sph_sitekernelsp0.copy() 

290 sph_sitekernels_app.append(sph_sitekernelsp1) 

291 

292 # Generate cylindrical site kernels instances 

293 # Normally 

294 cyl_sitekernels = CylindricalSiteKernels(positions, ez_pav, rc_pa, hc_pa) 

295 # Separately as a sum of site kernels 

296 cyl_sitekernels0 = CylindricalSiteKernels(positions[:1], ez_pav[:, :1, :], 

297 rc_pa[:, :1], hc_pa[:, :1]) 

298 cyl_sitekernels1 = CylindricalSiteKernels(positions[1:], ez_pav[:, 1:, :], 

299 rc_pa[:, 1:], hc_pa[:, 1:]) 

300 cyl_sitekernels_sum = cyl_sitekernels0 + cyl_sitekernels1 

301 # Seperately as appended partitions 

302 cyl_sitekernelsp0 = CylindricalSiteKernels(positions, ez_pav[:1, :, :], 

303 rc_pa[:1, :], hc_pa[:1, :]) 

304 cyl_sitekernelsp1 = CylindricalSiteKernels(positions, ez_pav[1:, :, :], 

305 rc_pa[1:, :], hc_pa[1:, :]) 

306 cyl_sitekernels_app = cyl_sitekernelsp0.copy() 

307 cyl_sitekernels_app.append(cyl_sitekernelsp1) 

308 

309 # Generate parallelepipedic site kernels instances 

310 # Normally 

311 parlp_sitekernels = ParallelepipedicSiteKernels(positions, cell_pacv) 

312 # Separately as sum of site kernels 

313 parlp_sitekernels0 = ParallelepipedicSiteKernels(positions[:1], 

314 cell_pacv[:, :1, ...]) 

315 parlp_sitekernels1 = ParallelepipedicSiteKernels(positions[1:], 

316 cell_pacv[:, 1:, ...]) 

317 parlp_sitekernels_sum = parlp_sitekernels0 + parlp_sitekernels1 

318 # Seperately as appended partitions 

319 parlp_sitekernelsp0 = ParallelepipedicSiteKernels(positions, 

320 cell_pacv[:1, :, ...]) 

321 parlp_sitekernelsp1 = ParallelepipedicSiteKernels(positions, 

322 cell_pacv[1:, :, ...]) 

323 parlp_sitekernels_app = parlp_sitekernelsp0.copy() 

324 parlp_sitekernels_app.append(parlp_sitekernelsp1) 

325 

326 # Collect all unique site kernels as both a sum and as different partitions 

327 # to show, that we can compute them in parallel 

328 all_sitekernels_sum = sph_sitekernels + cyl_sitekernels + parlp_sitekernels 

329 all_sitekernels_app = sph_sitekernels.copy() 

330 all_sitekernels_app.append(cyl_sitekernels) 

331 all_sitekernels_app.append(parlp_sitekernels) 

332 

333 # Calculate spherical site kernels 

334 Ksph_paGG = np.array([K_aGG for K_aGG in 

335 sph_sitekernels.calculate(qpd0)]) 

336 Ksph0_paGG = np.array([K_aGG for K_aGG in 

337 sph_sitekernels0.calculate(qpd0)]) 

338 Ksph1_paGG = np.array([K_aGG for K_aGG in 

339 sph_sitekernels1.calculate(qpd0)]) 

340 Ksph_sum_paGG = np.array([K_aGG for K_aGG in 

341 sph_sitekernels_sum.calculate(qpd0)]) 

342 Ksphp0_paGG = np.array([K_aGG for K_aGG in 

343 sph_sitekernelsp0.calculate(qpd0)]) 

344 Ksphp1_paGG = np.array([K_aGG for K_aGG in 

345 sph_sitekernelsp1.calculate(qpd0)]) 

346 Ksph_app_paGG = np.array([K_aGG for K_aGG in 

347 sph_sitekernels_app.calculate(qpd0)]) 

348 

349 # Calculate cylindrical site kernels 

350 Kcyl_paGG = np.array([K_aGG for K_aGG in 

351 cyl_sitekernels.calculate(qpd0)]) 

352 Kcyl0_paGG = np.array([K_aGG for K_aGG in 

353 cyl_sitekernels0.calculate(qpd0)]) 

354 Kcyl1_paGG = np.array([K_aGG for K_aGG in 

355 cyl_sitekernels1.calculate(qpd0)]) 

356 Kcyl_sum_paGG = np.array([K_aGG for K_aGG in 

357 cyl_sitekernels_sum.calculate(qpd0)]) 

358 Kcylp0_paGG = np.array([K_aGG for K_aGG in 

359 cyl_sitekernelsp0.calculate(qpd0)]) 

360 Kcylp1_paGG = np.array([K_aGG for K_aGG in 

361 cyl_sitekernelsp1.calculate(qpd0)]) 

362 Kcyl_app_paGG = np.array([K_aGG for K_aGG in 

363 cyl_sitekernels_app.calculate(qpd0)]) 

364 

365 # Calculate parallelepipedic site kernels 

366 Kparlp_paGG = np.array([K_aGG for K_aGG in 

367 parlp_sitekernels.calculate(qpd0)]) 

368 Kparlp0_paGG = np.array([K_aGG for K_aGG in 

369 parlp_sitekernels0.calculate(qpd0)]) 

370 Kparlp1_paGG = np.array([K_aGG for K_aGG in 

371 parlp_sitekernels1.calculate(qpd0)]) 

372 Kparlp_sum_paGG = np.array([K_aGG for K_aGG in 

373 parlp_sitekernels_sum.calculate(qpd0)]) 

374 Kparlpp0_paGG = np.array([K_aGG for K_aGG in 

375 parlp_sitekernelsp0.calculate(qpd0)]) 

376 Kparlpp1_paGG = np.array([K_aGG for K_aGG in 

377 parlp_sitekernelsp1.calculate(qpd0)]) 

378 Kparlp_app_paGG = np.array([K_aGG for K_aGG in 

379 parlp_sitekernels_app.calculate(qpd0)]) 

380 

381 # Calculate all site kernels together 

382 Kall_sum_paGG = np.array([K_aGG for K_aGG in 

383 all_sitekernels_sum.calculate(qpd0)]) 

384 Kall_app_paGG = np.array([K_aGG for K_aGG in 

385 all_sitekernels_app.calculate(qpd0)]) 

386 

387 # Calculate all site kernels at opposite qs 

388 qpd_q = [get_pw_descriptor(atoms, calc, qpm_c, 

389 ecut=ecut, 

390 gammacentered=gammacentered) 

391 for qpm_c in qpm_qc] 

392 Kall_pm_qpaGG = [np.array([K_aGG for K_aGG in 

393 all_sitekernels_app.calculate(qpd)]) 

394 for qpd in qpd_q] 

395 

396 # Part 4: Check the calculated kernels 

397 

398 # Check geometry shapes of basic arrays 

399 assert all([gs == 'sphere' for gs in sph_sitekernels.geometry_shapes]) 

400 assert all([gs == 'cylinder' for gs in cyl_sitekernels.geometry_shapes]) 

401 assert all([gs == 'parallelepiped' 

402 for gs in parlp_sitekernels.geometry_shapes]) 

403 

404 # Check geometry shapes of summed arrays 

405 assert all([gs == 'sphere' for gs in sph_sitekernels_sum.geometry_shapes]) 

406 assert all([gs == 'cylinder' 

407 for gs in cyl_sitekernels_sum.geometry_shapes]) 

408 assert all([gs == 'parallelepiped' 

409 for gs in parlp_sitekernels_sum.geometry_shapes]) 

410 assert all([gs is None for gs in all_sitekernels_sum.geometry_shapes]) 

411 

412 # Check geometry shapes of appended arrays 

413 assert all([gs == 'sphere' for gs in sph_sitekernels_app.geometry_shapes]) 

414 assert all([gs == 'cylinder' 

415 for gs in cyl_sitekernels_app.geometry_shapes]) 

416 assert all([gs == 'parallelepiped' 

417 for gs in parlp_sitekernels_app.geometry_shapes]) 

418 gs_refs = 2 * ['sphere'] + 2 * ['cylinder'] + 2 * ['parallelepiped'] 

419 assert all([gs == ref for gs, ref in 

420 zip(all_sitekernels_app.geometry_shapes, gs_refs)]) 

421 

422 # Check shape of spherical kernel arrays 

423 nG = len(get_pw_coordinates(qpd0)) 

424 assert sph_sitekernels.shape == Ksph_paGG.shape[:2] 

425 assert Ksph_paGG.shape == rc_pa.shape + (nG, nG) 

426 assert Ksph0_paGG.shape == (rc_pa.shape[0], 1) + (nG, nG) 

427 assert Ksph0_paGG.shape == Ksph1_paGG.shape 

428 assert Ksph_sum_paGG.shape == rc_pa.shape + (nG, nG) 

429 assert Ksphp0_paGG.shape == (1, rc_pa.shape[1]) + (nG, nG) 

430 assert Ksphp0_paGG.shape == Ksphp1_paGG.shape 

431 assert Ksph_app_paGG.shape == rc_pa.shape + (nG, nG) 

432 

433 # Check shape of cylindrical kernel arrays 

434 assert cyl_sitekernels.shape == Kcyl_paGG.shape[:2] 

435 assert Kcyl_paGG.shape == rc_pa.shape + (nG, nG) 

436 assert Kcyl0_paGG.shape == (rc_pa.shape[0], 1) + (nG, nG) 

437 assert Kcyl0_paGG.shape == Kcyl1_paGG.shape 

438 assert Kcyl_sum_paGG.shape == rc_pa.shape + (nG, nG) 

439 assert Kcylp0_paGG.shape == (1, rc_pa.shape[1]) + (nG, nG) 

440 assert Kcylp0_paGG.shape == Kcylp1_paGG.shape 

441 assert Kcyl_app_paGG.shape == rc_pa.shape + (nG, nG) 

442 

443 # Check shape of parallelepipedic kernel arrays 

444 assert parlp_sitekernels.shape == Kparlp_paGG.shape[:2] 

445 assert Kparlp_paGG.shape == cell_pacv.shape[:2] + (nG, nG) 

446 assert Kparlp0_paGG.shape == (cell_pacv.shape[0], 1) + (nG, nG) 

447 assert Kparlp0_paGG.shape == Kparlp1_paGG.shape 

448 assert Kparlp_sum_paGG.shape == cell_pacv.shape[:2] + (nG, nG) 

449 assert Kparlpp0_paGG.shape == (1, cell_pacv.shape[1]) + (nG, nG) 

450 assert Kparlpp0_paGG.shape == Kparlpp1_paGG.shape 

451 assert Kparlp_app_paGG.shape == cell_pacv.shape[:2] + (nG, nG) 

452 

453 # Check shape of array calculated in parallel 

454 assert Kall_sum_paGG.shape == (2, 6, nG, nG) 

455 assert Kall_app_paGG.shape == (6, 2, nG, nG) 

456 

457 # Check self-consitency of spherical arrays 

458 assert np.allclose(Ksph0_paGG[:, 0, ...], Ksph_sum_paGG[:, 0, ...]) 

459 assert np.allclose(Ksph1_paGG[:, 0, ...], Ksph_sum_paGG[:, 1, ...]) 

460 assert np.allclose(Ksph_paGG, Ksph_sum_paGG) 

461 assert np.allclose(Ksphp0_paGG[0], Ksph_app_paGG[0]) 

462 assert np.allclose(Ksphp1_paGG[0], Ksph_app_paGG[1]) 

463 assert np.allclose(Ksph_paGG, Ksph_app_paGG) 

464 

465 # Check self-consistency of cylindrical arrays 

466 assert np.allclose(Kcyl0_paGG[:, 0, ...], Kcyl_sum_paGG[:, 0, ...]) 

467 assert np.allclose(Kcyl1_paGG[:, 0, ...], Kcyl_sum_paGG[:, 1, ...]) 

468 assert np.allclose(Kcyl_paGG, Kcyl_sum_paGG) 

469 assert np.allclose(Kcylp0_paGG[0], Kcyl_app_paGG[0]) 

470 assert np.allclose(Kcylp1_paGG[0], Kcyl_app_paGG[1]) 

471 assert np.allclose(Kcyl_paGG, Kcyl_app_paGG) 

472 

473 # Check self-consistency of parallelepipedic arrays 

474 assert np.allclose(Kparlp0_paGG[:, 0, ...], Kparlp_sum_paGG[:, 0, ...]) 

475 assert np.allclose(Kparlp1_paGG[:, 0, ...], Kparlp_sum_paGG[:, 1, ...]) 

476 assert np.allclose(Kparlp_paGG, Kparlp_sum_paGG) 

477 assert np.allclose(Kparlpp0_paGG[0], Kparlp_app_paGG[0]) 

478 assert np.allclose(Kparlpp1_paGG[0], Kparlp_app_paGG[1]) 

479 assert np.allclose(Kparlp_paGG, Kparlp_app_paGG) 

480 

481 # Check self-consistency of kernels calculated in parallel 

482 assert np.allclose(Ksph_paGG, Kall_sum_paGG[:, :2]) 

483 assert np.allclose(Kcyl_paGG, Kall_sum_paGG[:, 2:4]) 

484 assert np.allclose(Kparlp_paGG, Kall_sum_paGG[:, -2:]) 

485 assert np.allclose(Ksph_paGG, Kall_app_paGG[:2]) 

486 assert np.allclose(Kcyl_paGG, Kall_app_paGG[2:4]) 

487 assert np.allclose(Kparlp_paGG, Kall_app_paGG[-2:]) 

488 

489 # Check that K_G=G'(q=0) gives Vint / V0 (fractional integration volume) 

490 # Volume of unit cell in Å^3 

491 V0 = atoms.get_volume() 

492 # Calculate integration volumes in Å^3 

493 Vsphere_pa = 4 / 3 * np.pi * rc_pa**3 

494 Vcylinder_pa = np.pi * rc_pa**2 * hc_pa 

495 Vparlp_pa = np.abs(np.linalg.det(cell_pacv)) 

496 assert np.allclose(np.diagonal(Ksph_paGG, axis1=2, axis2=3), 

497 Vsphere_pa[..., np.newaxis] / V0) 

498 assert np.allclose(np.diagonal(Kcyl_paGG, axis1=2, axis2=3), 

499 Vcylinder_pa[..., np.newaxis] / V0) 

500 assert np.allclose(np.diagonal(Kparlp_paGG, axis1=2, axis2=3), 

501 Vparlp_pa[..., np.newaxis] / V0) 

502 

503 # Check that K_G=G'(q) gives e^(-iq.τ_a) Θ(q) / V0 

504 B_cv = 2.0 * np.pi * atoms.cell.reciprocal() # Coordinate transform 

505 for qpm_c, Kall_pm_paGG in zip(qpm_qc, Kall_pm_qpaGG): 

506 # Calculate q-vector in Å^(-1) 

507 qpm_v = qpm_c @ B_cv 

508 exp_a = np.exp(-1.j * positions @ qpm_v) 

509 

510 # Calculate site centered geometry factors 

511 Theta_pa = [] 

512 # Spherical geometry factors 

513 for rc_a in rc_pa: 

514 Theta_a = [] 

515 for rc in rc_a: 

516 Theta_a.append(spherical_geometry_factor(qpm_v, rc)) 

517 Theta_pa.append(Theta_a) 

518 # Cylindrical geometry factors 

519 for ez_av, rc_a, hc_a in zip(ez_pav, rc_pa, hc_pa): 

520 Theta_a = [] 

521 for ez_v, rc, hc in zip(ez_av, rc_a, hc_a): 

522 Theta_a.append(cylindrical_geometry_factor(qpm_v, ez_v, 

523 rc, hc)) 

524 Theta_pa.append(Theta_a) 

525 # Parallelepipedic geometry factors 

526 for cell_acv in cell_pacv: 

527 Theta_a = [] 

528 for cell_cv in cell_acv: 

529 Theta_a.append(parallelepipedic_geometry_factor(qpm_v, 

530 cell_cv)) 

531 Theta_pa.append(Theta_a) 

532 Theta_pa = np.array(Theta_pa) 

533 

534 assert np.allclose(np.diagonal(Kall_pm_paGG, axis1=2, axis2=3), 

535 Theta_pa[..., np.newaxis] 

536 * exp_a[np.newaxis, :, np.newaxis] / V0) 

537 

538 # Check that K_GG'(q) is the hermitian conjugate of K_GG'(-q) 

539 Kall_mH_paGG = np.conjugate(np.transpose(Kall_pm_qpaGG[1], (0, 1, 3, 2))) 

540 assert np.allclose(Kall_pm_qpaGG[0], Kall_mH_paGG) 

541 

542 

543# ---------- Test functionality ---------- # 

544 

545 

546def get_pw_descriptor(atoms, calc, q_c, ecut=50., gammacentered=False): 

547 """Mock-up of ChiKSCalculator.get_pw_descriptor. 

548 

549 Works on a bare calculator instance without any actual data in it.""" 

550 from ase.units import Ha 

551 from gpaw.response.qpd import SingleQPWDescriptor 

552 

553 # Create the plane wave descriptor 

554 q_c = np.asarray(q_c, dtype=float) 

555 qpd = SingleQPWDescriptor.from_q(q_c, ecut / Ha, calc.wfs.gd, 

556 gammacentered=gammacentered) 

557 return qpd