Coverage for eminus/xc/gga_x_pbe.py: 100.00%
39 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-21 12:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-21 12:19 +0000
1# SPDX-FileCopyrightText: 2023 The eminus developers
2# SPDX-License-Identifier: Apache-2.0
3"""Perdew-Burke-Ernzerhof GGA exchange.
5Reference: Phys. Rev. Lett. 77, 3865.
6"""
8import math
10import numpy as np
12from eminus import backend as xp
14from .lda_x import lda_x, lda_x_spin
17def gga_x_pbe(n, mu=0.2195149727645171, dn_spin=None, **kwargs):
18 """Perdew-Burke-Ernzerhof parametrization of the exchange functional (spin-paired).
20 Corresponds to the functional with the label GGA_X_PBE and ID 101 in Libxc.
22 Reference: Phys. Rev. Lett. 77, 3865.
24 Args:
25 n: Real-space electronic density.
27 Keyword Args:
28 mu: Functional parameter.
29 dn_spin: Real-space gradient of densities per spin channel.
30 **kwargs: Throwaway arguments.
32 Returns:
33 PBE exchange energy density, potential, and vsigma.
34 """
35 ex, vx, _ = lda_x(n, **kwargs)
36 gex, gvx, vsigmax = pbe_x_base(n, mu, dn_spin[0], **kwargs)
37 vx, gvx = vx[0], gvx[0] # Remove spin dimension for the correct shape
38 return ex + gex / n, xp.stack([vx + gvx]), xp.stack([0.5 * vsigmax])
41def gga_x_pbe_spin(n, zeta, mu=0.2195149727645171, dn_spin=None, **kwargs):
42 """Perdew-Burke-Ernzerhof parametrization of the exchange functional (spin-polarized).
44 Corresponds to the functional with the label GGA_X_PBE and ID 101 in Libxc.
46 Reference: Phys. Rev. Lett. 77, 3865.
48 Args:
49 n: Real-space electronic density.
50 zeta: Relative spin polarization.
52 Keyword Args:
53 mu: Functional parameter.
54 dn_spin: Real-space gradient of densities per spin channel.
55 **kwargs: Throwaway arguments.
57 Returns:
58 PBE exchange energy density, potential, and vsigma.
59 """
60 # Use the spin-scaling relationship Exc(n_up, n_down)=(Exc(2 n_up)+Exc(2 n_down))/2
61 zeta = zeta[0] # Getting the non-zero values from zeta adds an extra dimension, remove it here
62 n_up = zeta * n + n # 2 * n_up
63 n_dw = -zeta * n + n # 2 * n_down
64 ex_up, vx_up, vsigma_up = pbe_x_base(n_up, mu, 2 * dn_spin[0], **kwargs)
65 ex_dw, vx_dw, vsigma_dw = pbe_x_base(n_dw, mu, 2 * dn_spin[1], **kwargs)
66 vx_up, vx_dw = vx_up[0], vx_dw[0] # Remove spin dimension for the correct shape
68 ex, vx, _ = lda_x_spin(n, zeta, **kwargs)
70 vsigmax = xp.stack([vsigma_up, xp.zeros_like(ex), vsigma_dw])
71 return ex + 0.5 * (ex_up + ex_dw) / n, xp.stack([vx[0] + vx_up, vx[1] + vx_dw]), vsigmax
74def pbe_x_base(n, mu=0.2195149727645171, dn=None, **kwargs):
75 """Base PBE exchange functional to be used in the spin-paired and -polarized case.
77 Reference: Phys. Rev. Lett. 77, 3865.
79 Args:
80 n: Real-space electronic density.
82 Keyword Args:
83 mu: Functional parameter.
84 dn: Real-space gradient of densities per spin channel.
85 **kwargs: Throwaway arguments.
87 Returns:
88 PBE exchange energy density, potential, and vsigma.
89 """
90 kappa = 0.804
92 norm_dn = xp.linalg.norm(dn, axis=1)
93 kf = (3 * math.pi**2 * n) ** (1 / 3)
94 # Handle divisions by zero
95 # divkf = 1 / kf
96 with np.errstate(divide="ignore"):
97 divkf = xp.where(kf > 0, 1 / kf, 0)
98 # Handle divisions by zero
99 # s = norm_dn * divkf / (2 * n)
100 with np.errstate(invalid="ignore"):
101 s = xp.where(n > 0, norm_dn * divkf / (2 * n), 0)
102 f1 = 1 + mu * s**2 / kappa
103 Fx = kappa - kappa / f1
104 exunif = -3 * kf / (4 * math.pi)
105 # In Fx a "1 + " is missing, since n * exunif is the Slater exchange that is added later
106 sx = exunif * Fx
108 dsdn = -4 / 3 * s
109 dFxds = 2 * mu * s / f1**2
110 dexunif = exunif / 3
111 exunifdFx = exunif * dFxds
112 vx = sx + dexunif * Fx + exunifdFx * dsdn # dFx/dn = dFx/ds * ds/dn
114 # Handle divisions by zero
115 # vsigmax = exunifdFx * divkf / (2 * norm_dn)
116 with np.errstate(invalid="ignore"):
117 vsigmax = xp.where(norm_dn > 0, exunifdFx * divkf / (2 * norm_dn), 0)
119 return sx * n, xp.stack([vx]), vsigmax