from gpaw.xc.gga import GGA
import numpy as np
from gpaw.lfc import LFC
from gpaw.spline import Spline
from gpaw.xc.gga import gga_x, gga_c
class QNAKernel:
def __init__(self, qna):
self.qna = qna
self.type = 'GGA'
self.name = 'QNA'
self.kappa = 0.804
def calculate(self, e_g, n_sg, dedn_sg,
sigma_xg, dedsigma_xg,
tau_sg=None, dedtau_sg=None, mu_g=None, beta_g=None,
dedmu_g=None, dedbeta_g=None):
e_g[:] = 0.
dedsigma_xg[:] = 0.
if self.qna.override_atoms is not None:
atoms = self.qna.override_atoms
self.qna.Pa.set_positions(atoms.get_scaled_positions() % 1.0)
else:
atoms = self.qna.atoms
if len(n_sg.shape) > 2:
# 3D xc calculation
mu_g, beta_g = self.qna.calculate_spatial_parameters(atoms)
dedmu_g = self.qna.dedmu_g
dedbeta_g = self.qna.dedbeta_g
else:
# Atomic xc calculation: use always atomwise mu and beta parameters
mu, beta = self.qna.parameters[atoms[self.qna.current_atom].symbol]
mu_g = np.zeros_like(n_sg[0])
beta_g = np.zeros_like(n_sg[0])
mu_g[:] = mu
beta_g[:] = beta
dedmu_g = None
dedbeta_g = None
# Enable to use PBE always
# mu_g[:] = 0.2195149727645171
# beta_g[:] = 0.06672455060314922
# Write mu and beta fields
if 0:
from ase.io import write
write('mu_g.cube', atoms, data=mu_g)
write('beta_g.cube', atoms, data=beta_g)
raise SystemExit
# spin-paired: XXX Copy-paste from gga.py to prevent
# distruptions to pyGGA
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, mu_g,
dedmu_g=dedmu_g)
ex, rs, dexdrs, dexda2 = res
if dedmu_g is not None:
dedmu_g[:] = n * dedmu_g
# correlation
res = gga_c(self.name, 0, n, sigma_xg[0], 0, beta_g,
decdbeta_g=dedbeta_g)
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
if dedmu_g is not None:
dedmua_g = dedmu_g.copy()
dedmub_g = dedmu_g.copy()
else:
dedmua_g = None
dedmub_g = None
# exchange
exa, rsa, dexadrs, dexada2 = gga_x(
self.name, 1, na, 4.0 * sigma_xg[0], self.kappa, mu_g,
dedmu_g=dedmua_g)
exb, rsb, dexbdrs, dexbda2 = gga_x(
self.name, 1, nb, 4.0 * sigma_xg[2], self.kappa, mu_g,
dedmu_g=dedmub_g)
a2 = sigma_xg[0] + 2.0 * sigma_xg[1] + sigma_xg[2]
if dedmu_g is not None:
dedmu_g[:] = 0.5 * (na * dedmua_g + nb * dedmub_g)
# correlation
ec, rs, decdrs, decda2, decdzeta = gga_c(
self.name, 1, n, a2, zeta, beta_g, decdbeta_g=dedbeta_g)
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
if dedbeta_g is not None:
dedbeta_g[:] = dedbeta_g * n
[docs]
class QNA(GGA):
def __init__(self, atoms, parameters, qna_setup_name='PBE', alpha=2.0,
override_atoms=None, stencil=2):
# override_atoms is only used to test the partial derivatives
# of xc-functional
kernel = QNAKernel(self)
GGA.__init__(self, kernel, stencil=stencil)
self.atoms = atoms
self.parameters = parameters
self.qna_setup_name = qna_setup_name
self.alpha = alpha
self.override_atoms = override_atoms
self.orbital_dependent = False
[docs]
def todict(self):
dct = dict(type='qna-gga',
name='QNA',
setup_name=self.qna_setup_name,
parameters=self.parameters,
alpha=self.alpha,
orbital_dependent=False)
return dct
def set_grid_descriptor(self, gd):
GGA.set_grid_descriptor(self, gd)
self.dedmu_g = gd.zeros()
self.dedbeta_g = gd.zeros()
# Create gaussian LFC
l_lim = 1.0e-30
rcut = 12
points = 200
r_i = np.linspace(0, rcut, points + 1)
rcgauss = 1.2
g_g = (2 / rcgauss**3 / np.pi *
np.exp(-((r_i / rcgauss)**2)**self.alpha))
# Values too close to zero can cause numerical problems especially with
# forces (some parts of the mu and beta field can become negative)
g_g[np.where(g_g < l_lim)] = l_lim
spline = Spline.from_data(l=0, rmax=rcut, f_g=g_g)
spline_j = [[spline]] * len(self.atoms)
self.Pa = LFC(gd, spline_j)
def set_positions(self, spos_ac, atom_partition=None):
self.Pa.set_positions(spos_ac)
def calculate_spatial_parameters(self, atoms):
mu_g = self.gd.zeros()
beta_g = self.gd.zeros()
denominator = self.gd.zeros()
mu_a = {}
beta_a = {}
eye_a = {}
for atom in atoms:
mu, beta = self.parameters[atom.symbol]
mu_a[atom.index] = np.array([mu])
beta_a[atom.index] = np.array([beta])
eye_a[atom.index] = np.array(1.0)
self.Pa.add(mu_g, mu_a)
self.Pa.add(beta_g, beta_a)
self.Pa.add(denominator, eye_a)
mu_g /= denominator
beta_g /= denominator
return mu_g, beta_g
def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None,
addcoredensity=True, a=None):
self.current_atom = a
return GGA.calculate_paw_correction(self, setup, D_sp, dEdD_sp,
addcoredensity, a)
def get_setup_name(self):
return self.qna_setup_name
[docs]
def get_description(self):
return 'QNA Parameters: ' + str(self.parameters)
def add_forces(self, F_av):
assert self.gd.comm.size == 1, 'Domain decomposition is not supported'
mu_g = self.gd.zeros()
beta_g = self.gd.zeros()
denominator = self.gd.zeros()
mu_a = {}
beta_a = {}
eye_a = {}
for atom in self.atoms:
mu, beta = self.parameters[atom.symbol]
mu_a[atom.index] = np.array([mu])
beta_a[atom.index] = np.array([beta])
eye_a[atom.index] = np.array(1.0)
self.Pa.add(mu_g, mu_a)
self.Pa.add(beta_g, beta_a)
self.Pa.add(denominator, eye_a)
mu_g /= denominator
beta_g /= denominator
# mu
part1 = -self.dedmu_g / denominator
part2 = -part1 * mu_g
c_axiv = self.Pa.dict(derivative=True)
self.Pa.derivative(part1, c_axiv)
for atom in self.atoms:
F_av[atom.index] -= c_axiv[atom.index][0][:] * mu_a[atom.index][0]
c_axiv = self.Pa.dict(derivative=True)
self.Pa.derivative(part2, c_axiv)
for atom in self.atoms:
F_av[atom.index] -= c_axiv[atom.index][0][:]
# beta
part1 = -self.dedbeta_g / denominator
part2 = -part1 * beta_g
c_axiv = self.Pa.dict(derivative=True)
self.Pa.derivative(part1, c_axiv)
for atom in self.atoms:
F_av[atom.index] -= c_axiv[atom.index][0] * beta_a[atom.index][0]
c_axiv = self.Pa.dict(derivative=True)
self.Pa.derivative(part2, c_axiv)
for atom in self.atoms:
F_av[atom.index] -= c_axiv[atom.index][0][:]