use num_dual::DualNum;
use crate::families::gga::{gga_exchange, Gga, GgaEnergy, GgaVars};
use crate::families::XcEval;
use crate::func::{Family, FunctionalId, FunctionalInfo, Kind};
use crate::reduced::consts::X_FACTOR_C;
const BETA: f64 = 0.0042;
const GAMMA: f64 = 6.0;
const T_SWITCH: f64 = 0.1;
const G_COEFFS: [f64; 16] = [
1.0,
-0.166_666_666_666_666_66,
0.075,
-0.044_642_857_142_857_144,
0.030_381_944_444_444_444,
-0.022_372_159_090_909_092,
0.017_352_764_423_076_924,
-0.013_964_843_75,
0.011_551_800_896_139_705,
-0.009_761_609_529_194_078,
0.008_390_335_809_616_815,
-0.007_312_525_873_598_845_4,
0.006_447_210_311_889_649,
-0.005_740_037_670_841_924,
0.005_153_309_682_319_905,
-0.004_660_143_486_915_096,
];
fn b88_g<N: DualNum<f64> + Copy>(t: N) -> N {
if t.re() < T_SWITCH {
let mut p = N::from(G_COEFFS[G_COEFFS.len() - 1]);
for &c in G_COEFFS[..G_COEFFS.len() - 1].iter().rev() {
p = p * t + N::from(c);
}
p * t
} else {
let x = t.sqrt();
x * x.asinh()
}
}
fn b88_enhancement<N: DualNum<f64> + Copy>(t: N) -> N {
let denom = N::from(1.0) + N::from(GAMMA * BETA) * b88_g(t); N::from(1.0) + N::from(BETA / X_FACTOR_C) * t / denom
}
pub(crate) struct GgaXB88 {
info: FunctionalInfo,
zeta_threshold: f64,
}
impl GgaXB88 {
fn new() -> Self {
Self {
info: FunctionalInfo {
id: Some(FunctionalId::GgaXB88),
name: "gga_x_b88",
family: Family::Gga,
kind: Kind::Exchange,
needs_sigma: true,
needs_lapl: false,
needs_tau: false,
dens_threshold: 1e-15,
hybrid: None,
},
zeta_threshold: f64::EPSILON, }
}
pub(crate) fn boxed() -> Box<dyn XcEval> {
Box::new(Gga(Self::new()))
}
}
impl GgaEnergy for GgaXB88 {
fn info(&self) -> &FunctionalInfo {
&self.info
}
fn f<N: DualNum<f64> + Copy>(&self, v: GgaVars<N>) -> N {
gga_exchange(
&v,
self.info.dens_threshold,
self.zeta_threshold,
b88_enhancement,
)
}
}
#[cfg(test)]
mod tests {
use super::{b88_g, G_COEFFS, T_SWITCH};
use crate::{Functional, FunctionalId, Spin, XcInput};
#[test]
fn g_coeffs_match_recurrence() {
assert_eq!(G_COEFFS[0], 1.0);
let mut prev = 1.0_f64;
for (k, &g) in G_COEFFS.iter().enumerate().skip(1) {
let kk = k as f64;
prev *= -((2.0 * kk - 1.0).powi(2)) / (2.0 * kk * (2.0 * kk + 1.0));
assert!(
(g - prev).abs() <= 1e-15 * prev.abs(),
"G[{k}] = {g} vs recurrence {prev}"
);
}
}
#[test]
fn b88_g_series_matches_direct() {
assert_eq!(b88_g(0.0_f64), 0.0, "g(0) must be exactly 0");
for &t in &[1e-8_f64, 1e-3, 0.05, 0.09, T_SWITCH * (1.0 - 1e-12)] {
let direct = t.sqrt() * t.sqrt().asinh();
assert!(
(b88_g(t) - direct).abs() <= 1e-13 * direct.abs(),
"b88_g({t}) series {} vs direct {direct}",
b88_g(t)
);
}
let t = T_SWITCH * (1.0 + 1e-12);
assert!((b88_g(t) - t.sqrt() * t.sqrt().asinh()).abs() <= 1e-15);
}
fn b88(spin: Spin) -> Functional {
Functional::new(FunctionalId::GgaXB88, spin).unwrap()
}
#[test]
fn unpol_vrho_vsigma_match_finite_difference() {
let f = b88(Spin::Unpolarized);
let edens = |n: f64, s: f64| n * f.eval(1, &XcInput::gga(&[n], &[s])).unwrap().exc[0];
for &(n, s) in &[(0.5, 0.1), (2.0, 0.7), (0.1, 0.02), (10.0, 5.0)] {
let out = f.eval(1, &XcInput::gga(&[n], &[s])).unwrap();
let hn = 1e-6 * n;
let hs = 1e-6 * s;
let fdn = (edens(n + hn, s) - edens(n - hn, s)) / (2.0 * hn);
let fds = (edens(n, s + hs) - edens(n, s - hs)) / (2.0 * hs);
assert!(
(out.vrho[0] - fdn).abs() <= 1e-6 * out.vrho[0].abs().max(1.0),
"vrho n={n} s={s}: {} vs {fdn}",
out.vrho[0]
);
assert!(
(out.vsigma[0] - fds).abs() <= 1e-6 * out.vsigma[0].abs().max(1.0),
"vsigma n={n} s={s}: {} vs {fds}",
out.vsigma[0]
);
}
}
#[test]
fn pol_derivs_match_finite_difference() {
let f = b88(Spin::Polarized);
let (na, nb, saa, sab, sbb) = (0.6, 0.3, 0.1, 0.05, 0.08);
let r = [na, nb];
let s = [saa, sab, sbb];
let edens = |r: [f64; 2], s: [f64; 3]| {
(r[0] + r[1]) * f.eval(1, &XcInput::gga(&r, &s)).unwrap().exc[0]
};
let out = f.eval(1, &XcInput::gga(&r, &s)).unwrap();
for (k, h) in [(0usize, 1e-6 * na), (1, 1e-6 * nb)] {
let mut rp = r;
let mut rm = r;
rp[k] += h;
rm[k] -= h;
let fd = (edens(rp, s) - edens(rm, s)) / (2.0 * h);
assert!(
(out.vrho[k] - fd).abs() <= 1e-6 * out.vrho[k].abs().max(1.0),
"vrho[{k}]: {} vs {fd}",
out.vrho[k]
);
}
for (k, h) in [(0usize, 1e-6 * saa), (2usize, 1e-6 * sbb)] {
let mut sp = s;
let mut sm = s;
sp[k] += h;
sm[k] -= h;
let fd = (edens(r, sp) - edens(r, sm)) / (2.0 * h);
assert!(
(out.vsigma[k] - fd).abs() <= 1e-6 * out.vsigma[k].abs().max(1.0),
"vsigma[{k}]: {} vs {fd}",
out.vsigma[k]
);
}
assert_eq!(out.vsigma[1], 0.0, "exchange vsigma_ab must be 0");
}
#[test]
fn sigma_zero_recovers_lda_x() {
let pu = b88(Spin::Unpolarized);
let lu = Functional::new(FunctionalId::LdaX, Spin::Unpolarized).unwrap();
for &n in &[0.1, 1.0, 7.3, 100.0] {
let p = pu.eval(1, &XcInput::gga(&[n], &[0.0])).unwrap();
let l = lu.eval(1, &XcInput::lda(&[n])).unwrap();
assert!(
(p.exc[0] - l.exc[0]).abs() <= 1e-10 * l.exc[0].abs(),
"exc n={n}: {} vs {}",
p.exc[0],
l.exc[0]
);
assert!(
(p.vrho[0] - l.vrho[0]).abs() <= 1e-10 * l.vrho[0].abs(),
"vrho n={n}: {} vs {}",
p.vrho[0],
l.vrho[0]
);
}
let pp = b88(Spin::Polarized);
let lp = Functional::new(FunctionalId::LdaX, Spin::Polarized).unwrap();
let p = pp
.eval(1, &XcInput::gga(&[0.6, 0.3], &[0.0, 0.0, 0.0]))
.unwrap();
let l = lp.eval(1, &XcInput::lda(&[0.6, 0.3])).unwrap();
assert!((p.exc[0] - l.exc[0]).abs() <= 1e-10 * l.exc[0].abs());
assert!((p.vrho[0] - l.vrho[0]).abs() <= 1e-10 * l.vrho[0].abs());
assert!((p.vrho[1] - l.vrho[1]).abs() <= 1e-10 * l.vrho[1].abs());
}
#[test]
fn unpol_pol_symmetry_at_zero_polarization() {
let up = b88(Spin::Unpolarized);
let po = b88(Spin::Polarized);
let (n, s) = (0.8, 0.3);
let ou = up.eval(1, &XcInput::gga(&[n], &[s])).unwrap();
let op = po
.eval(
1,
&XcInput::gga(&[n / 2.0, n / 2.0], &[s / 4.0, s / 4.0, s / 4.0]),
)
.unwrap();
assert!((ou.exc[0] - op.exc[0]).abs() <= 1e-12 * ou.exc[0].abs());
assert!((ou.vrho[0] - op.vrho[0]).abs() <= 1e-11 * ou.vrho[0].abs());
assert!((ou.vrho[0] - op.vrho[1]).abs() <= 1e-11 * ou.vrho[0].abs());
}
#[test]
fn edge_outputs_finite() {
let f = b88(Spin::Polarized);
let rho = [
1.0, 0.0, 0.0, 1.0, 1e-12, 1e-13, 1.0, 1.0, 100.0, 50.0, ];
let sigma = [
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1e-20, 0.0, 1e-22, 1e6, 1e6, 1e6, 1.0, 0.5, 0.8, ];
let out = f.eval(5, &XcInput::gga(&rho, &sigma)).unwrap();
for v in out.exc.iter().chain(&out.vrho).chain(&out.vsigma) {
assert!(v.is_finite(), "non-finite output: {v}");
}
}
}