use std::f64::consts::PI;
use num_dual::DualNum;
use super::lda_c_vwn::{vwn_f_aux, A_VWN, B_VWN, C_VWN, X0_VWN};
use crate::families::lda::{Lda, LdaEnergy, LdaVars};
use crate::families::XcEval;
use crate::func::{Family, FunctionalId, FunctionalInfo, Kind};
use crate::reduced::consts::FPP_VWN;
use crate::reduced::vars::{f_zeta, one_minus_z_pow4};
pub(crate) const A_RPA: [f64; 3] = [0.0310907, 0.01554535, -1.0 / (6.0 * PI * PI)];
pub(crate) const B_RPA: [f64; 3] = [13.0720, 20.1231, 1.06835];
pub(crate) const C_RPA: [f64; 3] = [42.7198, 101.578, 11.4813];
pub(crate) const X0_RPA: [f64; 3] = [-0.409286, -0.743294, -0.228344];
fn vwn3_ec<N: DualNum<f64> + Copy>(rs: N, z: N, zeta_threshold: f64) -> N {
let eps0 = vwn_f_aux(rs, A_VWN[0], B_VWN[0], C_VWN[0], X0_VWN[0]); let dmc = vwn_f_aux(rs, A_VWN[1], B_VWN[1], C_VWN[1], X0_VWN[1]) - eps0; let drpa = vwn_f_aux(rs, A_RPA[1], B_RPA[1], C_RPA[1], X0_RPA[1])
- vwn_f_aux(rs, A_RPA[0], B_RPA[0], C_RPA[0], X0_RPA[0]); let alpha = vwn_f_aux(rs, A_RPA[2], B_RPA[2], C_RPA[2], X0_RPA[2]); let fz = f_zeta(z, zeta_threshold);
let z4 = z.powi(4);
eps0 + (dmc / drpa) * alpha * fz * one_minus_z_pow4(z) / N::from(FPP_VWN) + dmc * fz * z4
}
pub(crate) struct LdaCVwn3 {
info: FunctionalInfo,
zeta_threshold: f64,
}
impl LdaCVwn3 {
fn new() -> Self {
Self {
info: FunctionalInfo {
id: Some(FunctionalId::LdaCVwn3),
name: "lda_c_vwn_3",
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 LdaCVwn3 {
fn info(&self) -> &FunctionalInfo {
&self.info
}
fn f<N: DualNum<f64> + Copy>(&self, v: LdaVars<N>) -> N {
vwn3_ec(v.rs, v.z, self.zeta_threshold)
}
}
#[cfg(test)]
mod tests {
use crate::{Functional, FunctionalId, Spin, XcInput};
#[test]
fn unpol_vrho_matches_finite_difference() {
let f = Functional::new(FunctionalId::LdaCVwn3, 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::LdaCVwn3, 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 unpol_pol_symmetry_at_zero_polarization() {
let up = Functional::new(FunctionalId::LdaCVwn3, Spin::Unpolarized).unwrap();
let po = Functional::new(FunctionalId::LdaCVwn3, Spin::Polarized).unwrap();
let n = 0.9;
let ou = up.eval(1, &XcInput::lda(&[n])).unwrap();
let op = po.eval(1, &XcInput::lda(&[n / 2.0, n / 2.0])).unwrap();
assert!((ou.exc[0] - op.exc[0]).abs() <= 1e-13 * ou.exc[0].abs());
assert!((ou.vrho[0] - op.vrho[0]).abs() <= 1e-12 * ou.vrho[0].abs());
assert!((ou.vrho[0] - op.vrho[1]).abs() <= 1e-12 * ou.vrho[0].abs());
}
#[test]
fn differs_from_vwn5_only_when_polarized() {
let v3 = Functional::new(FunctionalId::LdaCVwn3, Spin::Polarized).unwrap();
let v5 = Functional::new(FunctionalId::LdaCVwn, Spin::Polarized).unwrap();
let pol = v3.eval(1, &XcInput::lda(&[0.6, 0.25])).unwrap();
let pol5 = v5.eval(1, &XcInput::lda(&[0.6, 0.25])).unwrap();
assert!(
(pol.exc[0] - pol5.exc[0]).abs() > 1e-9,
"VWN3 should differ from VWN5 when polarized"
);
let unp3 = v3.eval(1, &XcInput::lda(&[0.3, 0.3])).unwrap();
let unp5 = v5.eval(1, &XcInput::lda(&[0.3, 0.3])).unwrap();
assert!((unp3.exc[0] - unp5.exc[0]).abs() <= 1e-13 * unp3.exc[0].abs());
}
#[test]
fn edge_energy_and_derivatives_finite() {
let f = Functional::new(FunctionalId::LdaCVwn3, 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}");
}
}
}