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::{f_zeta, opz_pow};
const MALPHA: f64 = 0.023266;
const MBETA: f64 = 7.389e-6;
const MGAMMA: f64 = 8.723;
const MDELTA: f64 = 0.472;
const AA: f64 = 0.001667;
const BB: f64 = 0.002568;
const FTILDE: f64 = 1.745 * 0.11;
const PZ_GAMMA: [f64; 2] = [-0.1423, -0.0843];
const PZ_BETA1: [f64; 2] = [1.0529, 1.3981];
const PZ_BETA2: [f64; 2] = [0.3334, 0.2611];
const PZ_A: [f64; 2] = [0.0311, 0.01555];
const PZ_B: [f64; 2] = [-0.048, -0.0269];
const PZ_C: [f64; 2] = [0.0020, 0.0007];
const PZ_D: [f64; 2] = [-0.0116, -0.0048];
fn pz_ec<N: DualNum<f64> + Copy>(i: usize, rs: N) -> N {
if rs.re() >= 1.0 {
N::from(PZ_GAMMA[i])
/ (N::from(1.0) + rs.sqrt() * N::from(PZ_BETA1[i]) + rs * N::from(PZ_BETA2[i]))
} else {
rs.ln() * N::from(PZ_A[i])
+ N::from(PZ_B[i])
+ rs * rs.ln() * N::from(PZ_C[i])
+ rs * N::from(PZ_D[i])
}
}
pub(crate) fn f_pz<N: DualNum<f64> + Copy>(rs: N, z: N, zeta_threshold: f64) -> N {
let ec1 = pz_ec(0, rs);
let ec2 = pz_ec(1, rs);
ec1 + (ec2 - ec1) * f_zeta(z, zeta_threshold)
}
fn p86_cc<N: DualNum<f64> + Copy>(rs: N) -> N {
N::from(AA)
+ (N::from(BB) + rs * N::from(MALPHA) + rs * rs * N::from(MBETA))
/ (N::from(1.0)
+ rs * N::from(MGAMMA)
+ rs * rs * N::from(MDELTA)
+ rs * rs * rs * N::from(1.0e4 * MBETA))
}
pub(crate) struct GgaCP86 {
info: FunctionalInfo,
zeta_threshold: f64,
}
impl GgaCP86 {
fn new() -> Self {
Self {
info: FunctionalInfo {
id: Some(FunctionalId::GgaCP86),
name: "gga_c_p86",
family: Family::Gga,
kind: Kind::Correlation,
needs_sigma: true,
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(Gga(Self::new()))
}
}
impl GgaEnergy for GgaCP86 {
fn info(&self) -> &FunctionalInfo {
&self.info
}
fn f<N: DualNum<f64> + Copy>(&self, v: GgaVars<N>) -> N {
let zt = self.zeta_threshold;
let opz53 = opz_pow(N::from(1.0) + v.z, 5.0 / 3.0, zt);
let omz53 = opz_pow(N::from(1.0) - v.z, 5.0 / 3.0, zt);
let dd = ((opz53 + omz53) / N::from(2.0)).sqrt();
let cc = p86_cc(v.rs);
let rr = v.rs / N::from(RS_FACTOR); let x1_sq = v.xt2 / rr;
let h = if x1_sq.re() > 0.0 {
let x1 = x1_sq.sqrt();
let mphi = x1 * N::from(FTILDE * (AA + BB)) / cc;
x1_sq * (-mphi).exp() * cc / dd
} else {
x1_sq * cc / dd
};
f_pz(v.rs, v.z, zt) + h
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Functional, FunctionalId, Spin, XcInput};
fn p86(spin: Spin) -> Functional {
Functional::new(FunctionalId::GgaCP86, spin).unwrap()
}
#[test]
fn unpol_vrho_vsigma_match_finite_difference() {
let f = p86(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),
(0.01, 1e-4),
] {
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 = p86(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, mut rm) = (r, 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, mut sm) = (s, 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");
}
assert!(
(out.vsigma[1] - 2.0 * out.vsigma[0]).abs() <= 1e-12 * out.vsigma[1].abs(),
"vsigma_ab must be 2·vsigma_aa: {} vs {}",
out.vsigma[1],
out.vsigma[0]
);
}
#[test]
fn sigma_zero_recovers_pz81() {
let f = p86(Spin::Unpolarized);
for &n in &[0.01, 0.1, 1.0, 7.3, 100.0] {
let got = f.eval(1, &XcInput::gga(&[n], &[0.0])).unwrap().exc[0];
let rs = crate::reduced::consts::RS_FACTOR / n.powf(1.0 / 3.0);
let want = if rs >= 1.0 {
PZ_GAMMA[0] / (1.0 + PZ_BETA1[0] * rs.sqrt() + PZ_BETA2[0] * rs)
} else {
PZ_A[0] * rs.ln() + PZ_B[0] + PZ_C[0] * rs * rs.ln() + PZ_D[0] * rs
};
assert!(
(got - want).abs() <= 1e-12 * want.abs(),
"n={n}: P86(σ=0) {got} vs PZ81 {want}"
);
}
}
#[test]
fn unpol_pol_symmetry_at_zero_polarization() {
let up = p86(Spin::Unpolarized);
let po = p86(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 = p86(Spin::Polarized);
let rho = [
1.0, 0.0, 0.0, 1.0, 1e-13, 1e-14, 0.5, 0.5, 100.0, 50.0, ];
let sigma = [
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1e6, 0.0, 1e6, 0.1, -10.0, 0.1, 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}");
}
}
}