use std::f64::consts::PI;
use num_dual::DualNum;
use crate::families::gga::{Gga, GgaEnergy, GgaVars};
use crate::families::XcEval;
use crate::func::{Family, FunctionalId, FunctionalInfo, Kind};
use crate::reduced::consts::RS_FACTOR;
use crate::reduced::vars::{one_minus_z_pow2, opz_pow};
const A: f64 = 0.04918;
const B: f64 = 0.132;
const C: f64 = 0.2533;
const D: f64 = 0.349;
#[allow(clippy::too_many_arguments)]
fn lyp_energy<N: DualNum<f64> + Copy>(rr: N, z: N, xt2: N, xs0_2: N, xs1_2: N, zt: f64) -> N {
let cf = 0.3 * (3.0 * PI * PI).powf(2.0 / 3.0); let aux6 = 2.0_f64.powf(-8.0 / 3.0); let aux4 = aux6 / 4.0;
let aux5 = aux4 / 18.0;
let opz = N::from(1.0) + z; let omz = N::from(1.0) - z; let opz83 = opz_pow(opz, 8.0 / 3.0, zt);
let omz83 = opz_pow(omz, 8.0 / 3.0, zt);
let opz113 = opz_pow(opz, 11.0 / 3.0, zt);
let omz113 = opz_pow(omz, 11.0 / 3.0, zt);
let opz2 = opz_pow(opz, 2.0, zt);
let omz2 = opz_pow(omz, 2.0, zt);
let omz2f = one_minus_z_pow2(z);
let one_p_drr = N::from(1.0) + N::from(D) * rr; let omega = N::from(B) * (-N::from(C) * rr).exp() / one_p_drr;
let delta = (N::from(C) + N::from(D) / one_p_drr) * rr;
let t1 = -omz2f / one_p_drr;
let t2 = -xt2
* (omz2f * (N::from(47.0) - N::from(7.0) * delta) / N::from(72.0) - N::from(2.0 / 3.0));
let t3 = N::from(-0.5 * cf) * omz2f * (opz83 + omz83);
let t4 = N::from(aux4)
* omz2f
* (N::from(2.5) - delta / N::from(18.0))
* (xs0_2 * opz83 + xs1_2 * omz83);
let t5 = N::from(aux5) * omz2f * (delta - N::from(11.0)) * (xs0_2 * opz113 + xs1_2 * omz113);
let t6 = N::from(-aux6)
* (N::from(2.0 / 3.0) * (xs0_2 * opz83 + xs1_2 * omz83)
- opz2 * xs1_2 * omz83 / N::from(4.0)
- omz2 * xs0_2 * opz83 / N::from(4.0));
N::from(A) * (t1 + omega * (t2 + t3 + t4 + t5 + t6))
}
pub(crate) struct GgaCLyp {
info: FunctionalInfo,
zeta_threshold: f64,
}
impl GgaCLyp {
fn new() -> Self {
Self {
info: FunctionalInfo {
id: Some(FunctionalId::GgaCLyp),
name: "gga_c_lyp",
family: Family::Gga,
kind: Kind::Correlation,
needs_sigma: true,
needs_lapl: false,
needs_tau: false,
dens_threshold: 1e-14, hybrid: None,
},
zeta_threshold: f64::EPSILON, }
}
pub(crate) fn boxed() -> Box<dyn XcEval> {
Box::new(Gga(Self::new()))
}
}
impl GgaEnergy for GgaCLyp {
fn info(&self) -> &FunctionalInfo {
&self.info
}
fn f<N: DualNum<f64> + Copy>(&self, v: GgaVars<N>) -> N {
let rr = v.rs / N::from(RS_FACTOR);
lyp_energy(rr, v.z, v.xt2, v.xs0_sq, v.xs1_sq, self.zeta_threshold)
}
}
#[cfg(test)]
mod tests {
use super::{A, B, C, D};
use crate::{Functional, FunctionalId, Spin, XcInput};
use std::f64::consts::PI;
fn lyp(spin: Spin) -> Functional {
Functional::new(FunctionalId::GgaCLyp, spin).unwrap()
}
#[test]
fn unpol_vrho_vsigma_match_finite_difference() {
let f = lyp(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),
"vrho n={n} s={s}: {} vs {fdn}",
out.vrho[0]
);
assert!(
(out.vsigma[0] - fds).abs() <= 1e-6 * out.vsigma[0].abs().max(1.0),
"vsigma n={n} s={s}: {} vs {fds}",
out.vsigma[0]
);
}
}
#[test]
fn pol_derivs_match_finite_difference() {
let f = lyp(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 * na), (1, 1e-6 * nb)] {
let mut rp = r;
let mut rm = r;
rp[k] += h;
rm[k] -= h;
let fd = (edens(rp, s) - edens(rm, s)) / (2.0 * h);
assert!(
(out.vrho[k] - fd).abs() <= 1e-6 * out.vrho[k].abs().max(1.0),
"vrho[{k}]: {} vs {fd}",
out.vrho[k]
);
}
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]
);
assert!(out.vsigma[k].abs() > 0.0, "vsigma[{k}] unexpectedly zero");
}
}
#[test]
fn sigma_zero_recovers_lyp_gradient_free_limit() {
let f = lyp(Spin::Unpolarized);
let cf = 0.3 * (3.0 * PI * PI).powf(2.0 / 3.0);
for &n in &[0.1, 1.0, 7.3, 100.0] {
let got = f.eval(1, &XcInput::gga(&[n], &[0.0])).unwrap().exc[0];
let rr = n.powf(-1.0 / 3.0);
let one_p_drr = 1.0 + D * rr;
let omega = B * (-C * rr).exp() / one_p_drr;
let want = A * (-1.0 / one_p_drr - omega * cf);
assert!(
(got - want).abs() <= 1e-12 * want.abs().max(1e-300),
"n={n}: LYP(σ=0) {got} vs gradient-free limit {want}"
);
}
}
#[test]
fn unpol_pol_symmetry_at_zero_polarization() {
let up = lyp(Spin::Unpolarized);
let po = lyp(Spin::Polarized);
let (n, s) = (0.8, 0.3);
let ou = up.eval(1, &XcInput::gga(&[n], &[s])).unwrap();
let op = po
.eval(
1,
&XcInput::gga(&[n / 2.0, n / 2.0], &[s / 4.0, s / 4.0, s / 4.0]),
)
.unwrap();
assert!((ou.exc[0] - op.exc[0]).abs() <= 1e-12 * ou.exc[0].abs());
assert!((ou.vrho[0] - op.vrho[0]).abs() <= 1e-11 * ou.vrho[0].abs());
assert!((ou.vrho[0] - op.vrho[1]).abs() <= 1e-11 * ou.vrho[0].abs());
}
#[test]
fn edge_derivatives_finite() {
let f = lyp(Spin::Polarized);
let rho = [
1.0, 0.0, 0.0, 1.0, 1e-13, 1e-14, 1.0, 1.0, 100.0, 50.0, ];
let sigma = [
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1e6, 0.0, 1e6, 1e6, 1e6, 1e6, 1.0, 0.5, 0.8, ];
let out = f.eval(5, &XcInput::gga(&rho, &sigma)).unwrap();
for v in out.exc.iter().chain(&out.vrho).chain(&out.vsigma) {
assert!(v.is_finite(), "non-finite output: {v}");
}
}
}