use nalgebra::SVector;
use num_dual::{gradient, hessian, Dual2SVec64, DualNum, DualSVec64};
use super::{check_len, XcEval};
use crate::error::XcError;
use crate::func::{FunctionalInfo, Spin};
use crate::io::{XcInput, XcResult};
use crate::reduced::vars;
#[derive(Clone, Copy)]
pub(crate) struct GgaVars<N> {
pub rs: N,
pub z: N,
pub opz: N,
pub omz: N,
pub na: N,
pub nb: N,
pub xt2: N,
pub xs0_sq: N,
pub xs1_sq: N,
}
pub(crate) fn gga_exchange<N, F>(
v: &GgaVars<N>,
dens_threshold: f64,
zeta_threshold: f64,
enhancement: F,
) -> N
where
N: DualNum<f64> + Copy,
F: Fn(N) -> N,
{
let up = if vars::screen_dens(v.na, dens_threshold) {
N::from(0.0)
} else {
vars::lda_x_spin(v.rs, v.opz, zeta_threshold) * enhancement(v.xs0_sq)
};
let dn = if vars::screen_dens(v.nb, dens_threshold) {
N::from(0.0)
} else {
vars::lda_x_spin(v.rs, v.omz, zeta_threshold) * enhancement(v.xs1_sq)
};
up + dn
}
pub(crate) trait GgaEnergy: Send + Sync {
fn info(&self) -> &FunctionalInfo;
fn f<N: DualNum<f64> + Copy>(&self, v: GgaVars<N>) -> N;
}
pub(crate) struct Gga<F: GgaEnergy>(pub F);
impl<F: GgaEnergy> XcEval for Gga<F> {
fn info(&self) -> &FunctionalInfo {
self.0.info()
}
fn eval(&self, spin: Spin, np: usize, input: &XcInput) -> Result<XcResult, XcError> {
let sigma = input.sigma.ok_or(XcError::MissingInput("sigma"))?;
match spin {
Spin::Unpolarized => self.eval_unpol(np, input.rho, sigma),
Spin::Polarized => self.eval_pol(np, input.rho, sigma),
}
}
fn eval_fxc(&self, spin: Spin, np: usize, input: &XcInput) -> Result<XcResult, XcError> {
let sigma = input.sigma.ok_or(XcError::MissingInput("sigma"))?;
match spin {
Spin::Unpolarized => self.eval_fxc_unpol(np, input.rho, sigma),
Spin::Polarized => self.eval_fxc_pol(np, input.rho, sigma),
}
}
}
impl<F: GgaEnergy> Gga<F> {
fn sigma_floor(&self) -> f64 {
let st = self.0.info().dens_threshold.powf(4.0 / 3.0);
st * st
}
fn energy_unpol<N: DualNum<f64> + Copy>(&self, x: &SVector<N, 2>) -> N {
let n = x[0];
let s = x[1];
let rs = vars::rs_from_n(n);
let half = n / N::from(2.0);
let xt2 = vars::reduced_grad_sq(s, n);
let xs_sq = vars::reduced_grad_sq(s / N::from(4.0), half);
n * self.0.f(GgaVars {
rs,
z: N::from(0.0),
opz: N::from(1.0),
omz: N::from(1.0),
na: half,
nb: half,
xt2,
xs0_sq: xs_sq,
xs1_sq: xs_sq,
})
}
fn energy_pol<N: DualNum<f64> + Copy>(&self, x: &SVector<N, 5>) -> N {
let na = x[0];
let nb = x[1];
let saa = x[2];
let sab = x[3];
let sbb = x[4];
let n = na + nb;
let rs = vars::rs_from_n(n);
let z = (na - nb) / n;
let opz = (na + na) / n; let omz = (nb + nb) / n; let sigma_tot = saa + sab + sab + sbb; let xt2 = vars::reduced_grad_sq(sigma_tot, n);
let xs0_sq = vars::reduced_grad_sq(saa, na);
let xs1_sq = vars::reduced_grad_sq(sbb, nb);
n * self.0.f(GgaVars {
rs,
z,
opz,
omz,
na,
nb,
xt2,
xs0_sq,
xs1_sq,
})
}
fn seed_pol(&self, na: f64, nb: f64, saa: f64, sab: f64, sbb: f64) -> (SVector<f64, 5>, f64) {
let thr = self.0.info().dens_threshold;
let sfloor = self.sigma_floor();
let na_f = na.max(thr);
let nb_f = nb.max(thr);
let saa_f = saa.max(sfloor);
let sbb_f = sbb.max(sfloor);
let s_ave = 0.5 * (saa_f + sbb_f);
let sab = if sab >= -s_ave { sab } else { -s_ave };
let sab_c = if sab <= s_ave { sab } else { s_ave };
(
SVector::<f64, 5>::from([na_f, nb_f, saa_f, sab_c, sbb_f]),
na_f + nb_f,
)
}
fn eval_unpol(&self, np: usize, rho: &[f64], sigma: &[f64]) -> Result<XcResult, XcError> {
check_len(rho, np)?;
check_len(sigma, np)?;
let thr = self.0.info().dens_threshold;
let sfloor = self.sigma_floor();
let mut exc = vec![0.0; np];
let mut vrho = vec![0.0; np];
let mut vsigma = vec![0.0; np];
for i in 0..np {
let n = rho[i];
if n < thr || n.is_nan() {
continue;
}
let nf = n.max(thr);
let sf = sigma[i].max(sfloor); let (e, g) = gradient(
|v: SVector<DualSVec64<2>, 2>| self.energy_unpol(&v),
&SVector::<f64, 2>::from([nf, sf]),
);
exc[i] = e / nf;
vrho[i] = g[0];
vsigma[i] = g[1];
}
Ok(XcResult {
exc,
vrho,
vsigma,
..Default::default()
})
}
fn eval_pol(&self, np: usize, rho: &[f64], sigma: &[f64]) -> Result<XcResult, XcError> {
check_len(rho, 2 * np)?;
check_len(sigma, 3 * np)?;
let thr = self.0.info().dens_threshold;
let mut exc = vec![0.0; np];
let mut vrho = vec![0.0; 2 * np];
let mut vsigma = vec![0.0; 3 * np];
for i in 0..np {
let na = rho[2 * i];
let nb = rho[2 * i + 1];
let n = na + nb;
if n < thr || n.is_nan() {
continue;
}
let (seed, n_f) =
self.seed_pol(na, nb, sigma[3 * i], sigma[3 * i + 1], sigma[3 * i + 2]);
let (e, g) = gradient(|v: SVector<DualSVec64<5>, 5>| self.energy_pol(&v), &seed);
exc[i] = e / n_f;
vrho[2 * i] = g[0];
vrho[2 * i + 1] = g[1];
vsigma[3 * i] = g[2];
vsigma[3 * i + 1] = g[3];
vsigma[3 * i + 2] = g[4];
}
Ok(XcResult {
exc,
vrho,
vsigma,
..Default::default()
})
}
fn eval_fxc_unpol(&self, np: usize, rho: &[f64], sigma: &[f64]) -> Result<XcResult, XcError> {
check_len(rho, np)?;
check_len(sigma, np)?;
let thr = self.0.info().dens_threshold;
let sfloor = self.sigma_floor();
let mut exc = vec![0.0; np];
let mut vrho = vec![0.0; np];
let mut vsigma = vec![0.0; np];
let mut v2rho2 = vec![0.0; np];
let mut v2rhosigma = vec![0.0; np];
let mut v2sigma2 = vec![0.0; np];
for i in 0..np {
let n = rho[i];
if n < thr || n.is_nan() {
continue; }
let nf = n.max(thr);
let sf = sigma[i].max(sfloor);
let (e, g, h) = hessian(
|v: SVector<Dual2SVec64<2>, 2>| self.energy_unpol(&v),
&SVector::<f64, 2>::from([nf, sf]),
);
exc[i] = e / nf;
vrho[i] = g[0];
vsigma[i] = g[1];
v2rho2[i] = h[(0, 0)];
v2rhosigma[i] = h[(0, 1)];
v2sigma2[i] = h[(1, 1)];
}
Ok(XcResult {
exc,
vrho,
vsigma,
v2rho2,
v2rhosigma,
v2sigma2,
..Default::default()
})
}
fn eval_fxc_pol(&self, np: usize, rho: &[f64], sigma: &[f64]) -> Result<XcResult, XcError> {
check_len(rho, 2 * np)?;
check_len(sigma, 3 * np)?;
let thr = self.0.info().dens_threshold;
let mut exc = vec![0.0; np];
let mut vrho = vec![0.0; 2 * np];
let mut vsigma = vec![0.0; 3 * np];
let mut v2rho2 = vec![0.0; 3 * np];
let mut v2rhosigma = vec![0.0; 6 * np];
let mut v2sigma2 = vec![0.0; 6 * np];
for i in 0..np {
let na = rho[2 * i];
let nb = rho[2 * i + 1];
let n = na + nb;
if n < thr || n.is_nan() {
continue;
}
let (seed, n_f) =
self.seed_pol(na, nb, sigma[3 * i], sigma[3 * i + 1], sigma[3 * i + 2]);
let (e, g, h) = hessian(|v: SVector<Dual2SVec64<5>, 5>| self.energy_pol(&v), &seed);
exc[i] = e / n_f;
vrho[2 * i] = g[0];
vrho[2 * i + 1] = g[1];
vsigma[3 * i] = g[2];
vsigma[3 * i + 1] = g[3];
vsigma[3 * i + 2] = g[4];
v2rho2[3 * i] = h[(0, 0)];
v2rho2[3 * i + 1] = h[(0, 1)];
v2rho2[3 * i + 2] = h[(1, 1)];
v2rhosigma[6 * i] = h[(0, 2)];
v2rhosigma[6 * i + 1] = h[(0, 3)];
v2rhosigma[6 * i + 2] = h[(0, 4)];
v2rhosigma[6 * i + 3] = h[(1, 2)];
v2rhosigma[6 * i + 4] = h[(1, 3)];
v2rhosigma[6 * i + 5] = h[(1, 4)];
v2sigma2[6 * i] = h[(2, 2)];
v2sigma2[6 * i + 1] = h[(2, 3)];
v2sigma2[6 * i + 2] = h[(2, 4)];
v2sigma2[6 * i + 3] = h[(3, 3)];
v2sigma2[6 * i + 4] = h[(3, 4)];
v2sigma2[6 * i + 5] = h[(4, 4)];
}
Ok(XcResult {
exc,
vrho,
vsigma,
v2rho2,
v2rhosigma,
v2sigma2,
..Default::default()
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::func::{Family, FunctionalId, Kind};
struct DummyGga(FunctionalInfo);
impl GgaEnergy for DummyGga {
fn info(&self) -> &FunctionalInfo {
&self.0
}
fn f<N: DualNum<f64> + Copy>(&self, v: GgaVars<N>) -> N {
let c = N::from(1e-3);
-v.rs.recip() + (v.xs0_sq + v.xs1_sq) * c
}
}
fn dummy() -> Gga<DummyGga> {
Gga(DummyGga(FunctionalInfo {
id: Some(FunctionalId::GgaXPbe),
name: "dummy_gga",
family: Family::Gga,
kind: Kind::Exchange,
needs_sigma: true,
needs_lapl: false,
needs_tau: false,
dens_threshold: 1e-15,
hybrid: None,
}))
}
#[test]
fn missing_sigma_errors() {
let f = dummy();
let rho = [0.3];
let err = f
.eval(Spin::Unpolarized, 1, &XcInput::lda(&rho))
.unwrap_err();
assert_eq!(err, XcError::MissingInput("sigma"));
}
#[test]
fn unpol_runs_finite() {
let f = dummy();
let rho = [0.1, 0.5];
let sigma = [0.01, 0.2];
let out = f
.eval(Spin::Unpolarized, 2, &XcInput::gga(&rho, &sigma))
.unwrap();
assert_eq!(out.vsigma.len(), 2);
assert!(out
.exc
.iter()
.chain(&out.vrho)
.chain(&out.vsigma)
.all(|v| v.is_finite()));
}
#[test]
fn pol_runs_finite() {
let f = dummy();
let rho = [0.05, 0.05, 0.3, 0.2];
let sigma = [0.01, 0.005, 0.01, 0.2, 0.1, 0.15];
let out = f
.eval(Spin::Polarized, 2, &XcInput::gga(&rho, &sigma))
.unwrap();
assert_eq!(out.vrho.len(), 4);
assert_eq!(out.vsigma.len(), 6);
assert!(out.vrho.iter().chain(&out.vsigma).all(|v| v.is_finite()));
}
#[test]
fn full_polarization_is_finite() {
let f = dummy();
let rho = [1.0, 0.0];
let sigma = [0.1, 0.0, 0.0];
let out = f
.eval(Spin::Polarized, 1, &XcInput::gga(&rho, &sigma))
.unwrap();
assert!(out
.exc
.iter()
.chain(&out.vrho)
.chain(&out.vsigma)
.all(|v| v.is_finite()));
}
}