Coverage for gpaw/lrtddft/omega_matrix.py: 84%

414 statements  

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

1import numpy as np 

2from scipy.linalg import eigh 

3 

4from ase.units import Hartree 

5from ase.utils.timing import Timer 

6 

7import gpaw.mpi as mpi 

8from .kssingle import KSSingles 

9from gpaw.setup import CachedYukawaInteractions 

10from gpaw.transformers import Transformer 

11from gpaw.utilities import pack_density 

12from gpaw.xc import XC 

13 

14"""This module defines a Omega Matrix class.""" 

15 

16 

17class OmegaMatrix: 

18 

19 """ 

20 Omega matrix in Casidas linear response formalism 

21 

22 Parameters 

23 - calculator: the calculator object the ground state calculation 

24 - kss: the Kohn-Sham singles object 

25 - xc: the exchange correlation approx. to use 

26 - derivativeLevel: which level i of d^i Exc/dn^i to use 

27 - numscale: numeric epsilon for derivativeLevel=0,1 

28 - filehandle: the oject can be read from a filehandle 

29 - txt: output stream or file name 

30 - finegrid: level of fine grid to use. 0: nothing, 1 for poisson only, 

31 2 everything on the fine grid 

32 """ 

33 

34 def __init__(self, 

35 calculator=None, 

36 kss=None, 

37 xc=None, 

38 derivativeLevel=None, 

39 numscale=0.001, 

40 poisson=None, 

41 filehandle=None, 

42 log=None, 

43 finegrid=2, 

44 eh_comm=None): 

45 self.log = log 

46 

47 if eh_comm is None: 

48 eh_comm = mpi.serial_comm 

49 

50 self.eh_comm = eh_comm 

51 self.fullkss = kss 

52 

53 if filehandle is not None: 

54 self.read(fh=filehandle) 

55 return None 

56 

57 self.finegrid = finegrid 

58 

59 if calculator is None: 

60 return 

61 

62 self.paw = calculator 

63 wfs = self.paw.wfs 

64 

65 # handle different grid possibilities 

66 self.restrict = None 

67 # self.poisson = PoissonSolver(nn=self.paw.hamiltonian.poisson.nn) 

68 if poisson is None: 

69 self.poisson = calculator.hamiltonian.poisson 

70 else: 

71 self.poisson = poisson 

72 if finegrid: 

73 self.poisson.set_grid_descriptor(self.paw.density.finegd) 

74 

75 self.gd = self.paw.density.finegd 

76 if finegrid == 1: 

77 self.gd = wfs.gd 

78 else: 

79 self.poisson.set_grid_descriptor(wfs.gd) 

80 self.gd = wfs.gd 

81 self.restrict = Transformer(self.paw.density.finegd, wfs.gd, 

82 self.paw.density.stencil).apply 

83 

84 if xc == 'RPA': 

85 xc = None # enable RPA as keyword 

86 if xc is not None: 

87 if isinstance(xc, str): 

88 self.xc = XC(xc) 

89 else: 

90 self.xc = xc 

91 self.xc.initialize(self.paw.density, self.paw.hamiltonian, wfs) 

92 

93 # check derivativeLevel 

94 if derivativeLevel is None: 

95 derivativeLevel = \ 

96 self.xc.get_functional().get_max_derivative_level() 

97 self.derivativeLevel = derivativeLevel 

98 else: 

99 self.xc = None 

100 

101 if getattr(self.xc, 'omega', 0): # can be None or int 

102 self.yukawa_interactions = CachedYukawaInteractions(self.xc.omega) 

103 else: 

104 self.yukawa_interactions = None 

105 

106 self.numscale = numscale 

107 

108 self.singletsinglet = False 

109 if kss.nvspins < 2 and kss.npspins < 2: 

110 # this will be a singlet to singlet calculation only 

111 self.singletsinglet = True 

112 

113 nij = len(kss) 

114 self.Om = np.zeros((nij, nij)) 

115 self.get_full() 

116 

117 def get_full(self): 

118 """Evaluate full omega matrix""" 

119 self.paw.timer.start('Omega RPA') 

120 self.log() 

121 self.log('Linear response TDDFT calculation') 

122 self.log() 

123 self.get_rpa() 

124 self.paw.timer.stop() 

125 

126 if self.xc is not None: 

127 self.paw.timer.start('Omega XC') 

128 self.get_xc() 

