Coverage for gpaw/wavefunctions/base.py: 79%
544 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-08 00:17 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-08 00:17 +0000
1import numpy as np
2from ase.units import Ha
4from gpaw.projections import Projections
5from gpaw.utilities import pack_density
6from gpaw.utilities.blas import axpy, mmm
7from gpaw.utilities.partition import AtomPartition
10class WaveFunctions:
11 """...
13 setups:
14 List of setup objects.
15 symmetry:
16 Symmetry object.
17 kpt_u:
18 List of **k**-point objects.
19 nbands: int
20 Number of bands.
21 nspins: int
22 Number of spins.
23 dtype: dtype
24 Data type of wave functions (float or complex).
25 bzk_kc: ndarray
26 Scaled **k**-points used for sampling the whole
27 Brillouin zone - values scaled to [-0.5, 0.5).
28 ibzk_kc: ndarray
29 Scaled **k**-points in the irreducible part of the
30 Brillouin zone.
31 weight_k: ndarray
32 Weights of the **k**-points in the irreducible part
33 of the Brillouin zone (summing up to 1).
34 kpt_comm:
35 MPI-communicator for parallelization over **k**-points.
36 """
38 def __init__(self, gd, nvalence, setups, bd, dtype, collinear,
39 world, kd, kptband_comm, timer):
40 self.gd = gd
41 self.nspins = kd.nspins
42 self.collinear = collinear
43 self.nvalence = nvalence
44 self.bd = bd
45 self.dtype = dtype
46 assert dtype == float or dtype == complex
47 self.world = world
48 self.kd = kd
49 self.kptband_comm = kptband_comm
50 self.timer = timer
51 self.atom_partition = None
53 self.kpt_qs = kd.create_k_points(self.gd.sdisp_cd, collinear)
54 self.kpt_u = [kpt for kpt_s in self.kpt_qs for kpt in kpt_s]
56 self.occupations = None
57 self.fermi_levels = None
59 self.eigensolver = None
60 self.positions_set = False
61 self.spos_ac = None
63 self.set_setups(setups)
65 @property
66 def fermi_level(self):
67 assert len(self.fermi_levels) == 1
68 return self.fermi_levels[0]
70 def summary(self, log):
71 log(eigenvalue_string(self))
73 func = None
74 if hasattr(self.eigensolver, 'dm_helper'):
75 func = getattr(self.eigensolver.dm_helper.func, 'name', None)
76 elif hasattr(self.eigensolver, 'odd'):
77 func = getattr(self.eigensolver.odd, 'name', None)
78 if func is None:
79 pass
80 elif 'SIC' in func:
81 self.summary_func(log)
83 if self.fermi_levels is None:
84 return
86 if len(self.fermi_levels) == 1:
87 log(f'Fermi level: {self.fermi_levels[0] * Ha:.5f}\n')
88 else:
89 f1, f2 = (f * Ha for f in self.fermi_levels)
90 log(f'Fermi levels: {f1:.5f}, {f2:.5f}\n')
92 def set_setups(self, setups):
93 self.setups = setups
95 def set_eigensolver(self, eigensolver):
96 self.eigensolver = eigensolver
98 def add_realspace_orbital_to_density(self, nt_G, psit_G):
99 if psit_G.dtype == float:
100 axpy(1.0, psit_G**2, nt_G)
101 else:
102 assert psit_G.dtype == complex
103 axpy(1.0, psit_G.real**2, nt_G)
104 axpy(1.0, psit_G.imag**2, nt_G)
106 def add_orbital_density(self, nt_G, kpt, n):
107 self.add_realspace_orbital_to_density(nt_G, kpt.psit_nG[n])
109 def calculate_band_energy(self):
110 e_band = 0.0
111 for kpt in self.kpt_u:
112 e_band += np.dot(kpt.f_n, kpt.eps_n)
114 try: # DCSF needs this ...
115 e_band += self.occupations.calculate_band_energy(self)
116 except AttributeError:
117 pass
119 return self.kptband_comm.sum_scalar(e_band)
121 def calculate_density_contribution(self, nt_sG):
122 """Calculate contribution to pseudo density from wave functions.
124 Array entries are written to (not added to)."""
125 nt_sG.fill(0.0)
126 for kpt in self.kpt_u:
127 self.add_to_density_from_k_point(nt_sG, kpt)
128 self.kptband_comm.sum(nt_sG)
130 self.timer.start('Symmetrize density')
131 for nt_G in nt_sG:
132 self.kd.symmetry.symmetrize(nt_G, self.gd)
133 self.timer.stop('Symmetrize density')
135 def add_to_density_from_k_point(self, nt_sG, kpt):
136 self.add_to_density_from_k_point_with_occupation(nt_sG, kpt, kpt.f_n)
138 def get_orbital_density_matrix(self, a, kpt, n):
139 """Add the nth band density from kpt to density matrix D_sp"""
140 ni = self.setups[a].ni
141 D_sii = np.zeros((self.nspins, ni, ni))
142 P_i = kpt.P_ani[a][n]
143 D_sii[kpt.s] += np.outer(P_i.conj(), P_i).real
144 D_sp = [pack_density(D_ii) for D_ii in D_sii]
145 return D_sp
147 def calculate_atomic_density_matrices_k_point(self, D_sii, kpt, a, f_n):
148 if kpt.rho_MM is not None:
149 P_Mi = self.P_aqMi[a][kpt.q]
150 rhoP_Mi = np.zeros_like(P_Mi)
151 D_ii = np.zeros(D_sii[kpt.s].shape, kpt.rho_MM.dtype)
152 mmm(1.0, kpt.rho_MM, 'N', P_Mi, 'N', 0.0, rhoP_Mi)
153 mmm(1.0, P_Mi, 'C', rhoP_Mi, 'N', 0.0, D_ii)
154 D_sii[kpt.s] += D_ii.real
155 else:
156 if self.collinear:
157 P_ni = kpt.projections[a]
158 D_sii[kpt.s] += np.dot(P_ni.T.conj() * f_n, P_ni).real
159 else:
160 P_nsi = kpt.projections[a]
161 D_ssii = np.einsum('nsi,n,nzj->szij',
162 P_nsi.conj(), f_n, P_nsi)
163 D_sii[0] += (D_ssii[0, 0] + D_ssii[1, 1]).real
164 D_sii[1] += 2 * D_ssii[0, 1].real
165 D_sii[2] += 2 * D_ssii[0, 1].imag
166 D_sii[3] += (D_ssii[0, 0] - D_ssii[1, 1]).real
168 if hasattr(kpt, 'c_on'):
169 for ne, c_n in zip(kpt.ne_o, kpt.c_on):
170 d_nn = ne * np.outer(c_n.conj(), c_n)
171 D_sii[kpt.s] += np.dot(P_ni.T.conj(), np.dot(d_nn, P_ni)).real
173 def calculate_atomic_density_matrices(self, D_asp):
174 """Calculate atomic density matrices from projections."""
175 f_un = [kpt.f_n for kpt in self.kpt_u]
176 self.calculate_atomic_density_matrices_with_occupation(D_asp, f_un)
178 def calculate_atomic_density_matrices_with_occupation(self, D_asp, f_un):
179 """Calculate atomic density matrices from projections with
180 custom occupation f_un."""
182 # Parameter check (if user accidentally passes f_n instead of f_un)
183 if f_un[0] is not None: # special case for transport calculations...
184 assert isinstance(f_un[0], np.ndarray)
185 # Varying f_n used in calculation of response part of GLLB-potential
186 for a, D_sp in D_asp.items():
187 ni = self.setups[a].ni
188 D_sii = np.zeros((len(D_sp), ni, ni))
189 for f_n, kpt in zip(f_un, self.kpt_u):
190 self.calculate_atomic_density_matrices_k_point(D_sii, kpt, a,
191 f_n)
192 D_sp[:] = [pack_density(D_ii) for D_ii in D_sii]
193 self.kptband_comm.sum(D_sp)
195 self.symmetrize_atomic_density_matrices(D_asp)
197 def symmetrize_atomic_density_matrices(self, D_asp):
198 if len(self.kd.symmetry.op_scc) == 0:
199 return
201 D_asp.redistribute(self.atom_partition.as_serial())
202 self.setups.atomrotations.symmetrize_atomic_density_matrices(
203 D_asp, a_sa=self.kd.symmetry.a_sa)
204 D_asp.redistribute(self.atom_partition)
206 def calculate_occupation_numbers(self, fix_fermi_level=False):
207 if self.collinear and self.nspins == 1:
208 degeneracy = 2
209 else:
210 degeneracy = 1
212 f_qn, fermi_levels, e_entropy = self.occupations.calculate(
213 nelectrons=self.nvalence / degeneracy,
214 eigenvalues=[kpt.eps_n * Ha for kpt in self.kpt_u],
215 weights=[kpt.weightk for kpt in self.kpt_u],
216 fermi_levels_guess=(self.fermi_levels * Ha
217 if self.fermi_levels is not None else None),
218 fix_fermi_level=fix_fermi_level)
220 if not fix_fermi_level or self.fermi_levels is None:
221 self.fermi_levels = np.array(fermi_levels) / Ha
223 for f_n, kpt in zip(f_qn, self.kpt_u):
224 kpt.f_n = f_n * (kpt.weightk * degeneracy)
226 return e_entropy * degeneracy / Ha
228 def set_positions(self, spos_ac, atom_partition=None):
229 self.positions_set = False
230 # rank_a = self.gd.get_ranks_from_positions(spos_ac)
231 # atom_partition = AtomPartition(self.gd.comm, rank_a)
232 # XXX pass AtomPartition around instead of spos_ac?
233 # All the classes passing around spos_ac end up needing the ranks
234 # anyway.
236 if atom_partition is None:
237 rank_a = self.gd.get_ranks_from_positions(spos_ac)
238 atom_partition = AtomPartition(self.gd.comm, rank_a)
240 if self.atom_partition is not None and self.kpt_u[0].P_ani is not None:
241 with self.timer('Redistribute'):
242 for kpt in self.kpt_u:
243 P = kpt.projections
244 assert self.atom_partition == P.atom_partition
245 kpt.projections = P.redist(atom_partition)
246 assert atom_partition == kpt.projections.atom_partition
248 self.atom_partition = atom_partition
249 self.kd.symmetry.check(spos_ac)
250 self.spos_ac = spos_ac
252 def allocate_arrays_for_projections(self, my_atom_indices): # XXX unused
253 if not self.positions_set and self.kpt_u[0]._projections is not None:
254 # Projections have been read from file - don't delete them!
255 pass
256 else:
257 nproj_a = [setup.ni for setup in self.setups]
258 for kpt in self.kpt_u:
259 kpt.projections = Projections(
260 self.bd.nbands, nproj_a,
261 self.atom_partition,
262 self.bd.comm,
263 collinear=self.collinear, spin=kpt.s, dtype=self.dtype)
265 def collect_eigenvalues(self, k, s):
266 return self.collect_array('eps_n', k, s)
268 def collect_occupations(self, k, s):
269 return self.collect_array('f_n', k, s)
271 def collect_array(self, name, k, s, subset=None):
272 """Helper method for collect_eigenvalues and collect_occupations.
274 For the parallel case find the rank in kpt_comm that contains
275 the (k,s) pair, for this rank, collect on the corresponding
276 domain a full array on the domain master and send this to the
277 global master."""
279 kpt_qs = self.kpt_qs
280 kpt_rank, q = self.kd.get_rank_and_index(k)
281 if self.kd.comm.rank == kpt_rank:
282 a_nx = getattr(kpt_qs[q][s], name)
284 if subset is not None:
285 a_nx = a_nx[subset]
287 # Domain master send this to the global master
288 if self.gd.comm.rank == 0:
289 if self.bd.comm.size == 1:
290 if kpt_rank == 0:
291 return a_nx
292 else:
293 self.kd.comm.ssend(a_nx, 0, 1301)
294 else:
295 b_nx = self.bd.collect(a_nx)
296 if self.bd.comm.rank == 0:
297 if kpt_rank == 0:
298 return b_nx
299 else:
300 self.kd.comm.ssend(b_nx, 0, 1301)
302 elif self.world.rank == 0 and kpt_rank != 0:
303 # Only used to determine shape and dtype of receiving buffer:
304 a_nx = getattr(kpt_qs[0][0], name)
306 if subset is not None:
307 a_nx = a_nx[subset]
309 b_nx = np.zeros((self.bd.nbands,) + a_nx.shape[1:],
310 dtype=a_nx.dtype)
311 self.kd.comm.receive(b_nx, kpt_rank, 1301)
312 return b_nx
314 return np.zeros(0) # see comment in get_wave_function_array() method
316 def collect_auxiliary(self, value, k, s, shape=1, dtype=float):
317 """Helper method for collecting band-independent scalars/arrays.
319 For the parallel case find the rank in kpt_comm that contains
320 the (k,s) pair, for this rank, collect on the corresponding
321 domain a full array on the domain master and send this to the
322 global master."""
324 kpt_rank, q = self.kd.get_rank_and_index(k)
326 if self.kd.comm.rank == kpt_rank:
327 if isinstance(value, str):
328 a_o = getattr(self.kpt_qs[q][s], value)
329 else:
330 u = q * self.nspins + s
331 a_o = value[u] # assumed list
333 # Make sure data is a mutable object
334 a_o = np.asarray(a_o)
336 if a_o.dtype is not dtype:
337 a_o = a_o.astype(dtype)
339 # Domain master send this to the global master
340 if self.gd.comm.rank == 0:
341 if kpt_rank == 0:
342 return a_o
343 else:
344 self.kd.comm.send(a_o, 0, 1302)
346 elif self.world.rank == 0 and kpt_rank != 0:
347 b_o = np.zeros(shape, dtype=dtype)
348 self.kd.comm.receive(b_o, kpt_rank, 1302)
349 return b_o
351 def collect_projections(self, k, s):
352 """Helper method for collecting projector overlaps across domains.
354 For the parallel case find the rank in kpt_comm that contains
355 the (k,s) pair, for this rank, send to the global master."""
357 kpt_rank, q = self.kd.get_rank_and_index(k)
359 if self.kd.comm.rank == kpt_rank:
360 kpt = self.kpt_qs[q][s]
361 P_nI = kpt.projections.collect()
362 if self.world.rank == 0:
363 return P_nI
364 if P_nI is not None:
365 self.kd.comm.send(np.ascontiguousarray(P_nI), 0, tag=117)
366 if self.world.rank == 0:
367 nproj = sum(setup.ni for setup in self.setups)
368 if not self.collinear:
369 nproj *= 2
370 P_nI = np.empty((self.bd.nbands, nproj), self.dtype)
371 self.kd.comm.receive(P_nI, kpt_rank, tag=117)
372 return P_nI
374 def get_wave_function_array(self, n, k, s, realspace=True, periodic=False):
375 """Return pseudo-wave-function array on master.
377 n: int
378 Global band index.
379 k: int
380 Global IBZ k-point index.
381 s: int
382 Spin index (0 or 1).
383 realspace: bool
384 Transform plane wave or LCAO expansion coefficients to real-space.
386 For the parallel case find the ranks in kd.comm and bd.comm
387 that contains to (n, k, s), and collect on the corresponding
388 domain a full array on the domain master and send this to the
389 global master."""
391 kpt_rank, q = self.kd.get_rank_and_index(k)
392 band_rank, myn = self.bd.who_has(n)
394 rank = self.world.rank
396 if (self.kd.comm.rank == kpt_rank and
397 self.bd.comm.rank == band_rank):
398 u = q * self.nspins + s
399 psit_G = self._get_wave_function_array(u, myn,
400 realspace, periodic)
402 if realspace:
403 psit_G = self.gd.collect(psit_G)
405 if rank == 0:
406 return psit_G
408 # Domain master send this to the global master
409 if self.gd.comm.rank == 0:
410 psit_G = np.ascontiguousarray(psit_G)
411 self.world.ssend(psit_G, 0, 1398)
413 if rank == 0:
414 # allocate full wave function and receive
415 shape = () if self.collinear else (2,)
416 psit_G = self.empty(shape, global_array=True,
417 realspace=realspace)
418 # XXX this will fail when using non-standard nesting
419 # of communicators.
420 world_rank = (kpt_rank * self.gd.comm.size *
421 self.bd.comm.size +
422 band_rank * self.gd.comm.size)
423 self.world.receive(psit_G, world_rank, 1398)
424 return psit_G
426 # We return a number instead of None on all the slaves. Most of
427 # the time the return value will be ignored on the slaves, but
428 # in some cases it will be multiplied by some other number and
429 # then ignored. Allowing for this will simplify some code here
430 # and there.
431 return np.nan
433 def get_homo_lumo(self, spin=None, _gllb=False):
434 """Return HOMO and LUMO eigenvalues."""
435 if spin is None:
436 if self.nspins == 1:
437 return self.get_homo_lumo(0)
438 h0, l0 = self.get_homo_lumo(0)
439 h1, l1 = self.get_homo_lumo(1)
440 return np.array([max(h0, h1), min(l0, l1)])
442 if _gllb:
443 # Backwards compatibility (see test/gllb/test_metallic.py test)
444 n = self.nvalence // 2
445 else:
446 nocc = 0.0
447 for kpt in self.kpt_u:
448 if kpt.s == spin:
449 nocc += kpt.f_n.sum()
450 nocc = self.kptband_comm.sum_scalar(nocc) * self.nspins / 2
451 n = int(round(nocc))
453 band_rank, myn = self.bd.who_has(n - 1)
454 homo = -np.inf
455 if self.bd.comm.rank == band_rank:
456 for kpt in self.kpt_u:
457 if kpt.s == spin:
458 homo = max(kpt.eps_n[myn], homo)
459 homo = self.world.max_scalar(homo)
461 lumo = np.inf
462 if n < self.bd.nbands: # there are not enough bands for LUMO
463 band_rank, myn = self.bd.who_has(n)
464 if self.bd.comm.rank == band_rank:
465 for kpt in self.kpt_u:
466 if kpt.s == spin:
467 lumo = min(kpt.eps_n[myn], lumo)
468 lumo = self.world.min_scalar(lumo)
470 return np.array([homo, lumo])
472 def write(self, writer):
473 writer.write(version=2, ha=Ha)
474 writer.write(fermi_levels=self.fermi_levels * Ha)
475 writer.write(kpts=self.kd)
476 self.write_projections(writer)
477 self.write_eigenvalues(writer)
478 self.write_occupations(writer)
480 def write_projections(self, writer):
481 nproj = sum(setup.ni for setup in self.setups)
483 if self.collinear:
484 shape = (self.nspins, self.kd.nibzkpts, self.bd.nbands, nproj)
485 else:
486 shape = (self.kd.nibzkpts, self.bd.nbands, 2, nproj)
488 writer.add_array('projections', shape, self.dtype)
490 for s in range(self.nspins):
491 for k in range(self.kd.nibzkpts):
492 P_nI = self.collect_projections(k, s)
493 if not self.collinear and P_nI is not None:
494 P_nI.shape = (self.bd.nbands, 2, nproj)
495 writer.fill(P_nI)
497 def write_eigenvalues(self, writer):
498 if self.collinear:
499 shape = (self.nspins, self.kd.nibzkpts, self.bd.nbands)
500 else:
501 shape = (self.kd.nibzkpts, self.bd.nbands)
503 writer.add_array('eigenvalues', shape)
504 for s in range(self.nspins):
505 for k in range(self.kd.nibzkpts):
506 writer.fill(self.collect_eigenvalues(k, s) * Ha)
508 def write_occupations(self, writer):
510 if self.collinear:
511 shape = (self.nspins, self.kd.nibzkpts, self.bd.nbands)
512 else:
513 shape = (self.kd.nibzkpts, self.bd.nbands)
515 writer.add_array('occupations', shape)
516 for s in range(self.nspins):
517 for k in range(self.kd.nibzkpts):
518 # Scale occupation numbers when writing:
519 # XXX fix this in the code also ...
520 weight = self.kd.weight_k[k] * 2 / self.nspins
521 writer.fill(self.collect_occupations(k, s) / weight)
523 def read(self, reader):
524 wfs_reader = reader.wave_functions
525 # Backward compatibility:
526 # Take parameters from main reader
527 if 'ha' not in wfs_reader:
528 wfs_reader.ha = reader.ha
529 if 'version' not in wfs_reader:
530 wfs_reader.version = reader.version
532 if reader.version >= 3:
533 self.fermi_levels = wfs_reader.fermi_levels / wfs_reader.ha
534 else:
535 o = reader.occupations
536 self.fermi_levels = np.array(
537 [o.fermilevel + o.split / 2,
538 o.fermilevel - o.split / 2]) / wfs_reader.ha
539 if self.occupations.name != 'fixmagmom':
540 assert o.split == 0.0
541 self.fermi_levels = self.fermi_levels[:1]
543 if reader.version >= 2:
544 kpts = wfs_reader.kpts
545 assert np.allclose(kpts.ibzkpts, self.kd.ibzk_kc)
546 assert np.allclose(kpts.bzkpts, self.kd.bzk_kc)
547 assert (kpts.bz2ibz == self.kd.bz2ibz_k).all()
548 assert np.allclose(kpts.weights, self.kd.weight_k)
550 if 'projections' in wfs_reader:
551 self.read_projections(wfs_reader)
552 self.read_eigenvalues(wfs_reader, wfs_reader.version <= 0)
553 self.read_occupations(wfs_reader, wfs_reader.version <= 0)
555 def read_projections(self, reader):
556 nslice = self.bd.get_slice()
557 nproj_a = [setup.ni for setup in self.setups]
558 atom_partition = AtomPartition(self.gd.comm,
559 np.zeros(len(nproj_a), int))
560 for u, kpt in enumerate(self.kpt_u):
561 if self.collinear:
562 index = (kpt.s, kpt.k)
563 else:
564 index = (kpt.k,)
565 kpt.projections = Projections(
566 self.bd.nbands, nproj_a,
567 atom_partition, self.bd.comm,
568 collinear=self.collinear, spin=kpt.s, dtype=self.dtype)
569 if self.gd.comm.rank == 0:
570 P_nI = reader.proxy('projections', *index)[nslice]
571 if not self.collinear:
572 P_nI.shape = (self.bd.mynbands, -1)
573 kpt.projections.matrix.array[:] = P_nI
575 def read_eigenvalues(self, reader, old=False):
576 nslice = self.bd.get_slice()
577 for u, kpt in enumerate(self.kpt_u):
578 if self.collinear:
579 index = (kpt.s, kpt.k)
580 else:
581 index = (kpt.k,)
582 eps_n = reader.proxy('eigenvalues', *index)[nslice]
583 x = self.bd.mynbands - len(eps_n) # missing bands?
584 if x > 0:
585 # Working on a real fix to this parallelization problem ...
586 eps_n = np.pad(eps_n, (0, x), 'constant')
587 if not old: # skip for old tar-files gpw's
588 eps_n /= reader.ha
589 kpt.eps_n = eps_n
591 def read_occupations(self, reader, old=False):
592 nslice = self.bd.get_slice()
593 for u, kpt in enumerate(self.kpt_u):
594 if self.collinear:
595 index = (kpt.s, kpt.k)
596 else:
597 index = (kpt.k,)
598 f_n = reader.proxy('occupations', *index)[nslice]
599 x = self.bd.mynbands - len(f_n) # missing bands?
600 if x > 0:
601 # Working on a real fix to this parallelization problem ...
602 f_n = np.pad(f_n, (0, x), 'constant')
603 if not old: # skip for old tar-files gpw's
604 f_n *= kpt.weight
605 kpt.f_n = f_n
607 def summary_func(self, log):
609 pot = None
610 if hasattr(self.eigensolver, 'dm_helper'):
611 pot = self.eigensolver.dm_helper.func
612 elif hasattr(self.eigensolver, 'odd'):
613 pot = self.eigensolver.odd
615 f_sn = {}
616 for kpt in self.kpt_u:
617 u = kpt.s * self.kd.nibzkpts + kpt.q
618 f_sn[u] = kpt.f_n
620 log("Diagonal elements of Lagrange matrix:")
621 if self.nspins == 1:
622 header = " Band L_ii " \
623 "Occupancy"
624 log(header)
626 lagr = pot.lagr_diag_s[0]
627 for i, x in enumerate(lagr):
628 log('%5d %11.5f %9.5f' % (
629 i, Ha * x, f_sn[0][i]))
631 if self.nspins == 2:
632 if self.kd.comm.size > 1:
633 if self.kd.comm.rank == 0:
634 # occupation numbers
635 size = np.array([0])
636 self.kd.comm.receive(size, 1)
637 f_2n = np.zeros(shape=(int(size[0])))
638 self.kd.comm.receive(f_2n, 1)
639 f_sn[1] = f_2n
641 # orbital energies
642 size = np.array([0])
643 self.kd.comm.receive(size, 1)
644 lagr_1 = np.zeros(shape=(int(size[0])))
645 self.kd.comm.receive(lagr_1, 1)
647 else:
648 # occupations
649 size = np.array([f_sn[1].shape[0]])
650 self.kd.comm.send(size, 0)
651 self.kd.comm.send(f_sn[1], 0)
653 # orbital energies
654 a = pot.lagr_diag_s[1].copy()
655 size = np.array([a.shape[0]])
656 self.kd.comm.send(size, 0)
657 self.kd.comm.send(a, 0)
659 del a
661 if self.kd.comm.rank == 0:
662 log(' Up '
663 ' Down')
664 log(' Band L_ii Occupancy '
665 ' Band L_ii Occupancy')
667 lagr_0 = np.sort(pot.lagr_diag_s[0])
668 lagr_labeled_0 = {}
669 for c, x in enumerate(pot.lagr_diag_s[0]):
670 lagr_labeled_0[str(round(x, 12))] = c
672 if self.kd.comm.size == 1:
673 lagr_1 = np.sort(pot.lagr_diag_s[1])
674 lagr_labeled_1 = {}
675 for c, x in enumerate(
676 pot.lagr_diag_s[1]):
677 lagr_labeled_1[str(round(x, 12))] = c
678 else:
679 lagr_labeled_1 = {}
680 for c, x in enumerate(lagr_1):
681 lagr_labeled_1[str(round(x, 12))] = c
682 lagr_1 = np.sort(lagr_1)
684 for x, y in zip(lagr_0, lagr_1):
685 i0 = lagr_labeled_0[str(round(x, 12))]
686 i1 = lagr_labeled_1[str(round(y, 12))]
688 log('%5d %11.5f %9.5f'
689 '%5d %11.5f %9.5f' %
690 (i0, Ha * x,
691 f_sn[0][i0],
692 i1,
693 Ha * y,
694 f_sn[1][i1]))
696 log(flush=True)
698 sic_n = pot.e_sic_by_orbitals
699 if pot.name == 'PZ-SIC':
700 log('Perdew-Zunger SIC')
701 elif 'SPZ' in pot.name:
702 log('Self-Interaction Corrections:\n')
703 sf = pot.scalingf
704 else:
705 raise NotImplementedError
707 if self.nspins == 2 and self.kd.comm.size > 1:
708 if self.kd.comm.rank == 0:
709 size = np.array([0])
710 self.kd.comm.receive(size, 1)
711 sic_n2 = np.zeros(shape=(int(size[0]), 2),
712 dtype=float)
713 self.kd.comm.receive(sic_n2, 1)
714 sic_n[1] = sic_n2
716 if 'SPZ' in pot.name:
717 sf_2 = np.zeros(shape=(int(size[0]), 1),
718 dtype=float)
719 self.kd.comm.receive(sf_2, 1)
720 sf[1] = sf_2
721 else:
722 size = np.array([sic_n[1].shape[0]])
723 self.kd.comm.send(size, 0)
724 self.kd.comm.send(sic_n[1], 0)
726 if 'SPZ' in pot.name:
727 self.kd.comm.send(sf[1], 0)
729 if self.kd.comm.rank == 0:
730 for s in range(self.nspins):
731 if self.nspins == 2:
732 log('Spin: %3d ' % (s))
733 header = """\
734 Self-Har. Self-XC Hartree + XC Scaling
735 energy: energy: energy: Factors:"""
736 log(header)
738 occupied = f_sn[s] > 1.0e-10
739 f_sn_occ = f_sn[s][occupied]
740 occupied_indices = np.where(occupied)[0]
741 u_s = 0.0
742 xc_s = 0.0
743 for i in range(len(sic_n[s])):
745 u = sic_n[s][i][0]
746 xc = sic_n[s][i][1]
747 if 'SPZ' in pot.name:
748 f = (sf[s][i], sf[s][i])
749 else:
750 f = (pot.beta_c, pot.beta_x)
752 log('band: %3d ' %
753 (occupied_indices[i]), end='')
754 log('%11.6f%11.6f%11.6f %8.3f%7.3f' %
755 (-Ha * u / (f[0] * f_sn_occ[i]),
756 -Ha * xc / (f[1] * f_sn_occ[i]),
757 -Ha * (u / (f[0] * f_sn_occ[i]) +
758 xc / (f[1] * f_sn_occ[i])),
759 f[0], f[1]), end='')
760 log(flush=True)
761 u_s += u / (f[0] * f_sn_occ[i])
762 xc_s += xc / (f[1] * f_sn_occ[i])
763 log('--------------------------------'
764 '-------------------------')
765 log('Total ', end='')
766 log('%11.6f%11.6f%11.6f' %
767 (-Ha * u_s,
768 -Ha * xc_s,
769 -Ha * (u_s + xc_s)
770 ), end='')
771 log("\n")
772 log(flush=True)
775def eigenvalue_string(wfs, comment=' '):
776 """Write eigenvalues and occupation numbers into a string.
778 The parameter comment can be used to comment out non-numers,
779 for example to escape it for gnuplot.
780 """
781 tokens = []
783 def add(*line):
784 for token in line:
785 tokens.append(token)
786 tokens.append('\n')
788 def eigs(k, s):
789 eps_n = wfs.collect_eigenvalues(k, s)
790 return eps_n * Ha
792 def occs(k, s):
793 occ_n = wfs.collect_occupations(k, s)
794 return occ_n / wfs.kd.weight_k[k]
796 if len(wfs.kd.ibzk_kc) == 1:
797 if wfs.nspins == 1:
798 add(comment, 'Band Eigenvalues Occupancy')
799 eps_n = eigs(0, 0)
800 f_n = occs(0, 0)
801 if wfs.world.rank == 0:
802 for n in range(wfs.bd.nbands):
803 add('%5d %11.5f %9.5f' % (n, eps_n[n], f_n[n]))
804 else:
805 add(comment, ' Up Down')
806 add(comment, 'Band Eigenvalues Occupancy Eigenvalues '
807 'Occupancy')
808 epsa_n = eigs(0, 0)
809 epsb_n = eigs(0, 1)
810 fa_n = occs(0, 0)
811 fb_n = occs(0, 1)
812 if wfs.world.rank == 0:
813 for n in range(wfs.bd.nbands):
814 add('%5d %11.5f %9.5f %11.5f %9.5f' %
815 (n, epsa_n[n], fa_n[n], epsb_n[n], fb_n[n]))
816 return ''.join(tokens)
818 if len(wfs.kd.ibzk_kc) > 2:
819 add('Showing only first 2 kpts')
820 print_range = 2
821 else:
822 add('Showing all kpts')
823 print_range = len(wfs.kd.ibzk_kc)
825 if wfs.nvalence / 2. > 2:
826 m = int(wfs.nvalence / 2. - 2)
827 else:
828 m = 0
829 if wfs.bd.nbands - wfs.nvalence / 2. > 2:
830 j = int(wfs.nvalence / 2. + 2)
831 else:
832 j = int(wfs.bd.nbands)
834 if wfs.nspins == 1:
835 add(comment, 'Kpt Band Eigenvalues Occupancy')
836 for i in range(print_range):
837 eps_n = eigs(i, 0)
838 f_n = occs(i, 0)
839 if wfs.world.rank == 0:
840 for n in range(m, j):
841 add('%3i %5d %11.5f %9.5f' % (i, n, eps_n[n], f_n[n]))
842 add()
843 else:
844 add(comment, ' Up Down')
845 add(comment, 'Kpt Band Eigenvalues Occupancy Eigenvalues '
846 'Occupancy')
848 for i in range(print_range):
849 epsa_n = eigs(i, 0)
850 epsb_n = eigs(i, 1)
851 fa_n = occs(i, 0)
852 fb_n = occs(i, 1)
853 if wfs.world.rank == 0:
854 for n in range(m, j):
855 add('%3i %5d %11.5f %9.5f %11.5f %9.5f' %
856 (i, n, epsa_n[n], fa_n[n], epsb_n[n], fb_n[n]))
857 add()
858 return ''.join(tokens)