Coverage for gpaw/xc/ri/spherical_hse_kernel.py: 100%
63 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-20 00:19 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-20 00:19 +0000
1"""
3Implementation of spherical harmonic expansion of screened Coulomb kernel.
4Based on
6 János G Ángyán et al 2006 J. Phys. A: Math. Gen. 39 8613
9"""
12import numpy as np
13from scipy.special import erfc, comb, factorial2
14from math import factorial
17def safeerfc(x):
18 taylor = (1
19 - 2 * x / np.pi**0.5
20 + 2 * x**3 / (3 * np.pi**0.5)
21 - x**5 / (5 * np.pi)**0.5)
22 return np.where(x < 1e-4, taylor, erfc(x))
25def Dnk(n, k, Xi):
26 # Eq. 28
27 if k == 0:
28 sum = 0
29 for m in range(1, n + 1):
30 sum += 2**(-m) * Xi**(-2 * m) / factorial2(2 * n - 2 * m + 1)
31 return (safeerfc(Xi)
32 + np.exp(-Xi**2)
33 / (np.pi**0.5) * 2**(n + 1) * Xi**(2 * n + 1) * sum)
34 # Eq. 29
35 sum = 0
36 for m in range(1, k + 1):
37 sum += (comb(m - k - 1, m - 1) * 2**(k - m) * Xi**(2 * (k - m))
38 / factorial2(2 * n + 2 * k - 2 * m + 1))
40 return (np.exp(-Xi**2)
41 * 2**(n + 1) * (2 * n + 1) * Xi**(2 * n + 1)
42 / np.pi**0.5 / factorial(k)
43 / (2 * n + 2 * k + 1) * sum)
46def Phinj(n, j, Xi, xi):
47 # Eq. 30
48 sum = 0
49 for k in range(j):
50 sum += Dnk(n, k, Xi) / (Xi**(n + 1)) * xi**(n + 2 * k)
51 return sum
54def Hn(n, Xi, xi):
55 """
57 Helper function (Eq. 24)
59 """
60 return 1 / (2 * (xi * Xi)**(n + 1)) * ((Xi**(2 * n + 1) + xi**(2 * n + 1)) * safeerfc(Xi + xi) - (Xi**(2 * n + 1) - xi**(2 * n + 1)) * safeerfc(Xi - xi)) # noqa: E501
63def Fn(n, Xi, xi):
64 """
66 Helper function (Eq. 22).
68 It appears, that the article has a typo, because the summation
69 starts at p=1, but correct results require to start at p=0.
71 """
72 prefactor = 2 / np.pi**0.5
73 result = 0.0
75 for p in range(0, n + 1):
76 result += (-1 / (4 * Xi * xi))**(p + 1) * factorial(n + p) / (factorial(p) * factorial(n - p)) * ((-1)**(n - p) * np.exp(-(xi + Xi)**2) - np.exp(-(xi - Xi)**2)) # noqa: E501
77 taylor = np.exp(-Xi**2 - xi**2) * 2**(n + 1) * (3 + 2 * n + 2 * xi**2 * Xi**2) * xi**n * Xi**n / (np.pi**0.5 * factorial2(2 * n + 3)) # noqa: E501
79 return np.where((Xi * xi)**(2 * n + 1) < 1e-6, taylor, prefactor * result)
82def Phi(n, mu, R, r):
83 """
85 The official spherical kernel expansion
87 """
88 Rg = np.maximum.reduce([R, r])
89 Rl = np.minimum.reduce([R, r])
91 # Scaling as given by Eq. 16 and the text above.
92 Xi = mu * Rg
93 xi = mu * Rl
95 # Eq. 21
96 result = Fn(n, Xi, xi) + Hn(n, Xi, xi)
97 for m in range(1, n + 1):
98 result += (Fn(n - m, Xi, xi)
99 * (Xi**(2 * m) + xi**(2 * m)) / (xi * Xi)**m)
101 result = np.where(xi < [1e-3, 1e-2, 1e-1, 1e-1, 1e-1, 1e-1][n],
102 Phinj(n, 2, Xi, xi), result)
103 result *= mu
105 return result
108"""
110Implementation of spherical harmonic expansion ends. GPAW spesific stuff
111remains below.
113"""
116class RadialHSE:
117 def __init__(self, rgd, omega):
118 self.rgd = rgd
119 self.omega = omega
121 self.r1_gg = np.zeros((rgd.N, rgd.N))
122 self.r2_gg = np.zeros((rgd.N, rgd.N))
123 self.d_gg = np.zeros((rgd.N, rgd.N))
124 r_g = rgd.r_g.copy()
125 r_g[0] = r_g[1] # XXX
126 self.r1_gg[:] = r_g[None, :]
127 self.r2_gg[:] = r_g[:, None]
128 self.d_gg[:] = rgd.dr_g[None, :] * rgd.r_g[None, :]**2 * 4 * np.pi
129 self.V_lgg = {}
131 def screened_coulomb(self, n_g, l):
132 # Buffer different l-values for optimal performance
133 if l not in self.V_lgg:
134 kernel_gg = np.reshape(Phi(l, self.omega, self.r1_gg.ravel(),
135 self.r2_gg.ravel()),
136 self.d_gg.shape) / (2 * l + 1)
137 self.V_lgg[l] = self.d_gg * kernel_gg
138 vr_g = (self.V_lgg[l] @ n_g) * self.rgd.r_g
139 return vr_g
141 def screened_coulomb_dv(self, n_g, l):
142 return self.screened_coulomb(n_g, l) * self.rgd.r_g * self.rgd.dr_g