129 self.paw.timer.stop() 

130 

131 self.eh_comm.sum(self.Om) 

132 self.full = self.Om 

133 

134 def get_xc(self): 

135 """Add xc part of the coupling matrix""" 

136 

137 # shorthands 

138 paw = self.paw 

139 wfs = paw.wfs 

140 gd = paw.density.finegd 

141 eh_comm = self.eh_comm 

142 

143 fg = self.finegrid == 2 

144 kss = self.fullkss 

145 nij = len(kss) 

146 

147 Om_xc = self.Om 

148 # initialize densities 

149 # nt_sg is the smooth density on the fine grid with spin index 

150 

151 if kss.nvspins == 2: 

152 # spin polarised ground state calc. 

153 nt_sg = paw.density.nt_sg 

154 else: 

155 # spin unpolarised ground state calc. 

156 if kss.npspins == 2: 

157 # construct spin polarised densities 

158 nt_sg = np.array([.5 * paw.density.nt_sg[0], 

159 .5 * paw.density.nt_sg[0]]) 

160 else: 

161 nt_sg = paw.density.nt_sg 

162 # check if D_sp have been changed before 

163 D_asp = {} 

164 for a, D_sp in self.paw.density.D_asp.items(): 

165 if len(D_sp) != kss.npspins: 

166 if len(D_sp) == 1: 

167 D_asp[a] = np.array([0.5 * D_sp[0], 0.5 * D_sp[0]]) 

168 else: 

169 D_asp[a] = np.array([D_sp[0] + D_sp[1]]) 

170 else: 

171 D_asp[a] = D_sp.copy() 

172 

173 # restrict the density if needed 

174 if fg: 

175 nt_s = nt_sg 

176 else: 

177 nt_s = self.gd.zeros(nt_sg.shape[0]) 

178 for s in range(nt_sg.shape[0]): 

179 self.restrict(nt_sg[s], nt_s[s]) 

180 gd = paw.density.gd 

181 

182 # initialize vxc or fxc 

183 

184 if self.derivativeLevel == 0: 

185 raise NotImplementedError 

186 if kss.npspins == 2: 

187 v_g = nt_sg[0].copy() 

188 else: 

189 v_g = nt_sg.copy() 

190 elif self.derivativeLevel == 1: 

191 pass 

192 elif self.derivativeLevel == 2: 

193 fxc_sg = np.zeros(nt_sg.shape) 

194 self.xc.calculate_fxc(gd, nt_sg, fxc_sg) 

195 else: 

196 raise ValueError('derivativeLevel can only be 0,1,2') 

197 

198 ns = self.numscale 

199 xc = self.xc 

200 self.log('XC', nij, 'transitions') 

201 for ij in range(eh_comm.rank, nij, eh_comm.size): 

202 self.log('XC kss[' + '%d' % ij + ']') 

203 

204 timer = Timer() 

205 timer.start('init') 

206 timer2 = Timer() 

207 

208 if self.derivativeLevel >= 1: 

209 # vxc is available 

210 # We use the numerical two point formula for calculating 

211 # the integral over fxc*n_ij. The results are 

212 # vvt_s smooth integral 

213 # nucleus.I_sp atom based correction matrices (pack_hermitian) 

214 # stored on each nucleus 

215 timer2.start('init v grids') 

216 vp_s = np.zeros(nt_s.shape, nt_s.dtype.char) 

217 vm_s = np.zeros(nt_s.shape, nt_s.dtype.char) 

218 if kss.npspins == 2: # spin polarised 

219 nv_s = nt_s.copy() 

220 nv_s[kss[ij].pspin] += ns * kss[ij].get(fg) 

221 xc.calculate(gd, nv_s, vp_s) 

222 nv_s = nt_s.copy() 

223 nv_s[kss[ij].pspin] -= ns * kss[ij].get(fg) 

224 xc.calculate(gd, nv_s, vm_s) 

225 else: # spin unpolarised 

226 nv = nt_s + ns * kss[ij].get(fg) 

227 xc.calculate(gd, nv, vp_s) 

228 nv = nt_s - ns * kss[ij].get(fg) 

229 xc.calculate(gd, nv, vm_s) 

230 vvt_s = (0.5 / ns) * (vp_s - vm_s) 

231 timer2.stop() 

232 

233 # initialize the correction matrices 

234 timer2.start('init v corrections') 

235 I_asp = {} 

