use num_dual::DualNum;
use crate::families::lda::{Lda, LdaEnergy, LdaVars};
use crate::families::XcEval;
use crate::func::{Family, FunctionalId, FunctionalInfo, Kind};
use crate::reduced::vars::f_zeta;
const A_STD: [f64; 3] = [0.031091, 0.015545, 0.016887];
pub(crate) const A_MOD: [f64; 3] = [0.0310907, 0.01554535, 0.0168869];
const ALPHA1: [f64; 3] = [0.21370, 0.20548, 0.11125];
const BETA1: [f64; 3] = [7.5957, 14.1189, 10.357];
const BETA2: [f64; 3] = [3.5876, 6.1977, 3.6231];
const BETA3: [f64; 3] = [1.6382, 3.3662, 0.88026];
const BETA4: [f64; 3] = [0.49294, 0.62517, 0.49671];
const FZ20_STD: f64 = 1.709921;
fn g_pw<N: DualNum<f64> + Copy>(rs: N, k: usize, a: &[f64; 3]) -> N {
let sqrt_rs = rs.sqrt();
let aux = N::from(BETA1[k]) * sqrt_rs
+ N::from(BETA2[k]) * rs
+ N::from(BETA3[k]) * (rs * sqrt_rs)
+ N::from(BETA4[k]) * (rs * rs);
let two_a = N::from(2.0 * a[k]);
let q = (two_a * aux).recip();
-two_a * (N::from(1.0) + N::from(ALPHA1[k]) * rs) * q.ln_1p()
}
pub(crate) fn pw92_ec<N: DualNum<f64> + Copy>(
rs: N,
z: N,
zeta_threshold: f64,
a: &[f64; 3],
fz20: f64,
) -> N {
let g0 = g_pw(rs, 0, a);
let g1 = g_pw(rs, 1, a);
let g2 = g_pw(rs, 2, a);
let fz = f_zeta(z, zeta_threshold);
let z4 = z.powi(4);
let fz20 = N::from(fz20);
g0 + z4 * fz * (g1 - g0 + g2 / fz20) - fz * g2 / fz20
}
pub(crate) struct LdaCPw {
info: FunctionalInfo,
zeta_threshold: f64,
}
impl LdaCPw {
fn new() -> Self {
Self {
info: FunctionalInfo {
id: Some(FunctionalId::LdaCPw),
name: "lda_c_pw",
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 LdaCPw {
fn info(&self) -> &FunctionalInfo {
&self.info
}
fn f<N: DualNum<f64> + Copy>(&self, v: LdaVars<N>) -> N {
pw92_ec(v.rs, v.z, self.zeta_threshold, &A_STD, FZ20_STD)
}
}
#[cfg(test)]
mod tests {
use crate::{Functional, FunctionalId, Spin, XcInput};
#[test]
fn unpol_vrho_matches_finite_difference() {
let f = Functional::new(FunctionalId::LdaCPw, 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::LdaCPw, 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::LdaCPw, Spin::Unpolarized).unwrap();
let po = Functional::new(FunctionalId::LdaCPw, 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 edge_energy_and_derivatives_finite() {
let f = Functional::new(FunctionalId::LdaCPw, 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}");
}
}
}