Coverage for gpaw/wavefunctions/pw.py: 87%
579 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-19 00:19 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-19 00:19 +0000
1from __future__ import annotations
2from math import pi
3import numbers
5from ase.units import Bohr, Ha
6from ase.utils.timing import timer
7import numpy as np
9from gpaw.band_descriptor import BandDescriptor
10from gpaw.blacs import BlacsDescriptor, BlacsGrid, Redistributor
11from gpaw.lfc import BasisFunctions
12from gpaw.matrix_descriptor import MatrixDescriptor
13from gpaw.pw.descriptor import PWDescriptor
14from gpaw.pw.lfc import PWLFC
15from gpaw.typing import Array2D
16from gpaw.utilities import unpack_hermitian
17from gpaw.utilities.blas import axpy
18from gpaw.utilities.progressbar import ProgressBar
19from gpaw.wavefunctions.arrays import PlaneWaveExpansionWaveFunctions
20from gpaw.wavefunctions.fdpw import FDPWWaveFunctions
21from gpaw.wavefunctions.mode import Mode
22import gpaw
23import gpaw.cgpaw as cgpaw
24import gpaw.fftw as fftw
27class PW(Mode):
28 name = 'pw'
30 def __init__(self,
31 ecut: float = 340,
32 *,
33 fftwflags: int = fftw.MEASURE,
34 cell: Array2D | None = None,
35 gammacentered=False, # Deprecated
36 pulay_stress: float | None = None,
37 dedecut: float | str | None = None,
38 force_complex_dtype: bool = False,
39 interpolation: str | int = 'fft'):
40 """Plane-wave basis mode.
42 Only one of ``dedecut`` and ``pulay_stress`` can be used.
44 Parameters
45 ==========
46 ecut: float
47 Plane-wave cutoff in eV.
48 gammacentered: bool
49 Center the grid of chosen plane waves around the
50 gamma point or q/k-vector
51 dedecut:
52 Estimate of derivative of total energy with respect to
53 plane-wave cutoff. Used to calculate the Pulay-stress.
54 pulay_stress: float or None
55 Pulay-stress correction.
56 fftwflags: int
57 Flags for making an FFTW plan. There are 4 possibilities
58 (default is MEASURE)::
60 from gpaw.fftw import ESTIMATE, MEASURE, PATIENT, EXHAUSTIVE
62 cell: np.ndarray
63 Use this unit cell to chose the plane-waves.
64 interpolation : str or int
65 Interpolation scheme to construct the density on the fine grid.
66 Default is ``'fft'`` and alternatively a stencil (integer) can
67 be given to perform an explicit real-space interpolation.
68 """
70 assert not gammacentered
71 self.ecut = ecut / Ha
72 # Don't do expensive planning in dry-run mode:
73 self.fftwflags = fftwflags if not gpaw.dry_run else fftw.MEASURE
74 self.dedecut = dedecut
75 self.interpolation = interpolation
76 self.pulay_stress = (None
77 if pulay_stress is None
78 else pulay_stress * Bohr**3 / Ha)
80 assert pulay_stress is None or dedecut is None
82 if cell is None:
83 self.cell_cv = None
84 else:
85 self.cell_cv = cell / Bohr
87 Mode.__init__(self, force_complex_dtype)
89 def __call__(self, parallel, initksl, gd, **kwargs):
90 dedepsilon = 0.0
92 if self.cell_cv is None:
93 ecut = self.ecut
94 else:
95 volume0 = abs(np.linalg.det(self.cell_cv))
96 ecut = self.ecut * (volume0 / gd.volume)**(2 / 3.0)
98 if self.pulay_stress is not None:
99 dedepsilon = self.pulay_stress * gd.volume
100 elif self.dedecut is not None:
101 if self.dedecut == 'estimate':
102 dedepsilon = 'estimate'
103 else:
104 dedepsilon = self.dedecut * 2 / 3 * ecut
106 wfs = PWWaveFunctions(ecut,
107 self.fftwflags, dedepsilon,
108 parallel, initksl, gd=gd,
109 **kwargs)
111 return wfs
113 def todict(self):
114 dct = Mode.todict(self)
115 dct['ecut'] = self.ecut * Ha
117 if self.cell_cv is not None:
118 dct['cell'] = self.cell_cv * Bohr
119 if self.pulay_stress is not None:
120 dct['pulay_stress'] = self.pulay_stress * Ha / Bohr**3
121 if self.dedecut is not None:
122 dct['dedecut'] = self.dedecut
123 if self.interpolation != 'fft':
124 dct['interpolation'] = self.interpolation
125 return dct
128class Preconditioner:
129 """Preconditioner for KS equation.
131 From:
133 Teter, Payne and Allen, Phys. Rev. B 40, 12255 (1989)
135 as modified by:
137 Kresse and Furthmüller, Phys. Rev. B 54, 11169 (1996)
138 """
140 def __init__(self, G2_qG, pd, _scale=1.0):
141 self.G2_qG = G2_qG
142 self.pd = pd
143 self._scale = _scale
145 def calculate_kinetic_energy(self, psit_xG, kpt):
146 if psit_xG.ndim == 1:
147 return self.calculate_kinetic_energy(psit_xG[np.newaxis], kpt)[0]
148 G2_G = self.G2_qG[kpt.q]
149 return np.array([self.pd.integrate(0.5 * G2_G * psit_G, psit_G).real
150 for psit_G in psit_xG]) * self._scale
152 def __call__(self, R_xG, kpt, ekin_x, out=None):
153 if out is None:
154 out = np.empty_like(R_xG)
155 G2_G = self.G2_qG[kpt.q]
156 if R_xG.ndim == 1:
157 cgpaw.pw_precond(G2_G, R_xG, ekin_x, out)
158 else:
159 for PR_G, R_G, ekin in zip(out, R_xG, ekin_x):
160 cgpaw.pw_precond(G2_G, R_G, ekin, PR_G)
161 return out
164class NonCollinearPreconditioner(Preconditioner):
165 def calculate_kinetic_energy(self, psit_xsG, kpt):
166 shape = psit_xsG.shape
167 ekin_xs = Preconditioner.calculate_kinetic_energy(
168 self, psit_xsG.reshape((-1, shape[-1])), kpt)
169 return ekin_xs.reshape(shape[:-1]).sum(-1)
171 def __call__(self, R_sG, kpt, ekin, out=None):
172 return Preconditioner.__call__(self, R_sG, kpt, [ekin, ekin], out)
175class PWWaveFunctions(FDPWWaveFunctions):
176 mode = 'pw'
178 def __init__(self, ecut, fftwflags, dedepsilon,
179 parallel, initksl,
180 reuse_wfs_method, collinear,
181 gd, nvalence, setups, bd, dtype,
182 world, kd, kptband_comm, timer):
183 self.ecut = ecut
184 self.fftwflags = fftwflags
185 self.dedepsilon = dedepsilon # Pulay correction for stress tensor
187 self.ng_k = None # number of G-vectors for all IBZ k-points
189 FDPWWaveFunctions.__init__(self, parallel, initksl,
190 reuse_wfs_method=reuse_wfs_method,
191 collinear=collinear,
192 gd=gd, nvalence=nvalence, setups=setups,
193 bd=bd, dtype=dtype, world=world, kd=kd,
194 kptband_comm=kptband_comm, timer=timer)
195 self.read_from_file_init_wfs_dm = False
197 def empty(self, n=(), global_array=False, realspace=False, q=None):
198 if isinstance(n, numbers.Integral):
199 n = (n,)
200 if realspace:
201 return self.gd.empty(n, self.dtype, global_array)
202 elif global_array:
203 return np.zeros(n + (self.pd.ngmax,), complex)
204 elif q is None:
205 return np.zeros(n + (self.pd.maxmyng,), complex)
206 else:
207 return self.pd.empty(n, self.dtype, q)
209 def integrate(self, a_xg, b_yg=None, global_integral=True):
210 return self.pd.integrate(a_xg, b_yg, global_integral)
212 def bytes_per_wave_function(self):
213 return 16 * self.pd.maxmyng
215 def set_setups(self, setups):
216 self.timer.start('PWDescriptor')
217 self.pd = PWDescriptor(self.ecut, self.gd, self.dtype, self.kd,
218 self.fftwflags)
219 self.timer.stop('PWDescriptor')
221 # Build array of number of plane wave coefficiants for all k-points
222 # in the IBZ:
223 self.ng_k = np.zeros(self.kd.nibzkpts, dtype=int)
224 for kpt in self.kpt_u:
225 if kpt.s != 1: # avoid double counting (only sum over s=0 or None)
226 self.ng_k[kpt.k] = len(self.pd.Q_qG[kpt.q])
227 self.kd.comm.sum(self.ng_k)
229 self.pt = PWLFC([setup.pt_j for setup in setups], self.pd)
231 FDPWWaveFunctions.set_setups(self, setups)
233 if self.dedepsilon == 'estimate':
234 dedecut = self.setups.estimate_dedecut(self.ecut)
235 self.dedepsilon = dedecut * 2 / 3 * self.ecut
237 def get_pseudo_partial_waves(self):
238 return PWLFC([setup.get_partial_waves_for_atomic_orbitals()
239 for setup in self.setups], self.pd)
241 def __str__(self):
242 s = 'Wave functions: Plane wave expansion\n'
243 s += ' Cutoff energy: %.3f eV\n' % (self.pd.ecut * Ha)
245 if self.dtype == float:
246 s += (' Number of coefficients: %d (reduced to %d)\n' %
247 (self.pd.ngmax * 2 - 1, self.pd.ngmax))
248 else:
249 s += (' Number of coefficients (min, max): %d, %d\n' %
250 (self.pd.ngmin, self.pd.ngmax))
252 stress = self.dedepsilon / self.gd.volume * Ha / Bohr**3
253 dedecut = 1.5 * self.dedepsilon / self.ecut
254 s += (' Pulay-stress correction: {:.6f} eV/Ang^3 '
255 '(de/decut={:.6f})\n'.format(stress, dedecut))
257 if fftw.have_fftw():
258 s += ' Using FFTW library\n'
259 else:
260 s += " Using Numpy's FFT\n"
261 return s + FDPWWaveFunctions.__str__(self)
263 def make_preconditioner(self, block=1):
264 if self.collinear:
265 return Preconditioner(self.pd.G2_qG, self.pd)
266 return NonCollinearPreconditioner(self.pd.G2_qG, self.pd)
268 @timer('Apply H')
269 def apply_pseudo_hamiltonian(self, kpt, ham, psit_xG, Htpsit_xG):
270 """Apply the pseudo Hamiltonian i.e. without PAW corrections."""
271 if not self.collinear:
272 self.apply_pseudo_hamiltonian_nc(kpt, ham, psit_xG, Htpsit_xG)
273 return
275 N = len(psit_xG)
276 S = self.gd.comm.size
278 vt_R = self.gd.collect(ham.vt_sG[kpt.s], broadcast=True)
279 Q_G = self.pd.Q_qG[kpt.q]
280 T_G = 0.5 * self.pd.G2_qG[kpt.q]
282 for n1 in range(0, N, S):
283 n2 = min(n1 + S, N)
284 psit_G = self.pd.alltoall1(psit_xG[n1:n2], kpt.q)
285 with self.timer('HMM T'):
286 np.multiply(T_G, psit_xG[n1:n2], Htpsit_xG[n1:n2])
287 if psit_G is not None:
288 psit_R = self.pd.ifft(psit_G, kpt.q, local=True, safe=False)
289 psit_R *= vt_R
290 self.pd.fftplan.execute()
291 vtpsit_G = self.pd.tmp_Q.ravel()[Q_G]
292 else:
293 vtpsit_G = self.pd.tmp_G
294 self.pd.alltoall2(vtpsit_G, kpt.q, Htpsit_xG[n1:n2])
296 ham.xc.apply_orbital_dependent_hamiltonian(
297 kpt, psit_xG, Htpsit_xG, ham.dH_asp)
299 def apply_pseudo_hamiltonian_nc(self, kpt, ham, psit_xG, Htpsit_xG):
300 Htpsit_xG[:] = 0.5 * self.pd.G2_qG[kpt.q] * psit_xG
301 v, x, y, z = ham.vt_xG
302 iy = y * 1j
303 for psit_sG, Htpsit_sG in zip(psit_xG, Htpsit_xG):
304 a = self.pd.ifft(psit_sG[0], kpt.q)
305 b = self.pd.ifft(psit_sG[1], kpt.q)
306 Htpsit_sG[0] += self.pd.fft(a * (v + z) + b * (x - iy), kpt.q)
307 Htpsit_sG[1] += self.pd.fft(a * (x + iy) + b * (v - z), kpt.q)
309 def add_orbital_density(self, nt_G, kpt, n):
310 axpy(1.0, abs(self.pd.ifft(kpt.psit_nG[n], kpt.q))**2, nt_G)
312 def add_to_density_from_k_point_with_occupation(self, nt_xR, kpt, f_n):
313 if not self.collinear:
314 self.add_to_density_from_k_point_with_occupation_nc(
315 nt_xR, kpt, f_n)
316 return
318 comm = self.gd.comm
320 nt_R = self.gd.zeros(global_array=True)
322 for n1 in range(0, self.bd.mynbands, comm.size):
323 n2 = min(n1 + comm.size, self.bd.mynbands)
324 psit_G = self.pd.alltoall1(kpt.psit.array[n1:n2], kpt.q)
325 if psit_G is not None:
326 f = f_n[n1 + comm.rank]
327 psit_R = self.pd.ifft(psit_G, kpt.q, local=True, safe=False)
328 # Same as nt_R += f * abs(psit_R)**2, but much faster:
329 cgpaw.add_to_density(f, psit_R, nt_R)
331 comm.sum(nt_R)
332 nt_R = self.gd.distribute(nt_R)
333 nt_xR[kpt.s] += nt_R
335 def add_to_density_from_k_point_with_occupation_nc(self, nt_xR, kpt, f_n):
336 for f, psit_sG in zip(f_n, kpt.psit.array):
337 p1 = self.pd.ifft(psit_sG[0], kpt.q)
338 p2 = self.pd.ifft(psit_sG[1], kpt.q)
339 p11 = p1.real**2 + p1.imag**2
340 p22 = p2.real**2 + p2.imag**2
341 p12 = p1.conj() * p2
342 nt_xR[0] += f * (p11 + p22)
343 nt_xR[1] += 2 * f * p12.real
344 nt_xR[2] += 2 * f * p12.imag
345 nt_xR[3] += f * (p11 - p22)
347 def add_to_kinetic_energy_density_kpt(self, kpt, psit_xG, taut_xR):
348 N = self.bd.mynbands
349 S = self.gd.comm.size
350 Gpsit_xG = np.empty((S,) + psit_xG.shape[1:], dtype=psit_xG.dtype)
351 taut_R = self.gd.zeros(global_array=True)
352 G_Gv = self.pd.get_reciprocal_vectors(q=kpt.q)
353 comm = self.gd.comm
355 for v in range(3):
356 for n1 in range(0, N, S):
357 n2 = min(n1 + S, N)
358 dn = n2 - n1
359 Gpsit_xG[:dn] = 1j * G_Gv[:, v] * psit_xG[n1:n2]
360 Gpsit_G = self.pd.alltoall1(Gpsit_xG[:dn], kpt.q)
361 if Gpsit_G is not None:
362 f = kpt.f_n[n1 + comm.rank]
363 a_R = self.pd.ifft(Gpsit_G, kpt.q, local=True, safe=False)
364 cgpaw.add_to_density(0.5 * f, a_R, taut_R)
366 comm.sum(taut_R)
367 taut_R = self.gd.distribute(taut_R)
368 taut_xR[kpt.s] += taut_R
370 def add_to_ke_crossterms_kpt(self, kpt, psit_xG, taut_xR):
371 N = self.bd.mynbands
372 S = self.gd.comm.size
373 Gpsit_xG = np.empty((S,) + psit_xG.shape[1:], dtype=psit_xG.dtype)
374 taut_wR = self.gd.zeros((6,), global_array=True)
375 G_Gv = self.pd.get_reciprocal_vectors(q=kpt.q)
376 comm = self.gd.comm
378 for n1 in range(0, N, S):
379 n2 = min(n1 + S, N)
380 dn = n2 - n1
381 a_vR = {}
382 for v in range(3):
383 Gpsit_xG[:dn] = 1j * G_Gv[:, v] * psit_xG[n1:n2]
384 Gpsit_G = self.pd.alltoall1(Gpsit_xG[:dn], kpt.q)
385 if Gpsit_G is not None:
386 f = kpt.f_n[n1 + comm.rank]
387 a_vR[v] = self.pd.ifft(Gpsit_G, kpt.q,
388 local=True, safe=True)
389 if len(a_vR) == 3:
390 f = kpt.f_n[n1 + comm.rank]
391 w = 0
392 for v1 in range(3):
393 for v2 in range(v1, 3):
394 # imaginary parts should cancel
395 taut_wR[w] += f * (a_vR[v1].conj()
396 * a_vR[v2]).real
397 w += 1
398 elif len(a_vR) == 0:
399 pass
400 else:
401 raise RuntimeError('Parallelization issue')
403 comm.sum(taut_wR)
404 taut_wR = self.gd.distribute(taut_wR)
405 taut_xR[kpt.s, :] += taut_wR
407 def calculate_kinetic_energy_density(self):
408 if self.kpt_u[0].f_n is None:
409 return None
411 taut_sR = self.gd.zeros(self.nspins)
412 for kpt in self.kpt_u:
413 self.add_to_kinetic_energy_density_kpt(kpt, kpt.psit_nG, taut_sR)
415 self.kptband_comm.sum(taut_sR)
416 for taut_R in taut_sR:
417 self.kd.symmetry.symmetrize(taut_R, self.gd)
418 return taut_sR
420 def calculate_kinetic_energy_density_crossterms(self):
421 if self.kpt_u[0].f_n is None:
422 return None
424 taut_svvR = self.gd.zeros((self.nspins, 6))
425 for kpt in self.kpt_u:
426 self.add_to_ke_crossterms_kpt(kpt, kpt.psit_nG, taut_svvR)
428 self.kptband_comm.sum(taut_svvR)
429 for taut_R in taut_svvR.reshape(-1, *taut_svvR.shape[-3:]):
430 self.kd.symmetry.symmetrize(taut_R, self.gd)
431 return taut_svvR
433 def apply_mgga_orbital_dependent_hamiltonian(self, kpt, psit_xG,
434 Htpsit_xG, dH_asp,
435 dedtaut_R):
436 N = len(psit_xG)
437 S = self.gd.comm.size
438 Q_G = self.pd.Q_qG[kpt.q]
439 Gpsit_xG = np.empty((S,) + psit_xG.shape[1:], dtype=psit_xG.dtype)
440 tmp_xG = np.empty((S,) + psit_xG.shape[1:], dtype=Htpsit_xG.dtype)
441 G_Gv = self.pd.get_reciprocal_vectors(q=kpt.q)
443 dedtaut_R = self.gd.collect(dedtaut_R, broadcast=True)
445 for v in range(3):
446 for n1 in range(0, N, S):
447 n2 = min(n1 + S, N)
448 dn = n2 - n1
449 Gpsit_xG[:dn] = 1j * G_Gv[:, v] * psit_xG[n1:n2]
450 tmp_xG[:] = 0
451 Gpsit_G = self.pd.alltoall1(Gpsit_xG[:dn], kpt.q)
452 if Gpsit_G is not None:
453 a_R = self.pd.ifft(Gpsit_G, kpt.q, local=True, safe=False)
454 a_R *= dedtaut_R
455 self.pd.fftplan.execute()
456 a_R = self.pd.tmp_Q.ravel()[Q_G]
457 else:
458 a_R = self.pd.tmp_G
459 self.pd.alltoall2(a_R, kpt.q, tmp_xG[:dn])
460 axpy(-0.5, (1j * G_Gv[:, v] * tmp_xG[:dn]).ravel(),
461 Htpsit_xG[n1:n2].ravel())
463 def _get_wave_function_array(self, u, n, realspace=True, periodic=False):
464 kpt = self.kpt_u[u]
465 psit_G = kpt.psit_nG[n]
467 if realspace:
468 if psit_G.ndim == 2:
469 psit_R = np.array([self.pd.ifft(psits_G, kpt.q)
470 for psits_G in psit_G])
471 else:
472 psit_R = self.pd.ifft(psit_G, kpt.q)
473 if self.kd.gamma or periodic:
474 return psit_R
476 k_c = self.kd.ibzk_kc[kpt.k]
477 eikr_R = self.gd.plane_wave(k_c)
478 return psit_R * eikr_R
480 return psit_G
482 def get_wave_function_array(self, n, k, s, realspace=True,
483 cut=True, periodic=False):
484 kpt_rank, q = self.kd.get_rank_and_index(k)
485 u = q * self.nspins + s
486 band_rank, myn = self.bd.who_has(n)
488 rank = self.world.rank
489 if (self.kd.comm.rank == kpt_rank and
490 self.bd.comm.rank == band_rank):
491 psit_G = self._get_wave_function_array(u, myn, realspace, periodic)
493 if realspace:
494 psit_G = self.gd.collect(psit_G)
495 else:
496 assert not cut
497 tmp_G = self.pd.gather(psit_G, self.kpt_u[u].q)
498 if tmp_G is not None:
499 ng = self.pd.ngmax
500 if self.collinear:
501 psit_G = np.zeros(ng, complex)
502 else:
503 psit_G = np.zeros((2, ng), complex)
504 psit_G[..., :tmp_G.shape[-1]] = tmp_G
506 if rank == 0:
507 return psit_G
509 # Domain master send this to the global master
510 if self.gd.comm.rank == 0:
511 self.world.ssend(psit_G, 0, 1398)
513 if rank == 0:
514 # allocate full wave function and receive
515 shape = () if self.collinear else (2,)
516 psit_G = self.empty(shape, global_array=True,
517 realspace=realspace)
518 # XXX this will fail when using non-standard nesting
519 # of communicators.
520 world_rank = (kpt_rank * self.gd.comm.size *
521 self.bd.comm.size +
522 band_rank * self.gd.comm.size)
523 self.world.receive(psit_G, world_rank, 1398)
524 return psit_G
526 # We return a number instead of None on all the slaves. Most of
527 # the time the return value will be ignored on the slaves, but
528 # in some cases it will be multiplied by some other number and
529 # then ignored. Allowing for this will simplify some code here
530 # and there.
531 return np.nan
533 def write(self, writer, write_wave_functions=False):
534 FDPWWaveFunctions.write(self, writer)
536 if not write_wave_functions:
537 return
539 if self.collinear:
540 shape = (self.nspins,
541 self.kd.nibzkpts, self.bd.nbands, self.pd.ngmax)
542 else:
543 shape = (self.kd.nibzkpts, self.bd.nbands, 2, self.pd.ngmax)
545 writer.add_array('coefficients', shape, complex)
547 c = Bohr**-1.5
548 for s in range(self.nspins):
549 for k in range(self.kd.nibzkpts):
550 for n in range(self.bd.nbands):
551 psit_G = self.get_wave_function_array(n, k, s,
552 realspace=False,
553 cut=False)
554 writer.fill(psit_G * c)
556 writer.add_array('indices', (self.kd.nibzkpts, self.pd.ngmax),
557 np.int32)
559 if self.bd.comm.rank > 0:
560 return
562 Q_G = np.empty(self.pd.ngmax, np.int32)
563 kk = 0
564 for r in range(self.kd.comm.size):
565 for q, k in enumerate(self.kd.get_indices(r)):
566 ng = self.ng_k[k]
567 if r == self.kd.comm.rank:
568 Q_G[:ng] = self.pd.Q_qG[q]
569 if r > 0:
570 self.kd.comm.send(Q_G, 0)
571 if self.kd.comm.rank == 0:
572 if r > 0:
573 self.kd.comm.receive(Q_G, r)
574 Q_G[ng:] = -1
575 writer.fill(Q_G)
576 assert k == kk
577 kk += 1
579 def read(self, reader):
580 FDPWWaveFunctions.read(self, reader)
582 if 'coefficients' not in reader.wave_functions:
583 return
585 Q_kG = reader.wave_functions.indices
586 for kpt in self.kpt_u:
587 if kpt.s == 0:
588 Q_G = Q_kG[kpt.k]
589 ng = self.ng_k[kpt.k]
590 assert (Q_G[:ng] == self.pd.Q_qG[kpt.q]).all()
591 assert (Q_G[ng:] == -1).all()
593 c = reader.bohr**1.5
594 if reader.version < 0:
595 c = 1 # old gpw file
596 elif reader.version >= 4:
597 c *= self.gd.N_c.prod()
598 for kpt in self.kpt_u:
599 ng = self.ng_k[kpt.k]
600 index = (kpt.s, kpt.k) if self.collinear else (kpt.k,)
601 psit_nG = reader.wave_functions.proxy('coefficients', *index)
602 psit_nG.scale = c
603 psit_nG.length_of_last_dimension = ng
605 kpt.psit = PlaneWaveExpansionWaveFunctions(
606 self.bd.nbands, self.pd, self.dtype, psit_nG,
607 kpt=kpt.q, dist=(self.bd.comm, self.bd.comm.size),
608 spin=kpt.s, collinear=self.collinear)
610 if self.world.size > 1:
611 # Read to memory:
612 for kpt in self.kpt_u:
613 kpt.psit.read_from_file()
614 self.read_from_file_init_wfs_dm = True
616 def hs(self, ham, q=-1, s=0, md=None):
617 npw = len(self.pd.Q_qG[q])
618 N = self.pd.tmp_R.size
620 if md is None:
621 H_GG = np.zeros((npw, npw), complex)
622 S_GG = np.zeros((npw, npw), complex)
623 G1 = 0
624 G2 = npw
625 else:
626 H_GG = md.zeros(dtype=complex)
627 S_GG = md.zeros(dtype=complex)
628 if S_GG.size == 0:
629 return H_GG, S_GG
630 G1, G2 = next(md.my_blocks(S_GG))[:2]
632 H_GG.ravel()[G1::npw + 1] = (0.5 * self.pd.gd.dv / N *
633 self.pd.G2_qG[q][G1:G2])
634 for G in range(G1, G2):
635 x_G = self.pd.zeros(q=q)
636 x_G[G] = 1.0
637 H_GG[G - G1] += (self.pd.gd.dv / N *
638 self.pd.fft(ham.vt_sG[s] *
639 self.pd.ifft(x_G, q), q))
641 if ham.xc.type == 'MGGA':
642 G_Gv = self.pd.get_reciprocal_vectors(q=q)
643 for G in range(G1, G2):
644 x_G = self.pd.zeros(q=q)
645 x_G[G] = 1.0
646 for v in range(3):
647 a_R = self.pd.ifft(1j * G_Gv[:, v] * x_G, q)
648 H_GG[G - G1] += (self.pd.gd.dv / N *
649 (-0.5) * 1j * G_Gv[:, v] *
650 self.pd.fft(ham.xc.dedtaut_sG[s] *
651 a_R, q))
653 S_GG.ravel()[G1::npw + 1] = self.pd.gd.dv / N
655 f_GI = self.pt.expand(q)
656 nI = f_GI.shape[1]
657 dH_II = np.zeros((nI, nI))
658 dS_II = np.zeros((nI, nI))
659 I1 = 0
660 for a in self.pt.my_atom_indices:
661 dH_ii = unpack_hermitian(ham.dH_asp[a][s])
662 dS_ii = self.setups[a].dO_ii
663 I2 = I1 + len(dS_ii)
664 dH_II[I1:I2, I1:I2] = dH_ii / N**2
665 dS_II[I1:I2, I1:I2] = dS_ii / N**2
666 I1 = I2
668 H_GG += np.dot(f_GI[G1:G2].conj(), np.dot(dH_II, f_GI.T))
669 S_GG += np.dot(f_GI[G1:G2].conj(), np.dot(dS_II, f_GI.T))
671 return H_GG, S_GG
673 @timer('Full diag')
674 def diagonalize_full_hamiltonian(self, ham, atoms, log,
675 nbands=None, ecut=None, scalapack=None,
676 expert=False):
678 if self.dtype != complex:
679 raise ValueError(
680 'Please use mode=PW(..., force_complex_dtype=True)')
682 if self.gd.comm.size > 1:
683 raise ValueError(
684 "Please use parallel={'domain': 1}")
686 S = self.bd.comm.size
688 if nbands is None and ecut is None:
689 nbands = self.pd.ngmin // S * S
690 elif nbands is None:
691 ecut /= Ha
692 # XXX I have seen this nbands expression elsewhere,
693 # extract to function!
694 nbands = int(self.gd.volume * ecut**1.5 * 2**0.5 / 3 / pi**2)
696 if nbands % S != 0:
697 nbands += S - nbands % S
699 assert nbands <= self.pd.ngmin
701 if expert:
702 iu = nbands
703 else:
704 iu = None
706 self.bd = bd = BandDescriptor(nbands, self.bd.comm)
707 self.occupations.bd = bd
709 log(f'Diagonalizing full Hamiltonian ({nbands} lowest bands)')
710 log('Matrix size (min, max): {}, {}'.format(self.pd.ngmin,
711 self.pd.ngmax))
712 mem = 3 * self.pd.ngmax**2 * 16 / S / 1024**2
713 log('Approximate memory used per core to store H_GG, S_GG: {:.3f} MB'
714 .format(mem))
715 log('Notice: Up to twice the amount of memory might be allocated\n'
716 'during diagonalization algorithm.')
717 log('The least memory is required when the parallelization is purely\n'
718 'over states (bands) and not k-points, set '
719 "GPAW(..., parallel={'kpt': 1}, ...).")
721 if S > 1:
722 if isinstance(scalapack, (list, tuple)):
723 nprow, npcol, b = scalapack
724 assert nprow * npcol == S, (nprow, npcol, S)
725 else:
726 nprow = int(round(S**0.5))
727 while S % nprow != 0:
728 nprow -= 1
729 npcol = S // nprow
730 b = 64
731 log(f'ScaLapack grid: {nprow}x{npcol},',
732 'block-size:', b)
733 bg = BlacsGrid(bd.comm, S, 1)
734 bg2 = BlacsGrid(bd.comm, nprow, npcol)
735 scalapack = True
736 else:
737 scalapack = False
739 self.set_positions(atoms.get_scaled_positions())
740 self.kpt_u[0].projections = None
741 self.allocate_arrays_for_projections(self.pt.my_atom_indices)
743 myslice = bd.get_slice()
745 pb = ProgressBar(log.fd)
746 nkpt = len(self.kpt_u)
748 for u, kpt in enumerate(self.kpt_u):
749 pb.update(u / nkpt)
750 npw = len(self.pd.Q_qG[kpt.q])
751 if scalapack:
752 mynpw = -(-npw // S)
753 md = BlacsDescriptor(bg, npw, npw, mynpw, npw)
754 md2 = BlacsDescriptor(bg2, npw, npw, b, b)
755 else:
756 md = md2 = MatrixDescriptor(npw, npw)
758 with self.timer('Build H and S'):
759 H_GG, S_GG = self.hs(ham, kpt.q, kpt.s, md)
761 if scalapack:
762 r = Redistributor(bd.comm, md, md2)
763 H_GG = r.redistribute(H_GG)
764 S_GG = r.redistribute(S_GG)
766 psit_nG = md2.empty(dtype=complex)
767 eps_n = np.empty(npw)
769 with self.timer('Diagonalize'):
770 if not scalapack:
771 md2.general_diagonalize_dc(H_GG, S_GG, psit_nG, eps_n,
772 iu=iu)
773 else:
774 md2.general_diagonalize_dc(H_GG, S_GG, psit_nG, eps_n)
775 if eps_n[0] < -1000:
776 msg = f"""Lowest eigenvalue is {eps_n[0]} Hartree.
777You might be suffering from MKL library bug MKLD-11440.
778See issue #241 in GPAW. Creashing to prevent corrupted results."""
779 raise RuntimeError(msg)
781 del H_GG, S_GG
783 kpt.eps_n = eps_n[myslice].copy()
785 if scalapack:
786 md3 = BlacsDescriptor(bg, npw, npw, bd.maxmynbands, npw)
787 r = Redistributor(bd.comm, md2, md3)
788 psit_nG = r.redistribute(psit_nG)
790 kpt.psit = PlaneWaveExpansionWaveFunctions(
791 self.bd.nbands, self.pd, self.dtype,
792 psit_nG[:bd.mynbands].copy(),
793 kpt=kpt.q, dist=(self.bd.comm, self.bd.comm.size),
794 spin=kpt.s, collinear=self.collinear)
795 del psit_nG
797 with self.timer('Projections'):
798 self.pt.integrate(kpt.psit_nG, kpt.P_ani, kpt.q)
800 kpt.f_n = None
802 pb.finish()
804 self.calculate_occupation_numbers()
806 return nbands
808 def initialize_from_lcao_coefficients(self,
809 basis_functions: BasisFunctions,
810 block_size: int = 10) -> None:
811 """Convert from LCAO to PW coefficients."""
812 nlcao = len(self.kpt_qs[0][0].C_nM)
814 # We go from LCAO to real-space and then to PW's.
815 # It's too expensive to allocate one big real-space array:
816 block_size = min(max(nlcao, 1), block_size)
817 psit_nR = self.gd.empty(block_size, self.dtype)
819 for kpt in self.kpt_u:
820 if self.kd.gamma:
821 emikr_R = 1.0
822 else:
823 k_c = self.kd.ibzk_kc[kpt.k]
824 emikr_R = self.gd.plane_wave(-k_c)
825 kpt.psit = PlaneWaveExpansionWaveFunctions(
826 self.bd.nbands, self.pd, self.dtype, kpt=kpt.q,
827 dist=(self.bd.comm, -1, 1),
828 spin=kpt.s, collinear=self.collinear)
829 psit_nG = kpt.psit.array
830 if psit_nG.ndim == 3: # non-collinear calculation
831 N, S, G = psit_nG.shape
832 psit_nG = psit_nG.reshape((N * S, G))
833 for n1 in range(0, nlcao, block_size):
834 n2 = min(n1 + block_size, nlcao)
835 psit_nR[:] = 0.0
836 basis_functions.lcao_to_grid(kpt.C_nM[n1:n2],
837 psit_nR[:n2 - n1],
838 kpt.q,
839 block_size)
840 for psit_R, psit_G in zip(psit_nR, psit_nG[n1:n2]):
841 psit_G[:] = self.pd.fft(psit_R * emikr_R, kpt.q)
842 kpt.C_nM = None
844 def random_wave_functions(self, mynao):
845 rs = np.random.RandomState(self.world.rank)
846 for kpt in self.kpt_u:
847 if kpt.psit is None:
848 kpt.psit = PlaneWaveExpansionWaveFunctions(
849 self.bd.nbands, self.pd, self.dtype, kpt=kpt.q,
850 dist=(self.bd.comm, -1, 1),
851 spin=kpt.s, collinear=self.collinear)
853 array = kpt.psit.array[mynao:]
854 weight_G = 1.0 / (1.0 + self.pd.G2_qG[kpt.q])
855 array.real = rs.uniform(-1, 1, array.shape) * weight_G
856 array.imag = rs.uniform(-1, 1, array.shape) * weight_G
857 if self.gd.comm.rank == 0:
858 array[:, 0].imag = 0.0
860 def estimate_memory(self, mem):
861 FDPWWaveFunctions.estimate_memory(self, mem)
862 self.pd.estimate_memory(mem.subnode('PW-descriptor'))
864 def get_kinetic_stress(self):
865 sigma_vv = np.zeros((3, 3), dtype=complex)
866 pd = self.pd
867 dOmega = pd.gd.dv / pd.gd.N_c.prod()
868 if pd.dtype == float:
869 dOmega *= 2
870 K_qv = self.pd.K_qv
871 for kpt in self.kpt_u:
872 G_Gv = pd.get_reciprocal_vectors(q=kpt.q, add_q=False)
873 psit2_G = 0.0
874 for n, f in enumerate(kpt.f_n):
875 psit2_G += f * np.abs(kpt.psit_nG[n])**2
876 for alpha in range(3):
877 Ga_G = G_Gv[:, alpha] + K_qv[kpt.q, alpha]
878 for beta in range(3):
879 Gb_G = G_Gv[:, beta] + K_qv[kpt.q, beta]
880 sigma_vv[alpha, beta] += (psit2_G * Ga_G * Gb_G).sum()
882 sigma_vv *= -dOmega
883 self.world.sum(sigma_vv)
884 return sigma_vv