236 for a, P_ni in wfs.kpt_u[kss[ij].spin].P_ani.items(): 

237 # create the modified density matrix 

238 Pi_i = P_ni[kss[ij].i] 

239 Pj_i = P_ni[kss[ij].j] 

240 P_ii = np.outer(Pi_i, Pj_i) 

241 # we need the symmetric form, hence we can pack 

242 P_p = pack_density(P_ii) 

243 D_sp = D_asp[a].copy() 

244 D_sp[kss[ij].pspin] -= ns * P_p 

245 setup = wfs.setups[a] 

246 I_sp = np.zeros_like(D_sp) 

247 self.xc.calculate_paw_correction(setup, D_sp, I_sp) 

248 I_sp *= -1.0 

249 D_sp = D_asp[a].copy() 

250 D_sp[kss[ij].pspin] += ns * P_p 

251 self.xc.calculate_paw_correction(setup, D_sp, I_sp) 

252 I_sp /= 2.0 * ns 

253 I_asp[a] = I_sp 

254 timer2.stop() 

255 

256 timer.stop() 

257 t0 = timer.get_time('init') 

258 timer.start(ij) 

259 

260 for kq in range(ij, nij): 

261 weight = self.weight_Kijkq(ij, kq) 

262 

263 if self.derivativeLevel == 0: 

264 # only Exc is available 

265 

266 if kss.npspins == 2: # spin polarised 

267 nv_g = nt_sg.copy() 

268 nv_g[kss[ij].pspin] += kss[ij].get(fg) 

269 nv_g[kss[kq].pspin] += kss[kq].get(fg) 

270 Excpp = xc.get_energy_and_potential( 

271 nv_g[0], v_g, nv_g[1], v_g) 

272 nv_g = nt_sg.copy() 

273 nv_g[kss[ij].pspin] += kss[ij].get(fg) 

274 nv_g[kss[kq].pspin] -= kss[kq].get(fg) 

275 Excpm = xc.get_energy_and_potential( 

276 nv_g[0], v_g, nv_g[1], v_g) 

277 nv_g = nt_sg.copy() 

278 nv_g[kss[ij].pspin] -=\ 

279 kss[ij].get(fg) 

280 nv_g[kss[kq].pspin] +=\ 

281 kss[kq].get(fg) 

282 Excmp = xc.get_energy_and_potential( 

283 nv_g[0], v_g, nv_g[1], v_g) 

284 nv_g = nt_sg.copy() 

285 nv_g[kss[ij].pspin] -= \ 

286 kss[ij].get(fg) 

287 nv_g[kss[kq].pspin] -=\ 

288 kss[kq].get(fg) 

289 Excpp = xc.get_energy_and_potential( 

290 nv_g[0], v_g, nv_g[1], v_g) 

291 else: # spin unpolarised 

292 nv_g = nt_sg + ns * kss[ij].get(fg)\ 

293 + ns * kss[kq].get(fg) 

294 Excpp = xc.get_energy_and_potential(nv_g, v_g) 

295 nv_g = nt_sg + ns * kss[ij].get(fg)\ 

296 - ns * kss[kq].get(fg) 

297 Excpm = xc.get_energy_and_potential(nv_g, v_g) 

298 nv_g = nt_sg - ns * kss[ij].get(fg)\ 

299 + ns * kss[kq].get(fg) 

300 Excmp = xc.get_energy_and_potential(nv_g, v_g) 

301 nv_g = nt_sg - ns * kss[ij].get(fg)\ 

302 - ns * kss[kq].get(fg) 

303 Excmm = xc.get_energy_and_potential(nv_g, v_g) 

304 

305 Om_xc[ij, kq] += weight *\ 

306 0.25 * \ 

307 (Excpp - Excmp - Excpm + Excmm) / (ns * ns) 

308 

309 elif self.derivativeLevel == 1: 

310 # vxc is available 

311 

312 timer2.start('integrate') 

313 Om_xc[ij, kq] += weight *\ 

314 self.gd.integrate(kss[kq].get(fg) * 

315 vvt_s[kss[kq].pspin]) 

316 timer2.stop() 

317 

318 timer2.start('integrate corrections') 

319 Exc = 0. 

320 for a, P_ni in wfs.kpt_u[kss[kq].spin].P_ani.items(): 

321 # create the modified density matrix 

322 Pk_i = P_ni[kss[kq].i] 

