Coverage for gpaw/new/eigensolver.py: 49%
74 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-12 00:18 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-12 00:18 +0000
1from __future__ import annotations
3import numpy as np
5from ase.units import Ha
7from gpaw.new.density import Density
8from gpaw.new.hamiltonian import Hamiltonian
9from gpaw.new.potential import Potential
10from gpaw.new.energies import DFTEnergies
11from gpaw.new.ibzwfs import IBZWaveFunctions
12from gpaw.new.pot_calc import PotentialCalculator
13from gpaw.mpi import broadcast_float
14from gpaw.typing import Array1D
17class Eigensolver:
18 direct = False
20 def iterate(self,
21 ibzwfs: IBZWaveFunctions,
22 density: Density,
23 potential: Potential,
24 hamiltonian: Hamiltonian,
25 pot_calc: PotentialCalculator,
26 energies: DFTEnergies) -> tuple[float, float, DFTEnergies]:
27 raise NotImplementedError
29 def postprocess(self, ibzwfs, density, potential, hamiltonian):
30 pass
32 def iterate_kpt(self, wfs, weight_n, iter_func, **fkwargs):
33 had_eigs_and_occs = wfs.has_eigs and wfs.has_occs
34 if had_eigs_and_occs:
35 eig_old = wfs.myeig_n
36 eigs_error = iter_func(wfs=wfs, weight_n=weight_n, **fkwargs)
37 if had_eigs_and_occs:
38 eig_error = np.max(weight_n * np.abs(eig_old - wfs.myeig_n),
39 initial=0)
40 else: # no eigenvalues to compare with
41 eig_error = np.inf
42 return eigs_error, eig_error
45def calculate_weights(converge_bands: int | str,
46 ibzwfs: IBZWaveFunctions) -> list[Array1D | None]:
47 """Calculate convergence weights for all eigenstates."""
48 weight_un = []
49 nu = len(ibzwfs.wfs_qs) * ibzwfs.nspins
50 nbands = ibzwfs.nbands
52 if converge_bands == 'occupied':
53 # Converge occupied bands:
54 for wfs in ibzwfs:
55 if wfs.has_occs:
56 # Methfessel-Paxton or cold-smearing distributions can give
57 # negative occupation numbers - so we take the absolute value:
58 weight_n = np.abs(wfs.myocc_n)
59 else:
60 # No eigenvalues yet:
61 return [None] * nu
62 weight_un.append(weight_n)
63 return weight_un
65 if converge_bands == 'all':
66 converge_bands = nbands
68 if not isinstance(converge_bands, str):
69 # Converge fixed number of bands:
70 n = converge_bands
71 if n < 0:
72 n += nbands
73 assert n >= 0
74 for wfs in ibzwfs:
75 weight_n = np.zeros(wfs.n2 - wfs.n1)
76 m = max(wfs.n1, min(n, wfs.n2)) - wfs.n1
77 weight_n[:m] = 1.0
78 weight_un.append(weight_n)
79 return weight_un
81 # Converge states with energy up to CBM + delta:
82 assert converge_bands.startswith('CBM+')
83 delta = float(converge_bands[4:]) / Ha
85 if ibzwfs.fermi_levels is None:
86 return [None] * nu
88 efermi = np.mean(ibzwfs.fermi_levels)
90 # Find CBM:
91 cbm = np.inf
92 nocc_u = np.empty(nu, int)
93 for u, wfs in enumerate(ibzwfs):
94 n = (wfs.eig_n < efermi).sum() # number of occupied bands
95 nocc_u[u] = n
96 if n < nbands:
97 cbm = min(cbm, wfs.eig_n[n])
99 # If all k-points don't have the same number of occupied bands,
100 # then it's a metal:
101 n0 = int(broadcast_float(float(nocc_u[0]), ibzwfs.kpt_comm))
102 metal = bool(ibzwfs.kpt_comm.sum_scalar(float((nocc_u != n0).any())))
103 if metal:
104 cbm = efermi
105 else:
106 cbm = ibzwfs.kpt_comm.min_scalar(cbm)
108 ecut = cbm + delta
110 for wfs in ibzwfs:
111 weight_n = (wfs.myeig_n < ecut).astype(float)
112 if wfs.eig_n[-1] < ecut:
113 # We don't have enough bands!
114 weight_n[:] = np.inf
115 weight_un.append(weight_n)
117 return weight_un