from __future__ import annotations
from pathlib import Path
import numpy as np
from ase.units import Ha
from gpaw import GPAW
from gpaw.new.ase_interface import ASECalculator
from gpaw.kpt_descriptor import KPointDescriptor
from gpaw.mpi import serial_comm
from gpaw.pw.descriptor import PWDescriptor
from gpaw.pw.lfc import PWLFC
from gpaw.xc import XC
from . import parse_name
from .coulomb import coulomb_interaction
from .kpts import get_kpt
from .paw import calculate_paw_stuff
from .symmetry import Symmetry
from gpaw.typing import Array1D
[docs]
def non_self_consistent_energy(calc: ASECalculator | str | Path,
xcname: str,
ftol=1e-9) -> Array1D:
"""Calculate non self-consistent energy for Hybrid functional.
Based on a self-consistent DFT calculation (calc). EXX integrals involving
states with occupation numbers less than ftol are skipped.
>>> energies = non_self_consistent_energy('<gpw-file>',
... xcname='HSE06')
>>> e_hyb = energies.sum()
The correction to the self-consistent energy will be
``energies[1:].sum()``.
The returned energy contributions are (in eV):
1. DFT total free energy (not extrapolated to zero smearing)
2. minus DFT XC energy
3. Hybrid semi-local XC energy
4. EXX core-core energy
5. EXX core-valence energy
6. EXX valence-valence energy
"""
if calc == '<gpw-file>': # for doctest
return np.zeros(6)
if isinstance(calc, (str, Path)):
calc = GPAW(calc, txt=None, parallel={'band': 1, 'kpt': 1})
assert not isinstance(calc, (str, Path)) # for mypy
wfs = calc.wfs
dens = calc.density
kd = wfs.kd
setups = wfs.setups
nspins = wfs.nspins
nocc = max(((kpt.f_n / kpt.weight) > ftol).sum()
for kpt in wfs.kpt_u)
nocc = kd.comm.max_scalar(wfs.bd.comm.sum_scalar(int(nocc)))
xcname, exx_fraction, omega = parse_name(xcname)
xc = XC(xcname)
exc = 0.0
for a, D_sp in dens.D_asp.items():
exc += xc.calculate_paw_correction(setups[a], D_sp)
exc = dens.finegd.comm.sum_scalar(exc)
if dens.nt_sg is None:
dens.interpolate_pseudo_density()
exc += xc.calculate(dens.finegd, dens.nt_sg)
coulomb = coulomb_interaction(omega, wfs.gd, kd)
sym = Symmetry(kd)
paw_s = calculate_paw_stuff(wfs, dens)
ecc = sum(setup.ExxC for setup in setups) * exx_fraction
evc = 0.0
evv = 0.0
for spin in range(nspins):
kpts = [get_kpt(wfs, k, spin, 0, nocc) for k in range(kd.nibzkpts)]
e1, e2 = calculate_energy(kpts, paw_s[spin],
wfs, sym, coulomb, calc.spos_ac)
evc += e1 * exx_fraction * 2 / wfs.nspins
evv += e2 * exx_fraction * 2 / wfs.nspins
return np.array([calc.hamiltonian.e_total_free,
-calc.hamiltonian.e_xc,
exc,
ecc,
evc,
evv]) * Ha
def calculate_energy(kpts, paw, wfs, sym, coulomb, spos_ac):
pd = kpts[0].psit.pd
gd = pd.gd.new_descriptor(comm=serial_comm)
comm = wfs.world
exxvv = 0.0
exxvc = 0.0
for i, kpt in enumerate(kpts):
for a, VV_ii in paw.VV_aii.items():
P_ni = kpt.proj[a]
vv_n = np.einsum('ni, ij, nj -> n',
P_ni.conj(), VV_ii, P_ni).real
vc_n = np.einsum('ni, ij, nj -> n',
P_ni.conj(), paw.VC_aii[a], P_ni).real
exxvv -= vv_n.dot(kpt.f_n) * kpt.weight
exxvc -= vc_n.dot(kpt.f_n) * kpt.weight
for i1, i2, s, k1, k2, count in sym.pairs(kpts, wfs, spos_ac):
q_c = k2.k_c - k1.k_c
qd = KPointDescriptor([-q_c])
pd12 = PWDescriptor(pd.ecut, gd, pd.dtype, kd=qd)
ghat = PWLFC([data.ghat_l for data in wfs.setups], pd12)
ghat.set_positions(spos_ac)
v_G = coulomb.get_potential(pd12)
e_nn = calculate_exx_for_pair(k1, k2, ghat, v_G, comm,
paw.Delta_aiiL)
e_nn *= count
e = k1.f_n.dot(e_nn).dot(k2.f_n) / sym.kd.nbzkpts**2
exxvv -= 0.5 * e
exxvv = comm.sum_scalar(exxvv)
exxvc = comm.sum_scalar(exxvc)
return exxvc, exxvv
def calculate_exx_for_pair(k1,
k2,
ghat,
v_G,
comm,
Delta_aiiL):
N1 = len(k1.u_nR)
N2 = len(k2.u_nR)
size = comm.size
rank = comm.rank
Q_annL = [np.einsum('mi, ijL, nj -> mnL',
k1.proj[a],
Delta_iiL,
k2.proj[a].conj())
for a, Delta_iiL in enumerate(Delta_aiiL)]
if k1 is k2:
n2max = (N1 + size - 1) // size
else:
n2max = N2
e_nn = np.zeros((N1, N2))
rho_nG = ghat.pd.empty(n2max, k1.u_nR.dtype)
for n1, u1_R in enumerate(k1.u_nR):
if k1 is k2:
B = (N1 - n1 + size - 1) // size
n2a = min(n1 + rank * B, N2)
n2b = min(n2a + B, N2)
else:
n2a = 0
n2b = N2
for n2, rho_G in enumerate(rho_nG[:n2b - n2a], n2a):
rho_G[:] = ghat.pd.fft(u1_R * k2.u_nR[n2].conj())
ghat.add(rho_nG[:n2b - n2a],
{a: Q_nnL[n1, n2a:n2b]
for a, Q_nnL in enumerate(Q_annL)})
for n2, rho_G in enumerate(rho_nG[:n2b - n2a], n2a):
vrho_G = v_G * rho_G
e = ghat.pd.integrate(rho_G, vrho_G).real
e_nn[n1, n2] = e
if k1 is k2:
e_nn[n2, n1] = e
return e_nn