323 Pq_i = P_ni[kss[kq].j] 

324 P_ii = np.outer(Pk_i, Pq_i) 

325 # we need the symmetric form, hence we can pack 

326 # use pack_density as I_sp used pack_hermitian 

327 P_p = pack_density(P_ii) 

328 Exc += np.dot(I_asp[a][kss[kq].pspin], P_p) 

329 Om_xc[ij, kq] += weight * self.gd.comm.sum_scalar(Exc) 

330 timer2.stop() 

331 

332 elif self.derivativeLevel == 2: 

333 # fxc is available 

334 if kss.npspins == 2: # spin polarised 

335 Om_xc[ij, kq] += weight *\ 

336 gd.integrate(kss[ij].get(fg) * 

337 kss[kq].get(fg) * 

338 fxc_sg[kss[ij].pspin, kss[kq].pspin]) 

339 else: # spin unpolarised 

340 Om_xc[ij, kq] += weight *\ 

341 gd.integrate(kss[ij].get(fg) * 

342 kss[kq].get(fg) * 

343 fxc_sg) 

344 

345 # XXX still numeric derivatives for local terms 

346 timer2.start('integrate corrections') 

347 Exc = 0. 

348 for a, P_ni in wfs.kpt_u[kss[kq].spin].P_ani.items(): 

349 # create the modified density matrix 

350 Pk_i = P_ni[kss[kq].i] 

351 Pq_i = P_ni[kss[kq].j] 

352 P_ii = np.outer(Pk_i, Pq_i) 

353 # we need the symmetric form, hence we can pack 

354 # use pack_density as I_sp used pack_hermitian 

355 P_p = pack_density(P_ii) 

356 Exc += np.dot(I_asp[a][kss[kq].pspin], P_p) 

357 Om_xc[ij, kq] += weight * self.gd.comm.sum(Exc) 

358 timer2.stop() 

359 

360 if ij != kq: 

361 Om_xc[kq, ij] = Om_xc[ij, kq] 

362 

363 timer.stop() 

364# timer2.write() 

365 if ij < (nij - 1): 

366 self.log('XC estimated time left', 

367 self.time_left(timer, t0, ij, nij)) 

368 

369 def Coulomb_integral_kss(self, kss_ij, kss_kq, phit, rhot, 

370 timer=None, yukawa=False): 

371 # smooth part 

372 if timer: 

373 timer.start('integrate') 

374 I = self.gd.integrate(rhot * phit) 

375 if timer: 

376 timer.stop() 

377 timer.start('integrate corrections 2') 

378 

379 wfs = self.paw.wfs 

380 Pij_ani = wfs.kpt_u[kss_ij.spin].P_ani 

381 Pkq_ani = wfs.kpt_u[kss_kq.spin].P_ani 

382 

383 # Add atomic corrections 

384 Ia = 0.0 

385 for a, Pij_ni in Pij_ani.items(): 

386 Pi_i = Pij_ni[kss_ij.i] 

387 Pj_i = Pij_ni[kss_ij.j] 

388 Dij_ii = np.outer(Pi_i, Pj_i) 

389 Dij_p = pack_density(Dij_ii) 

390 Pk_i = Pkq_ani[a][kss_kq.i] 

391 Pq_i = Pkq_ani[a][kss_kq.j] 

392 Dkq_ii = np.outer(Pk_i, Pq_i) 

393 Dkq_p = pack_density(Dkq_ii) 

394 if yukawa: 

395 assert abs( 

396 self.yukawa_interactions.omega - self.xc.omega) < 1e-14 

397 C_pp = self.yukawa_interactions.get_Mg_pp(wfs.setups[a]) 

398 else: 

399 C_pp = wfs.setups[a].M_pp 

400 # ---- 

401 # 2 > P P C P P 

402 # ---- ip jr prst ks qt 

403 # prst 

404 Ia += 2.0 * np.dot(Dkq_p, np.dot(C_pp, Dij_p)) 

405 I += self.gd.comm.sum_scalar(Ia) 

406 if timer: 

407 timer.stop() 

408 

409 return I 

410 

411 def get_rpa(self): 

412 """calculate RPA part of the omega matrix""" 

413 

414 # shorthands 

415 kss = self.fullkss 

416 finegrid = self.finegrid 

417 eh_comm = self.eh_comm 

418 

419 # calculate omega matrix 

420 nij = len(kss) 

