Source code for gpaw.xc.gga

from math import pi

import numpy as np

from gpaw.xc.lda import calculate_paw_correction
from gpaw.utilities.blas import axpy
from gpaw.fd_operators import Gradient
from gpaw.sphere.lebedev import Y_nL, weight_n
from gpaw.xc.pawcorrection import rnablaY_nLv
from gpaw.xc.functional import XCFunctional


class GGARadialExpansion:
    def __init__(self, rcalc, *args):
        self.rcalc = rcalc
        self.args = args

    def __call__(self, rgd, D_sLq, n_qg, nc0_sg):
        n_sLg = np.dot(D_sLq, n_qg)
        n_sLg[:, 0] += nc0_sg

        dndr_sLg = np.empty_like(n_sLg)
        for n_Lg, dndr_Lg in zip(n_sLg, dndr_sLg):
            for n_g, dndr_g in zip(n_Lg, dndr_Lg):
                rgd.derivative(n_g, dndr_g)

        nspins, Lmax, nq = D_sLq.shape
        dEdD_sqL = np.zeros((nspins, nq, Lmax))

        E = 0.0
        for n, Y_L in enumerate(Y_nL[:, :Lmax]):
            w = weight_n[n]
            rnablaY_Lv = rnablaY_nLv[n, :Lmax]
            e_g, dedn_sg, b_vsg, dedsigma_xg = \
                self.rcalc(rgd, n_sLg, Y_L, dndr_sLg, rnablaY_Lv, n,
                           *self.args)
            dEdD_sqL += np.dot(rgd.dv_g * dedn_sg,
                               n_qg.T)[:, :, np.newaxis] * (w * Y_L)
            dedsigma_xg *= rgd.dr_g
            B_vsg = dedsigma_xg[::2] * b_vsg
            if nspins == 2:
                B_vsg += 0.5 * dedsigma_xg[1] * b_vsg[:, ::-1]
            B_vsq = np.dot(B_vsg, n_qg.T)
            dEdD_sqL += 8 * pi * w * np.inner(rnablaY_Lv, B_vsq.T).T
            E += w * rgd.integrate(e_g)

        return E, dEdD_sqL


# First part of gga_calculate_radial - initializes some quantities.
def radial_gga_vars(rgd, n_sLg, Y_L, dndr_sLg, rnablaY_Lv):
    nspins = len(n_sLg)

    n_sg = np.dot(Y_L, n_sLg)

    a_sg = np.dot(Y_L, dndr_sLg)
    b_vsg = np.dot(rnablaY_Lv.T, n_sLg)

    sigma_xg = rgd.empty(2 * nspins - 1)
    sigma_xg[::2] = (b_vsg ** 2).sum(0)
    if nspins == 2:
        sigma_xg[1] = (b_vsg[:, 0] * b_vsg[:, 1]).sum(0)
    sigma_xg[:, 1:] /= rgd.r_g[1:] ** 2
    sigma_xg[:, 0] = sigma_xg[:, 1]
    sigma_xg[::2] += a_sg ** 2
    if nspins == 2:
        sigma_xg[1] += a_sg[0] * a_sg[1]

    e_g = rgd.empty()
    dedn_sg = rgd.zeros(nspins)
    dedsigma_xg = rgd.zeros(2 * nspins - 1)
    return e_g, n_sg, dedn_sg, sigma_xg, dedsigma_xg, a_sg, b_vsg


def add_radial_gradient_correction(rgd, sigma_xg, dedsigma_xg, a_sg):
    nspins = len(a_sg)
    vv_sg = sigma_xg[:nspins]  # reuse array
    for s in range(nspins):
        rgd.derivative2(-2 * rgd.dv_g * dedsigma_xg[2 * s] * a_sg[s],
                        vv_sg[s])
    if nspins == 2:
        v_g = sigma_xg[2]
        rgd.derivative2(rgd.dv_g * dedsigma_xg[1] * a_sg[1], v_g)
        vv_sg[0] -= v_g
        rgd.derivative2(rgd.dv_g * dedsigma_xg[1] * a_sg[0], v_g)
        vv_sg[1] -= v_g

    vv_sg[:, 1:] /= rgd.dv_g[1:]
    vv_sg[:, 0] = vv_sg[:, 1]
    return vv_sg


