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;
const TAU_THRESHOLD: f64 = 1e-20;
const ENFORCE_FHC: bool = true;
#[inline]
fn fhc_clamp(sigma: f64, n: f64, tau: f64) -> f64 {
if ENFORCE_FHC {
sigma.min(8.0 * n * tau)
} else {
sigma
}
}
#[derive(Clone, Copy)]
pub(crate) struct MggaVars<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 t0: N,
pub t1: N,
}
pub(crate) fn mgga_exchange<N, F>(
v: &MggaVars<N>,
dens_threshold: f64,
zeta_threshold: f64,
enhancement: F,
) -> N
where
N: DualNum<f64> + Copy,
F: Fn(N, 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, v.t0)
};
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, v.t1)
};
up + dn
}
pub(crate) fn rscan_f_alpha<N: DualNum<f64> + Copy>(
a: N,
c1: f64,
c2: f64,
d: f64,
coeffs: &[f64; 8],
) -> N {
if a.re() <= 0.0 {
(-N::from(c1) * a / (N::from(1.0) - a)).exp()
} else if a.re() <= 2.5 {
let mut poly = N::from(coeffs[0]);
for &c in &coeffs[1..] {
poly = poly * a + N::from(c);
}
poly
} else {
-N::from(d) * (N::from(c2) / (N::from(1.0) - a)).exp()
}
}
pub(crate) fn gtv4<N: DualNum<f64> + Copy>(alpha: f64, d: &[f64; 6], w: N, z: N) -> N {
let gamm = N::from(1.0) + N::from(alpha) * (w + z);
let g2 = gamm * gamm;
let g3 = g2 * gamm;
N::from(d[0]) / gamm
+ (N::from(d[1]) * w + N::from(d[2]) * z) / g2
+ (N::from(d[3]) * (w * w) + N::from(d[4]) * (w * z) + N::from(d[5]) * (z * z)) / g3
}
pub(crate) trait MggaEnergy: Send + Sync {
fn info(&self) -> &FunctionalInfo;
fn f<N: DualNum<f64> + Copy>(&self, v: MggaVars<N>) -> N;
}
pub(crate) struct Mgga<F: MggaEnergy>(pub F);
impl<F: MggaEnergy> XcEval for Mgga<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"))?;
let tau = input.tau.ok_or(XcError::MissingInput("tau"))?;
match spin {
Spin::Unpolarized => self.eval_unpol(np, input.rho, sigma, tau),
Spin::Polarized => self.eval_pol(np, input.rho, sigma, tau),
}
}
fn eval_fxc(&self, spin: Spin, np: usize, input: &XcInput) -> Result<XcResult, XcError> {
let sigma = input.sigma.ok_or(XcError::MissingInput("sigma"))?;
let tau = input.tau.ok_or(XcError::MissingInput("tau"))?;
match spin {
Spin::Unpolarized => self.eval_fxc_unpol(np, input.rho, sigma, tau),
Spin::Polarized => self.eval_fxc_pol(np, input.rho, sigma, tau),
}
}
}
impl<F: MggaEnergy> Mgga<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, 3>) -> N {
let n = x[0];
let s = x[1];
let tau = x[2];
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);
let t = vars::reduced_tau(tau / N::from(2.0), half);
n * self.0.f(MggaVars {
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,
t0: t,
t1: t,
})
}
fn energy_pol<N: DualNum<f64> + Copy>(&self, x: &SVector<N, 7>) -> N {
let na = x[0];
let nb = x[1];
let saa = x[2];
let sab = x[3];
let sbb = x[4];
let ta = x[5];
let tb = x[6];
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_raw = saa + sab + sab + sbb;
let sigma_tot = if sigma_tot_raw.re() < 0.0 {
N::from(0.0)
} else {
sigma_tot_raw
};
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);
let t0 = vars::reduced_tau(ta, na);
let t1 = vars::reduced_tau(tb, nb);
n * self.0.f(MggaVars {
rs,
z,
opz,
omz,
na,
nb,
xt2,
xs0_sq,
xs1_sq,
t0,
t1,
})
}
#[allow(clippy::too_many_arguments)]
fn seed_pol(
&self,
na: f64,
nb: f64,
saa: f64,
sab: f64,
sbb: f64,
ta: f64,
tb: f64,
) -> (SVector<f64, 7>, 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 ta_f = ta.max(TAU_THRESHOLD);
let tb_f = tb.max(TAU_THRESHOLD);
let saa_f = fhc_clamp(saa.max(sfloor), na_f, ta_f);
let sbb_f = fhc_clamp(sbb.max(sfloor), nb_f, tb_f);
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, 7>::from([na_f, nb_f, saa_f, sab_c, sbb_f, ta_f, tb_f]),
na_f + nb_f,
)
}
fn eval_unpol(
&self,
np: usize,
rho: &[f64],
sigma: &[f64],
tau: &[f64],
) -> Result<XcResult, XcError> {
check_len(rho, np)?;
check_len(sigma, np)?;
check_len(tau, 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 vtau = 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 tf = tau[i].max(TAU_THRESHOLD);
let sf = fhc_clamp(sigma[i].max(sfloor), nf, tf);
let (e, g) = gradient(
|v: SVector<DualSVec64<3>, 3>| self.energy_unpol(&v),
&SVector::<f64, 3>::from([nf, sf, tf]),
);
exc[i] = e / nf;
vrho[i] = g[0];
vsigma[i] = g[1];
vtau[i] = g[2];
}
Ok(XcResult {
exc,
vrho,
vsigma,
vtau,
..Default::default()
})
}
fn eval_pol(
&self,
np: usize,
rho: &[f64],
sigma: &[f64],
tau: &[f64],
) -> Result<XcResult, XcError> {
check_len(rho, 2 * np)?;
check_len(sigma, 3 * np)?;
check_len(tau, 2 * 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 vtau = vec![0.0; 2 * 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],
tau[2 * i],
tau[2 * i + 1],
);
let (e, g) = gradient(|v: SVector<DualSVec64<7>, 7>| 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];
vtau[2 * i] = g[5];
vtau[2 * i + 1] = g[6];
}
Ok(XcResult {
exc,
vrho,
vsigma,
vtau,
..Default::default()
})
}
fn eval_fxc_unpol(
&self,
np: usize,
rho: &[f64],
sigma: &[f64],
tau: &[f64],
) -> Result<XcResult, XcError> {
check_len(rho, np)?;
check_len(sigma, np)?;
check_len(tau, 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 vtau = 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];
let mut v2rhotau = vec![0.0; np];
let mut v2sigmatau = vec![0.0; np];
let mut v2tau2 = 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 tf = tau[i].max(TAU_THRESHOLD);
let sf = fhc_clamp(sigma[i].max(sfloor), nf, tf);
let (e, g, h) = hessian(
|v: SVector<Dual2SVec64<3>, 3>| self.energy_unpol(&v),
&SVector::<f64, 3>::from([nf, sf, tf]),
);
exc[i] = e / nf;
vrho[i] = g[0];
vsigma[i] = g[1];
vtau[i] = g[2];
v2rho2[i] = h[(0, 0)];
v2rhosigma[i] = h[(0, 1)];
v2sigma2[i] = h[(1, 1)];
v2rhotau[i] = h[(0, 2)];
v2sigmatau[i] = h[(1, 2)];
v2tau2[i] = h[(2, 2)];
}
Ok(XcResult {
exc,
vrho,
vsigma,
vtau,
v2rho2,
v2rhosigma,
v2sigma2,
v2rhotau,
v2sigmatau,
v2tau2,
..Default::default()
})
}
fn eval_fxc_pol(
&self,
np: usize,
rho: &[f64],
sigma: &[f64],
tau: &[f64],
) -> Result<XcResult, XcError> {
check_len(rho, 2 * np)?;
check_len(sigma, 3 * np)?;
check_len(tau, 2 * 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 vtau = vec![0.0; 2 * 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];
let mut v2rhotau = vec![0.0; 4 * np];
let mut v2sigmatau = vec![0.0; 6 * np];
let mut v2tau2 = 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],
tau[2 * i],
tau[2 * i + 1],
);
let (e, g, h) = hessian(|v: SVector<Dual2SVec64<7>, 7>| 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];
vtau[2 * i] = g[5];
vtau[2 * i + 1] = g[6];
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)];
v2rhotau[4 * i] = h[(0, 5)];
v2rhotau[4 * i + 1] = h[(0, 6)];
v2rhotau[4 * i + 2] = h[(1, 5)];
v2rhotau[4 * i + 3] = h[(1, 6)];
v2sigmatau[6 * i] = h[(2, 5)];
v2sigmatau[6 * i + 1] = h[(2, 6)];
v2sigmatau[6 * i + 2] = h[(3, 5)];
v2sigmatau[6 * i + 3] = h[(3, 6)];
v2sigmatau[6 * i + 4] = h[(4, 5)];
v2sigmatau[6 * i + 5] = h[(4, 6)];
v2tau2[3 * i] = h[(5, 5)];
v2tau2[3 * i + 1] = h[(5, 6)];
v2tau2[3 * i + 2] = h[(6, 6)];
}
Ok(XcResult {
exc,
vrho,
vsigma,
vtau,
v2rho2,
v2rhosigma,
v2sigma2,
v2rhotau,
v2sigmatau,
v2tau2,
..Default::default()
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::func::{Family, FunctionalId, Kind};
struct DummyMgga(FunctionalInfo);
impl MggaEnergy for DummyMgga {
fn info(&self) -> &FunctionalInfo {
&self.0
}
fn f<N: DualNum<f64> + Copy>(&self, v: MggaVars<N>) -> N {
let c = N::from(1e-3);
-v.rs.recip() + (v.xs0_sq + v.xs1_sq) * c + (v.t0 + v.t1) * c
}
}
fn dummy() -> Mgga<DummyMgga> {
Mgga(DummyMgga(FunctionalInfo {
id: Some(FunctionalId::MggaXTpss),
name: "dummy_mgga",
family: Family::Mgga,
kind: Kind::Exchange,
needs_sigma: true,
needs_lapl: false,
needs_tau: true,
dens_threshold: 1e-15,
hybrid: None,
}))
}
#[test]
fn missing_tau_errors() {
let f = dummy();
let rho = [0.3];
let sigma = [0.01];
let err = f
.eval(Spin::Unpolarized, 1, &XcInput::gga(&rho, &sigma))
.unwrap_err();
assert_eq!(err, XcError::MissingInput("tau"));
}
#[test]
fn unpol_runs_finite() {
let f = dummy();
let rho = [0.1, 0.5];
let sigma = [0.01, 0.2];
let tau = [0.05, 0.3];
let out = f
.eval(
Spin::Unpolarized,
2,
&XcInput::gga(&rho, &sigma).with_tau(&tau),
)
.unwrap();
assert_eq!(out.vtau.len(), 2);
assert!(out
.exc
.iter()
.chain(&out.vrho)
.chain(&out.vsigma)
.chain(&out.vtau)
.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 tau = [0.02, 0.02, 0.1, 0.08];
let out = f
.eval(
Spin::Polarized,
2,
&XcInput::gga(&rho, &sigma).with_tau(&tau),
)
.unwrap();
assert_eq!(out.vrho.len(), 4);
assert_eq!(out.vsigma.len(), 6);
assert_eq!(out.vtau.len(), 4);
assert!(out
.vrho
.iter()
.chain(&out.vsigma)
.chain(&out.vtau)
.all(|v| v.is_finite()));
}
#[test]
fn fxc_blocks_have_right_lengths() {
let f = dummy();
let rho = [0.6, 0.3];
let sigma = [0.1, 0.04, 0.08];
let tau = [0.2, 0.1];
let out = f
.eval_fxc(
Spin::Polarized,
1,
&XcInput::gga(&rho, &sigma).with_tau(&tau),
)
.unwrap();
assert_eq!(out.v2rho2.len(), 3);
assert_eq!(out.v2rhosigma.len(), 6);
assert_eq!(out.v2sigma2.len(), 6);
assert_eq!(out.v2rhotau.len(), 4);
assert_eq!(out.v2sigmatau.len(), 6);
assert_eq!(out.v2tau2.len(), 3);
assert!(out
.v2rho2
.iter()
.chain(&out.v2rhosigma)
.chain(&out.v2sigma2)
.chain(&out.v2rhotau)
.chain(&out.v2sigmatau)
.chain(&out.v2tau2)
.all(|v| v.is_finite()));
}
}