use num_dual::DualNum;
use super::gga_x_pbe::{KAPPA, MU_X2S2};
use crate::families::gga::{gga_exchange, Gga, GgaEnergy, GgaVars};
use crate::families::XcEval;
use crate::func::{Family, FunctionalId, FunctionalInfo, Kind};
fn rpbe_enhancement<N: DualNum<f64> + Copy>(t: N) -> N {
let arg = N::from(-MU_X2S2 / KAPPA) * t;
N::from(1.0) - N::from(KAPPA) * arg.exp_m1()
}
pub(crate) struct GgaXRpbe {
info: FunctionalInfo,
zeta_threshold: f64,
}
impl GgaXRpbe {
fn new() -> Self {
Self {
info: FunctionalInfo {
id: Some(FunctionalId::GgaXRpbe),
name: "gga_x_rpbe",
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 GgaXRpbe {
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,
rpbe_enhancement,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::functionals::gga_x_pbe::MU;
use crate::reduced::consts::X2S;
use crate::{Functional, FunctionalId, Spin, XcInput};
fn rpbe(spin: Spin) -> Functional {
Functional::new(FunctionalId::GgaXRpbe, spin).unwrap()
}
#[test]
fn enhancement_matches_closed_form() {
let kappa = KAPPA;
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 * (1.0 - (-MU * s2 / kappa).exp());
let got: f64 = rpbe_enhancement(t);
assert!(
(got - want).abs() <= 1e-12 * want.abs().max(1.0),
"rpbe_enhancement({t}) = {got} vs closed form {want}"
);
}
assert_eq!(rpbe_enhancement(0.0_f64), 1.0, "F(0) must be exactly 1");
}
#[test]
fn unpol_vrho_vsigma_match_finite_difference() {
let f = rpbe(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 = rpbe(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 fxc_finite_at_sigma_zero() {
let f = rpbe(Spin::Unpolarized);
for &n in &[0.1, 1.0, 50.0] {
let r = f.eval_fxc(1, &XcInput::gga(&[n], &[0.0])).unwrap();
assert!(r.v2rho2[0].is_finite() && r.v2sigma2[0].is_finite());
assert!(r.v2rhosigma[0].is_finite());
}
}
#[test]
fn pol_no_sigma_ab_dependence() {
let f = rpbe(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");
}
}