class GGARadialCalculator:
    def __init__(self, kernel):
        self.kernel = kernel

    def __call__(self, rgd, n_sLg, Y_L, dndr_sLg, rnablaY_Lv, n):
        (e_g, n_sg, dedn_sg, sigma_xg, dedsigma_xg, a_sg,
         b_vsg) = radial_gga_vars(rgd, n_sLg, Y_L, dndr_sLg, rnablaY_Lv)
        self.kernel.calculate(e_g, n_sg, dedn_sg, sigma_xg, dedsigma_xg)
        vv_sg = add_radial_gradient_correction(rgd, sigma_xg,
                                               dedsigma_xg, a_sg)
        return e_g, dedn_sg + vv_sg, b_vsg, dedsigma_xg


def calculate_sigma(gd, grad_v, n_sg):
    r"""Calculate sigma(r) and grad n(r).
                  _     __   _  2     __    _
    Returns sigma(r) = |\/ n(r)|  and \/ n (r).

    With multiple spins, sigma has the three elements

                   _     __     _  2
            sigma (r) = |\/ n  (r)|  ,
                 0           up

                   _     __     _      __     _
            sigma (r) =  \/ n  (r)  .  \/ n  (r) ,
                 1           up            dn

                   _     __     _  2
            sigma (r) = |\/ n  (r)|  .
                 2           dn
    """
    nspins = len(n_sg)
    gradn_svg = gd.empty((nspins, 3))
    sigma_xg = gd.zeros(nspins * 2 - 1)
    for v in range(3):
        for s in range(nspins):
            grad_v[v](n_sg[s], gradn_svg[s, v])
            axpy(1.0, gradn_svg[s, v]**2, sigma_xg[2 * s])
        if nspins == 2:
            axpy(1.0, gradn_svg[0, v] * gradn_svg[1, v], sigma_xg[1])
    return sigma_xg, gradn_svg


def add_gradient_correction(grad_v, gradn_svg, sigma_xg, dedsigma_xg, v_sg):
    r"""Add gradient correction to potential.

    ::

                      __   /    de(r)    __      \
        v  (r) += -2  \/ . |  ---------  \/ n(r) |
         xc                \  dsigma(r)          /

    Adds arbitrary data to sigma_xg.  Be sure to pass a copy if you need
    sigma_xg after calling this function.
    """
    nspins = len(v_sg)
    # vv_g is a calculation buffer.  Its contents will be corrupted.
    vv_g = sigma_xg[0]
    for v in range(3):
        for s in range(nspins):
            grad_v[v](dedsigma_xg[2 * s] * gradn_svg[s, v], vv_g)
            vv_g *= 2.0
            v_sg[s] -= vv_g
            # axpy(-2.0, vv_g, v_sg[s])
            if nspins == 2:
                grad_v[v](dedsigma_xg[1] * gradn_svg[s, v], vv_g)
                v_sg[1 - s] -= vv_g
                # axpy(-1.0, vv_g, v_sg[1 - s])
                # TODO: can the number of gradient evaluations be reduced?


def gga_vars(gd, grad_v, n_sg):
    nspins = len(n_sg)
    sigma_xg, gradn_svg = calculate_sigma(gd, grad_v, n_sg)
    dedsigma_xg = gd.empty(nspins * 2 - 1)
    return sigma_xg, dedsigma_xg, gradn_svg


def get_gradient_ops(gd, nn, xp):
    return [Gradient(gd, v, n=nn, xp=xp).apply for v in range(3)]


