Coverage for gpaw/xc/gllb/c_gllbscr.py: 99%
102 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
1import numpy as np
2from math import sqrt, pi
4from gpaw.xc.pawcorrection import rnablaY_nLv
5from gpaw.sphere.lebedev import weight_n
6from gpaw.xc import XC
7from gpaw.xc.gllb.contribution import Contribution
10class C_GLLBScr(Contribution):
11 def __init__(self, weight, functional, damp=1e-10):
12 Contribution.__init__(self, weight)
13 self.xc = XC(functional)
14 self.damp = damp
16 def set_damp(self, damp):
17 self.damp = damp
19 def get_name(self):
20 return 'SCREENING'
22 def get_desc(self):
23 desc = self.xc.get_description()
24 return self.xc.name if desc is None else desc
26 def initialize(self, density, hamiltonian, wfs):
27 Contribution.initialize(self, density, hamiltonian, wfs)
28 # Always 1 spin, no matter what calculation nspins is
29 self.vt_sg = self.finegd.empty(1)
30 self.e_g = self.finegd.empty()
32 def initialize_1d(self, ae):
33 Contribution.initialize_1d(self, ae)
34 self.v_g = np.zeros(self.ae.N)
35 self.e_g = np.zeros(self.ae.N)
37 # Calcualte the GLLB potential and energy 1d
38 def add_xc_potential_and_energy_1d(self, v_g):
39 self.v_g[:] = 0.0
40 self.e_g[:] = 0.0
41 self.xc.calculate_spherical(self.ae.rgd, self.ae.n.reshape((1, -1)),
42 self.v_g.reshape((1, -1)), self.e_g)
43 v_g += 2 * self.weight * self.e_g / (self.ae.n + self.damp)
44 Exc = self.weight * np.sum(self.e_g * self.ae.rgd.dv_g)
45 return Exc
47 def calculate(self, e_g, n_sg, v_sg):
48 # Calculate spin-paired exchange screening
49 # as Eqs. (21-22) of https://doi.org/10.1103/PhysRevB.82.115106
50 # and spin-polarized exchange screening
51 # as two spin-paired calculations n=2*n_s
52 mult = self.nspins # mult = 1 for spin-paired and 2 for spin-polarized
53 for n_g, v_g in zip(n_sg, v_sg):
54 self.e_g[:] = 0.0
55 self.vt_sg[:] = 0.0 # Note: this array has nspins=1 always
56 self.xc.calculate(self.finegd, mult * n_g[np.newaxis],
57 self.vt_sg, self.e_g)
58 self.e_g[:] = np.where(n_g < self.damp, 0, self.e_g)
59 v_g += self.weight * 2 * self.e_g / (mult * n_g + self.damp)
60 e_g += self.weight * self.e_g / mult
62 def calculate_energy_and_derivatives(self, setup, D_sp, H_sp, a,
63 addcoredensity=True):
64 # Get the XC-correction instance
65 c = setup.xc_correction
66 nspins = self.nspins
68 E = 0
69 for D_p, dEdD_p in zip(D_sp, H_sp):
70 D_Lq = np.dot(c.B_pqL.T, nspins * D_p)
71 n_Lg = np.dot(D_Lq, c.n_qg)
72 if addcoredensity:
73 n_Lg[0] += c.nc_g * sqrt(4 * pi)
74 nt_Lg = np.dot(D_Lq, c.nt_qg)
75 if addcoredensity:
76 nt_Lg[0] += c.nct_g * sqrt(4 * pi)
77 dndr_Lg = np.zeros((c.Lmax, c.ng))
78 dntdr_Lg = np.zeros((c.Lmax, c.ng))
79 for L in range(c.Lmax):
80 c.rgd.derivative(n_Lg[L], dndr_Lg[L])
81 c.rgd.derivative(nt_Lg[L], dntdr_Lg[L])
82 vt_g = np.zeros(c.ng)
83 v_g = np.zeros(c.ng)
84 e_g = np.zeros(c.ng)
85 deda2_g = np.zeros(c.ng)
86 for y, (w, Y_L) in enumerate(zip(weight_n, c.Y_nL)):
87 # Cut gradient releated coefficient to match the setup's Lmax
88 A_Li = rnablaY_nLv[y, :c.Lmax]
90 # Expand pseudo density
91 nt_g = np.dot(Y_L, nt_Lg)
93 # Expand pseudo density gradient
94 a1x_g = np.dot(A_Li[:, 0], nt_Lg)
95 a1y_g = np.dot(A_Li[:, 1], nt_Lg)
96 a1z_g = np.dot(A_Li[:, 2], nt_Lg)
97 a2_g = a1x_g**2 + a1y_g**2 + a1z_g**2
98 a2_g[1:] /= c.rgd.r_g[1:]**2
99 a2_g[0] = a2_g[1]
100 a1_g = np.dot(Y_L, dntdr_Lg)
101 a2_g += a1_g**2
103 vt_g[:] = 0.0
104 e_g[:] = 0.0
105 # Calculate pseudo GGA energy density (potential is discarded)
106 self.xc.kernel.calculate(e_g, nt_g.reshape((1, -1)),
107 vt_g.reshape((1, -1)),
108 a2_g.reshape((1, -1)),
109 deda2_g.reshape((1, -1)))
111 # Calculate pseudo GLLB-potential from GGA-energy density
112 vt_g[:] = 2 * e_g / (nt_g + self.damp)
114 dEdD_p -= self.weight * w * np.dot(np.dot(c.B_pqL, Y_L),
115 np.dot(c.nt_qg,
116 vt_g * c.rgd.dv_g))
118 E -= w * np.dot(e_g, c.rgd.dv_g) / nspins
120 # Expand density
121 n_g = np.dot(Y_L, n_Lg)
123 # Expand density gradient
124 a1x_g = np.dot(A_Li[:, 0], n_Lg)
125 a1y_g = np.dot(A_Li[:, 1], n_Lg)
126 a1z_g = np.dot(A_Li[:, 2], n_Lg)
127 a2_g = a1x_g**2 + a1y_g**2 + a1z_g**2
128 a2_g[1:] /= c.rgd.r_g[1:]**2
129 a2_g[0] = a2_g[1]
130 a1_g = np.dot(Y_L, dndr_Lg)
131 a2_g += a1_g**2
133 v_g[:] = 0.0
134 e_g[:] = 0.0
135 # Calculate GGA energy density (potential is discarded)
136 self.xc.kernel.calculate(e_g, n_g.reshape((1, -1)),
137 v_g.reshape((1, -1)),
138 a2_g.reshape((1, -1)),
139 deda2_g.reshape((1, -1)))
141 # Calculate GLLB-potential from GGA-energy density
142 v_g[:] = 2 * e_g / (n_g + self.damp)
144 dEdD_p += self.weight * w * np.dot(np.dot(c.B_pqL, Y_L),
145 np.dot(c.n_qg,
146 v_g * c.rgd.dv_g))
147 E += w * np.dot(e_g, c.rgd.dv_g) / nspins
149 return E * self.weight
151 def add_smooth_xc_potential_and_energy_1d(self, vt_g):
152 self.v_g[:] = 0.0
153 self.e_g[:] = 0.0
154 self.xc.calculate_spherical(self.ae.rgd, self.ae.nt.reshape((1, -1)),
155 self.v_g.reshape((1, -1)), self.e_g)
156 vt_g += 2 * self.weight * self.e_g / (self.ae.nt + self.damp)
157 return self.weight * np.sum(self.e_g * self.ae.rgd.dv_g)