use num_dual::DualNum;
use super::lda_c_vwn::vwn_f_aux;
use super::lda_c_vwn_3::{A_RPA, B_RPA, C_RPA, X0_RPA};
use crate::families::lda::{Lda, LdaEnergy, LdaVars};
use crate::families::XcEval;
use crate::func::{Family, FunctionalId, FunctionalInfo, Kind};
use crate::reduced::vars::f_zeta;
fn vwn_rpa_ec<N: DualNum<f64> + Copy>(rs: N, z: N, zeta_threshold: f64) -> N {
let para = vwn_f_aux(rs, A_RPA[0], B_RPA[0], C_RPA[0], X0_RPA[0]); let ferro = vwn_f_aux(rs, A_RPA[1], B_RPA[1], C_RPA[1], X0_RPA[1]); para + (ferro - para) * f_zeta(z, zeta_threshold)
}
pub(crate) struct LdaCVwnRpa {
info: FunctionalInfo,
zeta_threshold: f64,
}
impl LdaCVwnRpa {
fn new() -> Self {
Self {
info: FunctionalInfo {
id: Some(FunctionalId::LdaCVwnRpa),
name: "lda_c_vwn_rpa",
family: Family::Lda,
kind: Kind::Correlation,
needs_sigma: false,
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(Lda(Self::new()))
}
}
impl LdaEnergy for LdaCVwnRpa {
fn info(&self) -> &FunctionalInfo {
&self.info
}
fn f<N: DualNum<f64> + Copy>(&self, v: LdaVars<N>) -> N {
vwn_rpa_ec(v.rs, v.z, self.zeta_threshold)
}
}
#[cfg(test)]
mod tests {
use super::vwn_rpa_ec;
use super::{A_RPA, B_RPA, C_RPA, X0_RPA};
use crate::functionals::lda_c_vwn::vwn_f_aux;
use crate::reduced::vars::{f_zeta, rs_from_n};
use crate::{Functional, FunctionalId, Spin, XcInput};
#[test]
fn unpol_vrho_matches_finite_difference() {
let f = Functional::new(FunctionalId::LdaCVwnRpa, Spin::Unpolarized).unwrap();
let edens = |x: f64| x * f.eval(1, &XcInput::lda(&[x])).unwrap().exc[0];
for &n in &[0.02, 0.2, 2.0, 50.0] {
let h = 1e-6 * n;
let fd = (edens(n + h) - edens(n - h)) / (2.0 * h);
let v = f.eval(1, &XcInput::lda(&[n])).unwrap().vrho[0];
assert!(
(v - fd).abs() <= 1e-6 * v.abs().max(1.0),
"n={n}: {v} vs fd {fd}"
);
}
}
#[test]
fn pol_vrho_matches_finite_difference() {
let f = Functional::new(FunctionalId::LdaCVwnRpa, Spin::Polarized).unwrap();
let (na, nb) = (0.6, 0.25);
let e = |a: f64, b: f64| (a + b) * f.eval(1, &XcInput::lda(&[a, b])).unwrap().exc[0];
let out = f.eval(1, &XcInput::lda(&[na, nb])).unwrap();
let ha = 1e-6 * na;
let hb = 1e-6 * nb;
let fda = (e(na + ha, nb) - e(na - ha, nb)) / (2.0 * ha);
let fdb = (e(na, nb + hb) - e(na, nb - hb)) / (2.0 * hb);
assert!((out.vrho[0] - fda).abs() <= 1e-6 * out.vrho[0].abs().max(1.0));
assert!((out.vrho[1] - fdb).abs() <= 1e-6 * out.vrho[1].abs().max(1.0));
}
#[test]
fn matches_shared_form_and_differs_from_vwn5_vwn3() {
let zt = f64::EPSILON;
let (na, nb) = (0.6, 0.25);
let rs = rs_from_n(na + nb);
let z = (na - nb) / (na + nb);
let para = vwn_f_aux(rs, A_RPA[0], B_RPA[0], C_RPA[0], X0_RPA[0]);
let ferro = vwn_f_aux(rs, A_RPA[1], B_RPA[1], C_RPA[1], X0_RPA[1]);
let want = para + (ferro - para) * f_zeta(z, zt);
assert!((vwn_rpa_ec(rs, z, zt) - want).abs() <= 1e-15 * want.abs());
let rpa = Functional::new(FunctionalId::LdaCVwnRpa, Spin::Polarized).unwrap();
let got = rpa.eval(1, &XcInput::lda(&[na, nb])).unwrap().exc[0];
for other in [FunctionalId::LdaCVwn, FunctionalId::LdaCVwn3] {
let f = Functional::new(other, Spin::Polarized).unwrap();
let e = f.eval(1, &XcInput::lda(&[na, nb])).unwrap().exc[0];
assert!(
(got - e).abs() > 1e-6 * e.abs(),
"VWN_RPA should differ from {other:?}: {got} vs {e}"
);
}
}
#[test]
fn edge_energy_and_derivatives_finite() {
let f = Functional::new(FunctionalId::LdaCVwnRpa, Spin::Polarized).unwrap();
let rho = [
1.0, 0.0, 0.0, 1.0, 1e-3, 0.0, 1e-12, 1e-13, 100.0, 50.0, ];
let out = f.eval(5, &XcInput::lda(&rho)).unwrap();
for v in out.exc.iter().chain(&out.vrho) {
assert!(v.is_finite(), "non-finite output: {v}");
}
}
}