[docs]class GGA(XCFunctional): def __init__(self, kernel, stencil=2, xp=np): XCFunctional.__init__(self, kernel.name, kernel.type) self.kernel = kernel self.stencil_range = stencil self.xp = xp def set_grid_descriptor(self, gd): XCFunctional.set_grid_descriptor(self, gd) self.grad_v = get_gradient_ops(gd, self.stencil_range, self.xp)
[docs] def todict(self): d = super().todict() d['stencil'] = self.stencil_range return d
[docs] def get_description(self): return ('{} with {} nearest neighbor stencil' .format(self.name, self.stencil_range))
def calculate_impl(self, gd, n_sg, v_sg, e_g): sigma_xg, dedsigma_xg, gradn_svg = gga_vars(gd, self.grad_v, n_sg) self.kernel.calculate(e_g, n_sg, v_sg, sigma_xg, dedsigma_xg) add_gradient_correction(self.grad_v, gradn_svg, sigma_xg, dedsigma_xg, v_sg) def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): rcalc = GGARadialCalculator(self.kernel) expansion = GGARadialExpansion(rcalc) return calculate_paw_correction(expansion, setup, D_sp, dEdD_sp, addcoredensity, a) def stress_tensor_contribution(self, n_sg, skip_sum=False): sigma_xg, gradn_svg = calculate_sigma(self.gd, self.grad_v, n_sg) nspins = len(n_sg) dedsigma_xg = self.gd.empty(nspins * 2 - 1) v_sg = self.gd.zeros(nspins) e_g = self.gd.empty() self.kernel.calculate(e_g, n_sg, v_sg, sigma_xg, dedsigma_xg) def integrate(a1_g, a2_g=None): return self.gd.integrate(a1_g, a2_g, global_integral=False) P = integrate(e_g) for v_g, n_g in zip(v_sg, n_sg): P -= integrate(v_g, n_g) for sigma_g, dedsigma_g in zip(sigma_xg, dedsigma_xg): P -= 2 * integrate(sigma_g, dedsigma_g) stress_vv = P * np.eye(3) for v1 in range(3): for v2 in range(3): stress_vv[v1, v2] -= integrate(gradn_svg[0, v1] * gradn_svg[0, v2], dedsigma_xg[0]) * 2 if nspins == 2: stress_vv[v1, v2] -= integrate(gradn_svg[0, v1] * gradn_svg[1, v2], dedsigma_xg[1]) * 2 stress_vv[v1, v2] -= integrate(gradn_svg[1, v1] * gradn_svg[1, v2], dedsigma_xg[2]) * 2 if not skip_sum: self.gd.comm.sum(stress_vv) return stress_vv def calculate_spherical(self, rgd, n_sg, v_sg, e_g=None): dndr_sg = np.empty_like(n_sg) for n_g, dndr_g in zip(n_sg, dndr_sg): rgd.derivative(n_g, dndr_g) if e_g is None: e_g = rgd.empty() rcalc = GGARadialCalculator(self.kernel) e_g[:], dedn_sg = rcalc(rgd, n_sg[:, np.newaxis], [1.0], dndr_sg[:, np.newaxis], np.zeros((1, 3)), n=None)[:2] v_sg[:] = dedn_sg return rgd.integrate(e_g)
class PurePythonGGAKernel: def __init__(self, name): self.type = 'GGA' self.name, self.kappa, self.mu, self.beta = pbe_constants(name) def calculate(self, e_g, n_sg, dedn_sg, sigma_xg, dedsigma_xg, tau_sg=None, dedtau_sg=None): e_g[:] = 0. dedsigma_xg[:] = 0. # spin-paired: if len(n_sg) == 1: n = n_sg[0] n[n < 1e-20] = 1e-40 # exchange res = gga_x(self.name, 0, n, sigma_xg[0], self.kappa, self.mu) ex, rs, dexdrs, dexda2 = res # correlation res = gga_c(self.name, 0, n, sigma_xg[0], 0, self.beta) ec, rs_, decdrs, decda2, decdzeta = res e_g[:] += n * (ex + ec) dedn_sg[:] += ex + ec - rs * (dexdrs + decdrs) / 3. dedsigma_xg[:] += n * (dexda2 + decda2) # spin-polarized: else: na = 2. * n_sg[0] na[na < 1e-20] = 1e-40 nb = 2. * n_sg[1] nb[nb < 1e-20] = 1e-40 n = 0.5 * (na + nb) zeta = 0.5 * (na - nb) / n # exchange exa, rsa, dexadrs, dexada2 = gga_x( self.name, 1, na, 4.0 * sigma_xg[0], self.kappa, self.mu) exb, rsb, dexbdrs, dexbda2 = gga_x( self.name, 1, nb, 4.0 * sigma_xg[2], self.kappa, self.mu) a2 = sigma_xg[0] + 2.0 * sigma_xg[1] + sigma_xg[2] # correlation ec, rs, decdrs, decda2, decdzeta = gga_c( self.name, 1, n, a2, zeta, self.beta) e_g[:] += 0.5 * (na * exa + nb * exb) + n * ec dedn_sg[0][:] += (exa + ec - (rsa * dexadrs + rs * decdrs) / 3.0 - (zeta - 1.0) * decdzeta) dedn_sg[1][:] += (exb + ec - (rsb * dexbdrs + rs * decdrs) / 3.0 - (zeta + 1.0) * decdzeta) dedsigma_xg[0][:] += 2.0 * na * dexada2 + n * decda2 dedsigma_xg[1][:] += 2.0 * n * decda2 dedsigma_xg[2][:] += 2.0 * nb * dexbda2 + n * decda2 def pbe_constants(name): if name == 'pyPBE': name = 'PBE' kappa = 0.804 mu = 0.2195149727645171 beta = 0.06672455060314922 elif name in ['pyPBEsol', 'pyzvPBEsol']: name = name[2:] kappa = 0.804 mu = 10. / 81. beta = 0.046 elif name == 'pyRPBE': name = 'RPBE' kappa = 0.804 mu = 0.2195149727645171 beta = 0.06672455060314922 else: raise NotImplementedError(name) return name, kappa, mu, beta # a2 = |grad n|^2 def gga_x(name, spin, n, a2, kappa, mu, dedmu_g=None): # if dedmu is given, calculate also d(e_x)/d(mu) assert spin in [0, 1] C0I, C1, C2, C3, CC1, CC2, IF2, GAMMA = gga_constants() rs = (C0I / n)**(1 / 3.) # lda part ex = C1 / rs dexdrs = -ex / rs # gga part c = (C2 * rs / n)**2. s2 = a2 * c if name in ['PBE', 'PBEsol', 'zvPBEsol', 'QNA']: x = 1.0 + mu * s2 / kappa Fx = 1.0 + kappa - kappa / x dFxds2 = mu / (x**2.) elif name == 'RPBE': arg = np.maximum(-mu * s2 / kappa, -5.e2) x = np.exp(arg) Fx = 1.0 + kappa * (1.0 - x) dFxds2 = mu * x else: raise NotImplementedError ds2drs = 8.0 * c * a2 / rs dexdrs = dexdrs * Fx + ex * dFxds2 * ds2drs dexda2 = ex * dFxds2 * c if dedmu_g is not None: dedmu_g[:] = ex * s2 / (1 + mu * s2 / kappa)**2 ex *= Fx return ex, rs, dexdrs, dexda2 def gga_c(name, spin, n, a2, zeta, BETA, decdbeta_g=None): # If decdbeta is specified, calculate also d(e_c)/d(beta) assert spin in [0, 1] from gpaw.xc.lda import G C0I, C1, C2, C3, CC1, CC2, IF2, GAMMA = gga_constants() rs = (C0I / n)**(1 / 3.) if name == 'zvPBEsol': zv_a = 1.8 zv_o = 9. / 2. zv_x = 1. / 6. # lda part ec, decdrs_0 = G(rs**0.5, 0.031091, 0.21370, 7.5957, 3.5876, 1.6382, 0.49294) if spin == 0: decdrs = decdrs_0 decdzeta = 0. # dummy else: e1, decdrs_1 = G(rs**0.5, 0.015545, 0.20548, 14.1189, 6.1977, 3.3662, 0.62517) alpha, dalphadrs = G(rs**0.5, 0.016887, 0.11125, 10.357, 3.6231, 0.88026, 0.49671) alpha *= -1. dalphadrs *= -1. zp = 1.0 + zeta zm = 1.0 - zeta xp = zp**(1 / 3.) xm = zm**(1 / 3.) f = CC1 * (zp * xp + zm * xm - 2.0) f1 = CC2 * (xp - xm) zeta3 = zeta * zeta * zeta zeta4 = zeta * zeta * zeta * zeta x = 1.0 - zeta4 decdrs = (decdrs_0 * (1.0 - f * zeta4) + decdrs_1 * f * zeta4 + dalphadrs * f * x * IF2) decdzeta = (4.0 * zeta3 * f * (e1 - ec - alpha * IF2) + f1 * (zeta4 * e1 - zeta4 * ec + x * alpha * IF2)) ec += alpha * IF2 * f * x + (e1 - ec) * f * zeta4 # gga part n2 = n * n if spin == 1: phi = 0.5 * (xp * xp + xm * xm) phi2 = phi * phi phi3 = phi * phi2 t2 = C3 * a2 * rs / (n2 * phi2) y = -ec / (GAMMA * phi3) if name == 'zvPBEsol': u3 = t2**(3. / 2.) * phi3 * (rs / 3.)**(-3. * zv_x) zvarg = -zv_a * u3 * abs(zeta)**zv_o zvf = np.exp(zvarg) else: t2 = C3 * a2 * rs / n2 y = -ec / GAMMA x = np.exp(y) A = np.zeros_like(x) indices = np.nonzero(y) if isinstance(BETA, np.ndarray): A[indices] = (BETA[indices] / (GAMMA * (x[indices] - 1.0))) else: A[indices] = (BETA / (GAMMA * (x[indices] - 1.0))) At2 = A * t2 nom = 1.0 + At2 denom = nom + At2 * At2 X = 1.0 + BETA * t2 * nom / (denom * GAMMA) H = GAMMA * np.log(X) tmp = (GAMMA * BETA / (denom * (BETA * t2 * nom + GAMMA * denom))) tmp2 = A * A * x / BETA dAdrs = tmp2 * decdrs if spin == 1: H *= phi3 tmp *= phi3 dAdrs /= phi3 if name == 'zvPBEsol': H_ = H.copy() H *= zvf dHdt2 = (1.0 + 2.0 * At2) * tmp dHdA = -At2 * t2 * t2 * (2.0 + At2) * tmp decdrs += dHdt2 * 7.0 * t2 / rs + dHdA * dAdrs decda2 = dHdt2 * C3 * rs / n2 if spin == 1: dphidzeta = np.zeros_like(x) ind1 = np.nonzero(xp) ind2 = np.nonzero(xm) dphidzeta[ind1] += 1.0 / (3.0 * xp[ind1]) dphidzeta[ind2] -= 1.0 / (3.0 * xm[ind2]) dAdzeta = tmp2 * (decdzeta - 3.0 * ec * dphidzeta / phi) / phi3 decdzeta += ((3.0 * H / phi - dHdt2 * 2.0 * t2 / phi) * dphidzeta + dHdA * dAdzeta) decda2 /= phi2 if name == 'zvPBEsol': u3_ = (t2**(3. / 2.) * phi3 * rs**(-3. * zv_x - 1.) / 3.**(-3. * zv_x)) zvarg_ = -zv_a * u3_ * abs(zeta)**zv_o dzvfdrs = -3. * zv_x * zvf * zvarg_ decdrs *= zvf decdrs += H_ * dzvfdrs dt2da2 = C3 * rs / n2 assert np.shape(dt2da2) == np.shape(t2) dzvfda2 = dt2da2 * zvf * zvarg * 3. / (2. * t2) decda2 *= zvf decda2 += H_ * dzvfda2 dadz = zv_o * abs(zeta)**(zv_o - 1.) dbdphi = 3. * u3 / phi dPdzeta = u3 * dadz + abs(zeta)**(zv_o) * dbdphi * dphidzeta dzvfdzeta = -zv_a * zvf * dPdzeta decdzeta *= zvf decdzeta += H_ * dzvfdzeta ec += H if decdbeta_g is not None: if spin == 0: phi3 = 1.0 Y = GAMMA * phi3 decdbeta_g[:] = Y / X * t2 / GAMMA decdbeta_g *= ((1 + 2 * At2) / (1 + At2 + At2**2) - (1 + At2) * (At2 + 2 * At2**2) / (1 + At2 + At2**2)**2) return ec, rs, decdrs, decda2, decdzeta def gga_constants(): from gpaw.xc.lda import lda_constants C0I, C1, CC1, CC2, IF2 = lda_constants() C2 = 0.26053088059892404 C3 = 0.10231023756535741 GAMMA = 0.0310906908697 return C0I, C1, C2, C3, CC1, CC2, IF2, GAMMA