use num_dual::DualNum;
use super::lda_c_pw::{pw92_ec, A_STD, FZ20_STD};
use super::mgga_c_m06_l::b97_g;
use crate::families::gga::{Gga, GgaEnergy, GgaVars};
use crate::families::XcEval;
use crate::func::{Family, FunctionalId, FunctionalInfo, Kind};
use crate::reduced::vars;
pub(crate) const GAMMA_X: f64 = 0.004;
pub(crate) const GAMMA_SS: f64 = 0.2;
pub(crate) const GAMMA_OS: f64 = 0.006;
const B97_3C_C_X: [f64; 3] = [1.076616, -0.469912, 3.322442];
const B97_3C_C_SS: [f64; 3] = [0.543788, -1.444420, 1.637436];
const B97_3C_C_OS: [f64; 3] = [0.635047, 5.532103, -15.301575];
pub(crate) struct B97Series {
info: FunctionalInfo,
zeta_threshold: f64,
c_x: Vec<f64>,
c_ss: Vec<f64>,
c_os: Vec<f64>,
}
impl B97Series {
fn boxed_with(
info: FunctionalInfo,
c_x: &[f64],
c_ss: &[f64],
c_os: &[f64],
) -> Box<dyn XcEval> {
Box::new(Gga(Self {
info,
zeta_threshold: f64::EPSILON, c_x: c_x.to_vec(),
c_ss: c_ss.to_vec(),
c_os: c_os.to_vec(),
}))
}
}
fn series_info(id: Option<FunctionalId>, name: &'static str) -> FunctionalInfo {
FunctionalInfo {
id,
name,
family: Family::Gga,
kind: Kind::ExchangeCorrelation,
needs_sigma: true,
needs_lapl: false,
needs_tau: false,
dens_threshold: 1e-14, hybrid: None,
}
}
pub(crate) fn b97_3c() -> Box<dyn XcEval> {
B97Series::boxed_with(
series_info(Some(FunctionalId::GgaXcB973c), "gga_xc_b97_3c"),
&B97_3C_C_X,
&B97_3C_C_SS,
&B97_3C_C_OS,
)
}
pub(crate) fn b97_series(c_x: &[f64], c_ss: &[f64], c_os: &[f64]) -> Box<dyn XcEval> {
B97Series::boxed_with(series_info(None, "gga_xc_b97_series"), c_x, c_ss, c_os)
}
impl GgaEnergy for B97Series {
fn info(&self) -> &FunctionalInfo {
&self.info
}
fn f<N: DualNum<f64> + Copy>(&self, v: GgaVars<N>) -> N {
let zt = self.zeta_threshold;
let thr = self.info.dens_threshold;
let GgaVars {
rs,
z,
opz,
omz,
na,
nb,
xs0_sq,
xs1_sq,
..
} = v;
let up_screened = na.re() <= thr || opz.re() <= zt;
let dn_screened = nb.re() <= thr || omz.re() <= zt;
let x_up = if up_screened {
N::from(0.0)
} else {
vars::lda_x_spin(rs, opz, zt) * b97_g(GAMMA_X, &self.c_x, xs0_sq)
};
let x_dn = if dn_screened {
N::from(0.0)
} else {
vars::lda_x_spin(rs, omz, zt) * b97_g(GAMMA_X, &self.c_x, xs1_sq)
};
let f_pw = |rs: N, zz: N| pw92_ec(rs, zz, zt, &A_STD, FZ20_STD);
let par_up = if up_screened {
N::from(0.0)
} else {
opz / N::from(2.0) * f_pw(rs * (N::from(2.0) / opz).powf(1.0 / 3.0), N::from(1.0))
};
let par_dn = if dn_screened {
N::from(0.0)
} else {
omz / N::from(2.0) * f_pw(rs * (N::from(2.0) / omz).powf(1.0 / 3.0), N::from(-1.0))
};
let perp = f_pw(rs, z) - par_up - par_dn;
let c_up = if up_screened {
N::from(0.0)
} else {
par_up * b97_g(GAMMA_SS, &self.c_ss, xs0_sq)
};
let c_dn = if dn_screened {
N::from(0.0)
} else {
par_dn * b97_g(GAMMA_SS, &self.c_ss, xs1_sq)
};
let cross = perp * b97_g(GAMMA_OS, &self.c_os, (xs0_sq + xs1_sq) / N::from(2.0));
x_up + x_dn + c_up + c_dn + cross
}
}
#[cfg(test)]
mod tests {
use crate::func::Rung;
use crate::{Functional, FunctionalId, Spin, XcInput};
fn b97_3c(spin: Spin) -> Functional {
Functional::new(FunctionalId::GgaXcB973c, spin).unwrap()
}
#[test]
fn metadata_pure_gga_no_dispersion() {
let f = b97_3c(Spin::Unpolarized);
let info = f.info();
assert_eq!(info.rung(), Rung::Gga);
assert_eq!(f.exx_fraction(), 0.0);
assert!(info.hybrid.is_none());
assert_eq!(info.dispersion(), None);
let g = info.grid();
assert_eq!((g.level, g.grid_sensitive), (3, false));
assert!(info.needs_sigma && !info.needs_tau);
assert_eq!(info.name, "gga_xc_b97_3c");
assert_eq!(FunctionalId::GgaXcB973c.as_u32(), 327);
assert_eq!(
FunctionalId::from_name("b97-3c"),
Some(FunctionalId::GgaXcB973c)
);
}
#[test]
fn unit_series_recovers_lda_x_plus_pw92() {
let one = [1.0_f64];
for &spin in &[Spin::Unpolarized, Spin::Polarized] {
let b97 = Functional::b97_xc(&one, &one, &one, spin);
let ldax = Functional::new(FunctionalId::LdaX, spin).unwrap();
let pw = Functional::new(FunctionalId::LdaCPw, spin).unwrap();
let (rho, sigma): (&[f64], &[f64]) = match spin {
Spin::Unpolarized => (&[0.7], &[0.4]),
Spin::Polarized => (&[0.6, 0.3], &[0.1, 0.05, 0.08]),
};
let got = b97.eval(1, &XcInput::gga(rho, sigma)).unwrap().exc[0];
let want = ldax.eval(1, &XcInput::lda(rho)).unwrap().exc[0]
+ pw.eval(1, &XcInput::lda(rho)).unwrap().exc[0];
assert!(
(got - want).abs() <= 1e-12 * want.abs(),
"{spin:?}: unit B97 series {got} vs lda_x + pw92 {want}"
);
}
}
#[test]
fn sigma_zero_collapses_to_constant_series() {
let f = b97_3c(Spin::Polarized);
let g = Functional::b97_xc(&[1.076616], &[0.543788], &[0.635047], Spin::Polarized);
let rho = [0.6, 0.3];
let sigma = [0.0, 0.0, 0.0];
let a = f.eval(1, &XcInput::gga(&rho, &sigma)).unwrap();
let b = g.eval(1, &XcInput::gga(&rho, &sigma)).unwrap();
assert_eq!(a.exc[0], b.exc[0]);
assert_eq!(a.vrho, b.vrho);
}
#[test]
fn unpol_derivs_match_finite_difference() {
let f = b97_3c(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.3, 0.02), (5.0, 3.0), (0.1, 0.01)] {
let out = f.eval(1, &XcInput::gga(&[n], &[s])).unwrap();
let (hn, hs) = (1e-6 * n, 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-5 * out.vrho[0].abs().max(1.0),
"vrho n={n} s={s}: {} vs {fdn}",
out.vrho[0]
);
assert!(
(out.vsigma[0] - fds).abs() <= 1e-5 * 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 = b97_3c(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-5 * out.vrho[k].abs().max(1.0),
"vrho[{k}]: {} vs {fd}",
out.vrho[k]
);
}
assert_eq!(out.vsigma[1], 0.0);
for (k, h) in [(0usize, 1e-6 * saa), (2usize, 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-5 * out.vsigma[k].abs().max(1.0),
"vsigma[{k}]: {} vs {fd}",
out.vsigma[k]
);
}
}
#[test]
fn unpol_pol_symmetry_at_zero_polarization() {
let up = b97_3c(Spin::Unpolarized);
let po = b97_3c(Spin::Polarized);
let (n, s) = (0.8, 0.3);
let ou = up.eval_fxc(1, &XcInput::gga(&[n], &[s])).unwrap();
let op = po
.eval_fxc(
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-11 * ou.exc[0].abs());
assert!((ou.vrho[0] - op.vrho[0]).abs() <= 1e-10 * ou.vrho[0].abs().max(1.0));
assert!((ou.vrho[0] - op.vrho[1]).abs() <= 1e-10 * ou.vrho[0].abs().max(1.0));
for v in op.v2rho2.iter().chain(&op.v2rhosigma).chain(&op.v2sigma2) {
assert!(v.is_finite());
}
}
#[test]
fn edge_outputs_finite() {
let f = b97_3c(Spin::Polarized);
let rho = [1.0, 0.0, 0.0, 1.0, 1e-10, 1e-11, 1.0, 1.0, 1000.0, 500.0];
let sigma = [
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1e-18, 0.0, 1e-20, 1e6, 1e6, 1e6, 1e6, 5e5, 8e5, ];
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}");
}
}
}