Coverage for gpaw/response/density_kernels.py: 93%
104 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
1"""XC density kernels for response function calculations."""
2from abc import ABC, abstractmethod
3from dataclasses import dataclass
5import numpy as np
7from gpaw.response import ResponseGroundStateAdapter, ResponseContext
8from gpaw.response.qpd import SingleQPWDescriptor
9from gpaw.response.localft import LocalFTCalculator
10from gpaw.response.fxc_kernels import AdiabaticFXCCalculator
13@dataclass
14class DensityXCKernel(ABC):
15 gs: ResponseGroundStateAdapter
16 context: ResponseContext
17 functional: str
19 def __post_init__(self):
20 assert self.gs.nspins == 1
22 @staticmethod
23 def from_functional(gs, context, functional, **kwargs):
24 """Factory function creating DensityXCKernels.
26 Choose between ALDA, Bootstrap and LRalpha (long-range kernel), where
27 alpha is a user specified parameter (for example functional='LR0.25').
28 """
29 if functional[0] == 'A':
30 return AdiabaticDensityKernel(gs, context, functional, kwargs)
31 elif functional[:2] == 'LR':
32 return LRDensityKernel(gs, context, functional)
33 elif functional == 'Bootstrap':
34 return BootstrapDensityKernel(gs, context, functional)
35 raise ValueError(
36 'Invalid functional for the density-density xc kernel:'
37 f'{functional}')
39 def __call__(self, qpd: SingleQPWDescriptor, chi0_wGG=None):
40 self.context.print(f'Calculating {self}')
41 return self.calculate(qpd=qpd, chi0_wGG=chi0_wGG)
43 @abstractmethod
44 def __repr__(self):
45 """String representation of the density xc kernel."""
47 @abstractmethod
48 def calculate(self, *args, **kwargs):
49 """Calculate the xc kernel.
51 Since the exchange-correlation kernel is going to be rescaled according
52 to the bare Coulomb interaction,
53 ˷
54 K_xc(q) = v^(-1/2)(q) K_xc(q) v^(-1/2)(q)
56 and that the Coulomb interaction in the optical q→0 limit leaves the
57 long-range q-dependence out, see gpaw.response.coulomb_kernels,
59 v(q) = 4π/|G+q| -> 4π for G==0 and 4π/|G| otherwise if q==0,
61 we need to account to account for it here. That is,
63 For q→0, the head and wings of the returned K_xc(q) needs to be
64 rescaled according to K_xc(q) -> q K_xc(q) q
65 """
68@dataclass
69class AdiabaticDensityKernel(DensityXCKernel):
70 rshe_kwargs: dict
72 def __post_init__(self):
73 super().__post_init__()
74 localft_calc = LocalFTCalculator.from_rshe_parameters(
75 self.gs, self.context, **self.rshe_kwargs)
76 self.fxc_calc = AdiabaticFXCCalculator(localft_calc)
78 def __repr__(self):
79 return f'{self.functional} kernel'
81 def calculate(self, qpd: SingleQPWDescriptor, **ignored):
82 fxc_kernel = self.fxc_calc(self.functional, '00', qpd)
83 Kxc_GG = fxc_kernel.get_Kxc_GG()
84 if qpd.optical_limit:
85 Kxc_GG[0, :] = 0.0
86 Kxc_GG[:, 0] = 0.0
87 return Kxc_GG
90@dataclass
91class LRDensityKernel(DensityXCKernel):
93 def __post_init__(self):
94 super().__post_init__()
95 self.alpha = float(self.functional[2:])
97 def __repr__(self):
98 return f'LR kernel with alpha = {self.alpha}'
100 def calculate(self, qpd: SingleQPWDescriptor, **ignored):
101 Kxc_sGG = calculate_lr_kernel(qpd, alpha=self.alpha)
102 return Kxc_sGG[0]
105@dataclass
106class BootstrapDensityKernel(DensityXCKernel):
108 def __repr__(self):
109 return 'Bootstrap kernel'
111 def calculate(self, qpd, *, chi0_wGG):
112 Kxc_sGG = get_bootstrap_kernel(qpd, chi0_wGG, self.context)
113 return Kxc_sGG[0]
116def calculate_lr_kernel(qpd, alpha=0.2):
117 """Long range kernel: fxc = \alpha / |q+G|^2"""
119 assert qpd.optical_limit
121 f_G = np.zeros(len(qpd.G2_qG[0]))
122 f_G[0] = -alpha
123 f_G[1:] = -alpha / qpd.G2_qG[0][1:]
125 return np.array([np.diag(f_G)])
128def get_bootstrap_kernel(qpd, chi0_wGG, context):
129 """ Bootstrap kernel (see below) """
131 if context.comm.rank == 0:
132 chi0_GG = chi0_wGG[0]
133 if context.comm.size > 1:
134 # If size == 1, chi0_GG is not contiguous, and broadcast()
135 # will fail in debug mode. So we skip it until someone
136 # takes a closer look.
137 context.comm.broadcast(chi0_GG, 0)
138 else:
139 nG = qpd.ngmax
140 chi0_GG = np.zeros((nG, nG), complex)
141 context.comm.broadcast(chi0_GG, 0)
143 return calculate_bootstrap_kernel(qpd, chi0_GG, context)
146def calculate_bootstrap_kernel(qpd, chi0_GG, context):
147 """Bootstrap kernel PRL 107, 186401"""
148 p = context.print
150 if qpd.optical_limit:
151 v_G = np.zeros(len(qpd.G2_qG[0]))
152 v_G[0] = 4 * np.pi
153 v_G[1:] = 4 * np.pi / qpd.G2_qG[0][1:]
154 else:
155 v_G = 4 * np.pi / qpd.G2_qG[0]
157 nG = len(v_G)
158 K_GG = np.diag(v_G)
160 Kxc_GG = np.zeros((nG, nG), dtype=complex)
161 dminv_GG = np.zeros((nG, nG), dtype=complex)
163 for iscf in range(120):
164 dminvold_GG = dminv_GG.copy()
165 Kxc_GG = K_GG + Kxc_GG
167 chi_GG = np.dot(np.linalg.inv(np.eye(nG, nG)
168 - np.dot(chi0_GG, Kxc_GG)), chi0_GG)
169 dminv_GG = np.eye(nG, nG) + np.dot(K_GG, chi_GG)
171 alpha = dminv_GG[0, 0] / (K_GG[0, 0] * chi0_GG[0, 0])
172 Kxc_GG = alpha * K_GG
173 p(iscf, 'alpha =', alpha, flush=False)
174 error = np.abs(dminvold_GG - dminv_GG).sum()
175 if np.sum(error) < 0.1:
176 p('Self consistent fxc finished in %d iterations !' % iscf)
177 break
178 if iscf > 100:
179 p('Too many fxc scf steps !')
181 return np.array([Kxc_GG])