421 self.log('RPA', nij, 'transitions') 

422 

423 Om = self.Om 

424 

425 for ij in range(eh_comm.rank, nij, eh_comm.size): 

426 self.log('RPA kss[' + '%d' % ij + ']=', kss[ij]) 

427 

428 timer = Timer() 

429 timer.start('init') 

430 timer2 = Timer() 

431 

432 # smooth density including compensation charges 

433 timer2.start('with_compensation_charges 0') 

434 rhot_p = kss[ij].with_compensation_charges( 

435 finegrid != 0) 

436 timer2.stop() 

437 

438 # integrate with 1/|r_1-r_2| 

439 timer2.start('poisson') 

440 phit_p = np.zeros(rhot_p.shape, rhot_p.dtype.char) 

441 self.poisson.solve(phit_p, rhot_p, charge=None) 

442 timer2.stop() 

443 

444 timer.stop() 

445 t0 = timer.get_time('init') 

446 timer.start(ij) 

447 

448 if finegrid == 1: 

449 rhot = kss[ij].with_compensation_charges() 

450 phit = self.gd.zeros() 

451 self.restrict(phit_p, phit) 

452 else: 

453 phit = phit_p 

454 rhot = rhot_p 

455 

456 for kq in range(ij, nij): 

457 if kq != ij: 

458 # smooth density including compensation charges 

459 timer2.start('kq with_compensation_charges') 

460 rhot = kss[kq].with_compensation_charges( 

461 finegrid == 2) 

462 timer2.stop() 

463 

464 pre = 2 * np.sqrt(kss[ij].get_energy() * 

465 kss[kq].get_energy() * 

466 kss[ij].get_weight() * 

467 kss[kq].get_weight()) 

468 I = self.Coulomb_integral_kss(kss[ij], kss[kq], 

469 rhot, phit, timer2) 

470 Om[ij, kq] = pre * I 

471 

472 if ij == kq: 

473 Om[ij, kq] += kss[ij].get_energy() ** 2 

474 else: 

475 Om[kq, ij] = Om[ij, kq] 

476 

477 timer.stop() 

478# timer2.write() 

479 if ij < (nij - 1): 

480 t = timer.get_time(ij) # time for nij-ij calculations 

481 t = .5 * t * \ 

482 (nij - ij) # estimated time for n*(n+1)/2, n=nij-(ij+1) 

483 self.log('RPA estimated time left', 

484 self.timestring(t0 * (nij - ij - 1) + t)) 

485 

486 def singlets_triplets(self): 

487 """Split yourself into singlet and triplet transitions""" 

488 

489 assert self.fullkss.npspins == 2 

490 assert self.fullkss.nvspins == 1 

491 

492 # strip kss from down spins 

493 skss = KSSingles() 

494 skss.dtype = self.fullkss.dtype 

495 tkss = KSSingles() 

496 tkss.dtype = self.fullkss.dtype 

497 map = [] 

498 for ij, ks in enumerate(self.fullkss): 

499 if ks.pspin == ks.spin: 

500 skss.append((ks + ks) / np.sqrt(2)) 

501 tkss.append((ks - ks) / np.sqrt(2)) 

502 map.append(ij) 

503 skss.istart = tkss.istart = self.fullkss.restrict['istart'] 

504 skss.jend = tkss.jend = self.fullkss.restrict['jend'] 

505 nkss = len(skss) 

506 

507 # define the singlet and the triplet omega-matrices 

508 sOm = OmegaMatrix(kss=skss, log=self.log) 

509 sOm.full = np.empty((nkss, nkss)) 

510 tOm = OmegaMatrix(kss=tkss, log=self.log) 

511 tOm.full = np.empty((nkss, nkss)) 

512 for ij in range(nkss): 

513 for kl in range(nkss): 

514 sOm.full[ij, kl] = (self.full[map[ij], map[kl]] + 

515 self.full[map[ij], nkss + map[kl]]) 

516 tOm.full[ij, kl] = (self.full[map[ij], map[kl]] - 

517 self.full[map[ij], nkss + map[kl]]) 

518 return sOm, tOm 

519 

520 def timestring(self, t): 

521 ti = int(t + 0.5) 

522 td = ti // 86400 

523 st = '' 

524 if td > 0: 

525 st += '%d' % td + 'd' 

526 ti -= td * 86400 

527 th = ti // 3600 

528 if th > 0: 

