use num_dual::DualNum;
use super::gga_c_pbe::pbe_c_energy;
use crate::families::gga::{Gga, GgaEnergy, GgaVars};
use crate::families::XcEval;
use crate::func::{Family, FunctionalId, FunctionalInfo, Kind};
const BETA_SOL: f64 = 0.046;
const _: () = assert!(BETA_SOL < super::gga_c_pbe::BETA);
pub(crate) struct GgaCPbeSol {
info: FunctionalInfo,
zeta_threshold: f64,
}
impl GgaCPbeSol {
fn new() -> Self {
Self {
info: FunctionalInfo {
id: Some(FunctionalId::GgaCPbeSol),
name: "gga_c_pbe_sol",
family: Family::Gga,
kind: Kind::Correlation,
needs_sigma: true,
needs_lapl: false,
needs_tau: false,
dens_threshold: 1e-12, hybrid: None,
},
zeta_threshold: f64::EPSILON, }
}
pub(crate) fn boxed() -> Box<dyn XcEval> {
Box::new(Gga(Self::new()))
}
}
impl GgaEnergy for GgaCPbeSol {
fn info(&self) -> &FunctionalInfo {
&self.info
}
fn f<N: DualNum<f64> + Copy>(&self, v: GgaVars<N>) -> N {
pbe_c_energy(v.rs, v.z, v.xt2, self.zeta_threshold, BETA_SOL)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::functionals::gga_c_pbe::BETA;
use crate::reduced::vars::{reduced_grad_sq, rs_from_n};
use crate::{Functional, FunctionalId, Spin, XcInput};
fn pbesol(spin: Spin) -> Functional {
Functional::new(FunctionalId::GgaCPbeSol, spin).unwrap()
}
#[test]
fn beta_pbe_recovers_gga_c_pbe() {
let pbe = Functional::new(FunctionalId::GgaCPbe, Spin::Unpolarized).unwrap();
let sol = pbesol(Spin::Unpolarized);
let zt = f64::EPSILON;
for &(n, s) in &[(0.5, 0.1), (1.0, 0.3), (2.0, 1.5), (0.3, 0.02)] {
let rs = rs_from_n(n);
let xt2 = reduced_grad_sq(s, n); let got = pbe_c_energy(rs, 0.0_f64, xt2, zt, BETA);
let want = pbe.eval(1, &XcInput::gga(&[n], &[s])).unwrap().exc[0];
assert!(
(got - want).abs() <= 1e-12 * want.abs().max(1.0),
"pbe_c_energy(β=BETA) {got} vs gga_c_pbe exc {want} @ n={n}, σ={s}"
);
let solv = sol.eval(1, &XcInput::gga(&[n], &[s])).unwrap().exc[0];
assert!(
(solv - want).abs() > 1e-9 * want.abs(),
"PBEsol-c unexpectedly equals PBE-c @ n={n}, σ={s}"
);
}
}
#[test]
fn sigma_zero_matches_pbe_c_uniform_limit() {
let sol = pbesol(Spin::Unpolarized);
let pbe = Functional::new(FunctionalId::GgaCPbe, Spin::Unpolarized).unwrap();
for &n in &[0.1, 1.0, 7.3, 100.0] {
let s = sol.eval(1, &XcInput::gga(&[n], &[0.0])).unwrap().exc[0];
let p = pbe.eval(1, &XcInput::gga(&[n], &[0.0])).unwrap().exc[0];
assert!(
(s - p).abs() <= 1e-12 * p.abs(),
"PBEsol-c(σ=0) {s} vs PBE-c(σ=0) {p} @ n={n}"
);
}
}
#[test]
fn unpol_vrho_vsigma_match_finite_difference() {
let f = pbesol(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 pol_derivs_match_finite_difference() {
let f = pbesol(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 * saa), (1, 1e-6 * sab), (2, 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]
);
}
}
}