use num_dual::DualNum;
use super::gga_x_pbe::{pbe_enhancement, MU_X2S2};
use crate::families::gga::{gga_exchange, Gga, GgaEnergy, GgaVars};
use crate::families::XcEval;
use crate::func::{Family, FunctionalId, FunctionalInfo, Kind};
const KAPPA_R: f64 = 1.245;
pub(crate) struct GgaXPbeR {
info: FunctionalInfo,
zeta_threshold: f64,
}
impl GgaXPbeR {
fn new() -> Self {
Self {
info: FunctionalInfo {
id: Some(FunctionalId::GgaXPbeR),
name: "gga_x_pbe_r",
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 GgaXPbeR {
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, |t| {
pbe_enhancement(t, KAPPA_R, MU_X2S2)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::functionals::gga_x_pbe::{KAPPA, MU};
use crate::reduced::consts::X2S;
use crate::{Functional, FunctionalId, Spin, XcInput};
fn revpbe(spin: Spin) -> Functional {
Functional::new(FunctionalId::GgaXPbeR, spin).unwrap()
}
#[test]
fn kappa_804_recovers_pbe_x() {
for &t in &[0.0_f64, 1e-8, 0.01, 0.5, 3.0, 100.0, 1e4] {
let s2 = X2S * X2S * t;
let want = 1.0 + KAPPA * MU * s2 / (KAPPA + MU * s2);
let got = pbe_enhancement(t, KAPPA, MU_X2S2);
assert!(
(got - want).abs() <= 1e-13 * want.abs().max(1.0),
"pbe_enhancement({t}, κ=0.804) = {got} vs PBE-x pbe_f0 {want}"
);
let got_r = pbe_enhancement(t, KAPPA_R, MU_X2S2);
assert!(got_r >= want, "revPBE F_x must be ≥ PBE-x at t={t}");
}
let t = 1.0_f64;
assert!(
pbe_enhancement(t, KAPPA_R, MU_X2S2) > pbe_enhancement(t, KAPPA, MU_X2S2),
"revPBE F_x must strictly exceed PBE-x at t=1"
);
}
#[test]
fn unpol_vrho_vsigma_match_finite_difference() {
let f = revpbe(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));
assert!((out.vsigma[0] - fds).abs() <= 1e-6 * out.vsigma[0].abs().max(1.0));
}
}
#[test]
fn sigma_zero_recovers_lda_x() {
let pu = revpbe(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());
assert!((p.vrho[0] - l.vrho[0]).abs() <= 1e-10 * l.vrho[0].abs());
}
}
#[test]
fn pol_no_sigma_ab_dependence() {
let f = revpbe(Spin::Polarized);
let out = f
.eval(1, &XcInput::gga(&[0.6, 0.3], &[0.1, 0.05, 0.08]))
.unwrap();
assert_eq!(out.vsigma[1], 0.0, "exchange vsigma_ab must be 0");
}
}