529 st += '%d' % th + 'h' 

530 ti -= th * 3600 

531 tm = ti // 60 

532 if tm > 0: 

533 st += '%d' % tm + 'm' 

534 ti -= tm * 60 

535 st += '%d' % ti + 's' 

536 return st 

537 

538 def time_left(self, timer, t0, ij, nij): 

539 t = timer.get_time(ij) # time for nij-ij calculations 

540 t = .5 * t * (nij - ij) # estimated time for n*(n+1)/2, n=nij-(ij+1) 

541 return self.timestring(t0 * (nij - ij - 1) + t) 

542 

543 def get_map(self, restrict=None): 

544 """Return the reduction map for the given requirements 

545 

546 Returns 

547 ------- 

548 map - list of original indices 

549 kss - reduced KSSingles object 

550 """ 

551 self.log('# diagonalize: %d transitions original' 

552 % len(self.fullkss)) 

553 

554 map = [] 

555 

556 rst_dict = self.fullkss.restrict.values 

557 if restrict is not None: 

558 rst_dict.update(restrict) 

559 kss = KSSingles(restrict=rst_dict) 

560 kss.dtype = self.fullkss.dtype 

561 

562 for ij, k in zip(range(len(self.fullkss)), self.fullkss): 

563 if kss.restrict.is_good(k): 

564 kss.append(k) 

565 map.append(ij) 

566 kss.update() 

567 self.log('# diagonalize: %d transitions now' % len(kss)) 

568 

569 return map, kss 

570 

571 def diagonalize(self, restrict=None): 

572 """Evaluate Eigenvectors and Eigenvalues:""" 

573 map, kss = self.get_map(restrict) 

574 

575 nij = len(kss) 

576 if map is None: 

577 evec = self.full.copy() 

578 else: 

579 evec = np.zeros((nij, nij)) 

580 for ij in range(nij): 

581 for kq in range(nij): 

582 evec[ij, kq] = self.full[map[ij], map[kq]] 

583 assert len(evec) > 0 

584 

585 self.eigenvalues, v = eigh(evec) 

586 self.eigenvectors = v.T 

587 self.kss = kss 

588 

589 @property 

590 def kss(self): 

591 if hasattr(self, '_kss'): 

592 return self._kss 

593 return self.fullkss 

594 

595 @kss.setter 

596 def kss(self, kss): 

597 """Set current (restricted) KSSingles object""" 

598 self._kss = kss 

599 

600 def read(self, filename=None, fh=None): 

601 """Read myself from a file""" 

602 if fh is None: 

603 f = open(filename) 

604 else: 

605 f = fh 

606 

607 f.readline() 

608 nij = int(f.readline()) 

609 full = np.zeros((nij, nij)) 

610 for ij in range(nij): 

611 l = [float(x) for x in f.readline().split()] 

612 full[ij, ij:] = l 

613 full[ij:, ij] = l 

614 self.full = full 

615 

616 if fh is None: 

617 f.close() 

618 

619 def write(self, filename=None, fh=None): 

620 """Write current state to a file.""" 

621 try: 

622 rank = self.paw.wfs.world.rank 

623 except AttributeError: 

624 rank = mpi.world.rank 

625 if rank == 0: 

626 if fh is None: 

627 f = open(filename, 'w') 

628 else: 

629 f = fh 

630 

631 f.write('# OmegaMatrix\n') 

632 nij = len(self.fullkss) 

633 f.write('%d\n' % nij) 

634 for ij in range(nij): 

635 for kq in range(ij, nij): 

636 f.write(' %g' % self.full[ij, kq]) 

637 f.write('\n') 

638 

639 if fh is None: 

640 f.close() 

641 

642 def weight_Kijkq(self, ij, kq): 

643 """weight for the coupling matrix terms""" 

644 kss = self.fullkss 

645 return 2. * np.sqrt(kss[ij].get_energy() * 

646 kss[kq].get_energy() * 

647 kss[ij].get_weight() * 

648 kss[kq].get_weight()) 

649 

650 def __str__(self): 

651 str = '<OmegaMatrix> ' 

652 if hasattr(self, 'eigenvalues'): 

653 str += 'dimension ' + ('%d' % len(self.eigenvalues)) 

654 str += '\neigenvalues: ' 

655 for ev in self.eigenvalues: 

656 str += ' ' + ('%f' % (np.sqrt(ev) * Hartree)) 

657 return str