Coverage for gpaw/eigensolvers/davidson.py: 97%
147 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-14 00:18 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-14 00:18 +0000
1from functools import partial
3import numpy as np
4from ase.utils.timing import timer
5from gpaw import debug
6from gpaw.eigensolvers.diagonalizerbackend import (ScalapackDiagonalizer,
7 ElpaDiagonalizer,
8 ScipyDiagonalizer)
9from gpaw.eigensolvers.eigensolver import Eigensolver
10from gpaw.hybrids import HybridXC
11from gpaw.matrix import matrix_matrix_multiply as mmm
14class DummyArray:
15 def __getitem__(self, x):
16 return np.empty((0, 0))
19class Davidson(Eigensolver):
20 """Simple Davidson eigensolver
22 It is expected that the trial wave functions are orthonormal
23 and the integrals of projector functions and wave functions
24 ``nucleus.P_uni`` are already calculated.
26 Solution steps are:
28 * Subspace diagonalization
29 * Calculate all residuals
30 * Add preconditioned residuals to the subspace and diagonalize
31 """
33 def __init__(
34 self, niter=2):
35 super().__init__()
36 self.niter = niter
37 self.diagonalizer_backend = None
39 self.orthonormalization_required = False
40 self.H_NN = DummyArray()
41 self.S_NN = DummyArray()
42 self.eps_N = DummyArray()
44 def __repr__(self):
45 return f'Davidson(niter={self.niter})'
47 def todict(self):
48 return {'name': 'dav', 'niter': self.niter}
50 def initialize(self, wfs, dist_backend='scalapack'):
51 # dist_backend keyword exists due to this class having
52 # no reference to the parallelization backend. As such,
53 # the keyword must be specified by the user upon creation
54 # of this object. Usually one would want this to be ELPA
55 # if use_elpa in parallel is set to True.
56 dist_diagonalizers = {
57 'scalapack': ScalapackDiagonalizer,
58 'elpa': ElpaDiagonalizer
59 }
61 super().initialize(wfs)
62 slcomm, nrows, ncols, slsize = wfs.scalapack_parameters
64 if wfs.gd.comm.rank == 0 and wfs.bd.comm.rank == 0:
65 # Allocate arrays
66 B = self.nbands
67 self.H_NN = np.zeros((2 * B, 2 * B), self.dtype)
68 self.S_NN = np.zeros((2 * B, 2 * B), self.dtype)
69 self.eps_N = np.zeros(2 * B)
71 if slsize is not None:
72 self.diagonalizer_backend = dist_diagonalizers[dist_backend](
73 arraysize=self.nbands * 2,
74 grid_nrows=nrows,
75 grid_ncols=ncols,
76 scalapack_communicator=slcomm,
77 dtype=self.dtype,
78 blocksize=slsize)
79 else:
80 self.diagonalizer_backend = ScipyDiagonalizer(slcomm)
82 def estimate_memory(self, mem, wfs):
83 super().estimate_memory(mem, wfs)
84 nbands = wfs.bd.nbands
85 mem.subnode('H_nn', nbands * nbands * mem.itemsize[wfs.dtype])
86 mem.subnode('S_nn', nbands * nbands * mem.itemsize[wfs.dtype])
87 mem.subnode('H_2n2n', 4 * nbands * nbands * mem.itemsize[wfs.dtype])
88 mem.subnode('S_2n2n', 4 * nbands * nbands * mem.itemsize[wfs.dtype])
89 mem.subnode('eps_2n', 2 * nbands * mem.floatsize)
91 @timer('Davidson')
92 def iterate_one_k_point(self, ham, wfs, kpt, weights):
93 """Do Davidson iterations for the kpoint"""
94 if isinstance(ham.xc, HybridXC):
95 niter = 1
96 else:
97 niter = self.niter
99 bd = wfs.bd
100 B = bd.nbands
102 H_NN = self.H_NN
103 S_NN = self.S_NN
104 eps_N = self.eps_N
106 def integrate(a_G):
107 if wfs.collinear:
108 return np.real(wfs.integrate(a_G, a_G, global_integral=False))
109 return sum(
110 np.real(wfs.integrate(b_G, b_G, global_integral=False))
111 for b_G in a_G)
113 self.subspace_diagonalize(ham, wfs, kpt)
115 psit = kpt.psit
116 psit2 = psit.new(buf=wfs.work_array)
117 P = kpt.projections
118 P2 = P.new()
119 P3 = P.new()
120 M = wfs.work_matrix_nn
121 dS = wfs.setups.dS
122 comm = wfs.gd.comm
124 is_gridband_master = (comm.rank == 0) and (bd.comm.rank == 0)
126 if bd.comm.size > 1:
127 M0 = M.new(dist=(bd.comm, 1, 1))
128 else:
129 M0 = M
131 if comm.rank == 0:
132 e_N = bd.collect(kpt.eps_n)
133 if e_N is not None:
134 eps_N[:B] = e_N
136 Ht = partial(wfs.apply_pseudo_hamiltonian, kpt, ham)
138 if self.keep_htpsit:
139 R = psit.new(buf=self.Htpsit_nG)
140 else:
141 R = psit.apply(Ht)
143 self.calculate_residuals(kpt, wfs, ham, psit, P, kpt.eps_n, R, P2)
145 precond = self.preconditioner
147 for nit in range(niter):
148 if nit == niter - 1:
149 error = np.dot(weights, [integrate(R_G) for R_G in R.array])
151 for psit_G, R_G, psit2_G in zip(psit.array, R.array, psit2.array):
152 ekin = precond.calculate_kinetic_energy(psit_G, kpt)
153 precond(R_G, kpt, ekin, out=psit2_G)
155 # Calculate projections
156 psit2.matrix_elements(wfs.pt, out=P2)
158 def copy(M, C_nn):
159 comm.sum(M.array, 0)
160 if comm.rank == 0:
161 M.complex_conjugate()
162 M.redist(M0)
163 if bd.comm.rank == 0:
164 C_nn[:] = M0.array
166 with self.timer('calc. matrices'):
167 # <psi2 | H | psi2>
168 psit2.matrix_elements(
169 operator=Ht, result=R, out=M, symmetric=True, cc=True)
170 ham.dH(P2, out=P3)
171 mmm(1.0, P2, 'N', P3, 'C', 1.0, M) # , symmetric=True)
172 copy(M, H_NN[B:, B:])
174 # <psi2 | H | psi>
175 R.matrix_elements(psit, out=M, cc=True)
176 mmm(1.0, P3, 'N', P, 'C', 1.0, M)
177 copy(M, H_NN[B:, :B])
179 # <psi2 | S | psi2>
180 psit2.matrix_elements(out=M, symmetric=True, cc=True)
181 dS.apply(P2, out=P3)
182 mmm(1.0, P2, 'N', P3, 'C', 1.0, M)
183 copy(M, S_NN[B:, B:])
185 # <psi2 | S | psi>
186 psit2.matrix_elements(psit, out=M, cc=True)
187 mmm(1.0, P3, 'N', P, 'C', 1.0, M)
188 copy(M, S_NN[B:, :B])
190 if is_gridband_master:
191 H_NN[:B, :B] = np.diag(eps_N[:B])
192 S_NN[:B, :B] = np.eye(B)
194 with self.timer('diagonalize'):
195 if is_gridband_master and debug:
196 H_NN[np.triu_indices(2 * B, 1)] = 42.0
197 S_NN[np.triu_indices(2 * B, 1)] = 42.0
199 try:
200 self.diagonalizer_backend.diagonalize(
201 H_NN, S_NN, eps_N, debug=debug)
202 except np.linalg.LinAlgError as ex:
203 raise ValueError(
204 'Too few plane waves or grid points') from ex
205 if comm.rank == 0:
206 bd.distribute(eps_N[:B], kpt.eps_n)
207 comm.broadcast(kpt.eps_n, 0)
209 with self.timer('rotate_psi'):
210 if comm.rank == 0:
211 if bd.comm.rank == 0:
212 M0.array[:] = H_NN[:B, :B].T
213 M0.redist(M)
214 comm.broadcast(M.array, 0)
215 mmm(1.0, M, 'N', psit, 'N', 0.0, R)
216 mmm(1.0, M, 'N', P, 'N', 0.0, P3)
217 if comm.rank == 0:
218 if bd.comm.rank == 0:
219 M0.array[:] = H_NN[B:, :B].T
220 M0.redist(M)
221 comm.broadcast(M.array, 0)
222 mmm(1.0, M, 'N', psit2, 'N', 1.0, R)
223 mmm(1.0, M, 'N', P2, 'N', 1.0, P3)
224 psit[:] = R
225 P, P3 = P3, P
226 kpt.projections = P
228 if nit < niter - 1:
229 psit.apply(Ht, out=R)
230 self.calculate_residuals(
231 kpt, wfs, ham, psit, P, kpt.eps_n, R, P2)
233 error = wfs.gd.comm.sum_scalar(error)
234 return error