use crate::error::XcError;
use crate::families::XcEval;
use crate::func::{mixed_eval, Family, FunctionalId, FunctionalInfo, HybridInfo, Kind};
fn hyb_info(id: FunctionalId, name: &'static str, exx_fraction: f64) -> FunctionalInfo {
FunctionalInfo {
id: Some(id),
name,
family: Family::HybGga,
kind: Kind::ExchangeCorrelation,
needs_sigma: true,
needs_lapl: false,
needs_tau: false,
dens_threshold: 1e-15,
hybrid: Some(HybridInfo {
exx_fraction,
cam: None,
vv10: None,
}),
}
}
fn build_mix(
components: &[(f64, FunctionalId)],
info: FunctionalInfo,
) -> Result<Box<dyn XcEval>, XcError> {
let parts = components
.iter()
.map(|&(w, id)| Ok((w, super::build(id)?)))
.collect::<Result<Vec<_>, XcError>>()?;
Ok(mixed_eval(parts, info))
}
pub(crate) fn pbeh() -> Result<Box<dyn XcEval>, XcError> {
build_mix(
&[(0.75, FunctionalId::GgaXPbe), (1.0, FunctionalId::GgaCPbe)],
hyb_info(FunctionalId::HybGgaXcPbeh, "hyb_gga_xc_pbeh", 0.25),
)
}
const B3LYP_A0: f64 = 0.20; const B3LYP_AX: f64 = 0.72; const B3LYP_AC: f64 = 0.81;
fn b3lyp_like(
id: FunctionalId,
name: &'static str,
lda_c: FunctionalId,
) -> Result<Box<dyn XcEval>, XcError> {
build_mix(
&[
(1.0 - B3LYP_A0 - B3LYP_AX, FunctionalId::LdaX),
(B3LYP_AX, FunctionalId::GgaXB88),
(1.0 - B3LYP_AC, lda_c),
(B3LYP_AC, FunctionalId::GgaCLyp),
],
hyb_info(id, name, B3LYP_A0),
)
}
pub(crate) fn b3lyp5() -> Result<Box<dyn XcEval>, XcError> {
b3lyp_like(
FunctionalId::HybGgaXcB3lyp5,
"hyb_gga_xc_b3lyp5",
FunctionalId::LdaCVwn,
)
}
pub(crate) fn b3lyp() -> Result<Box<dyn XcEval>, XcError> {
b3lyp_like(
FunctionalId::HybGgaXcB3lyp,
"hyb_gga_xc_b3lyp",
FunctionalId::LdaCVwnRpa,
)
}
#[cfg(test)]
mod tests {
use crate::{Functional, FunctionalId, Spin, XcInput};
#[test]
fn pbe0_is_semilocal_mix_and_exposes_exx() {
let f = Functional::new(FunctionalId::HybGgaXcPbeh, Spin::Polarized).unwrap();
assert_eq!(f.exx_fraction(), 0.25);
assert_eq!(f.info().name, "hyb_gga_xc_pbeh");
let px = Functional::new(FunctionalId::GgaXPbe, Spin::Polarized).unwrap();
let pc = Functional::new(FunctionalId::GgaCPbe, Spin::Polarized).unwrap();
let r = [0.6, 0.3];
let s = [0.1, 0.05, 0.08];
let h = f.eval(1, &XcInput::gga(&r, &s)).unwrap();
let ex = px.eval(1, &XcInput::gga(&r, &s)).unwrap();
let ec = pc.eval(1, &XcInput::gga(&r, &s)).unwrap();
let close = |a: f64, b: f64| (a - b).abs() <= 1e-14 * a.abs().max(1.0);
assert!(close(h.exc[0], 0.75 * ex.exc[0] + ec.exc[0]));
for k in 0..2 {
assert!(
close(h.vrho[k], 0.75 * ex.vrho[k] + ec.vrho[k]),
"vrho[{k}]"
);
}
for k in 0..3 {
assert!(
close(h.vsigma[k], 0.75 * ex.vsigma[k] + ec.vsigma[k]),
"vsigma[{k}]"
);
}
}
#[test]
fn b3lyp5_is_semilocal_mix_with_vwn5() {
let f = Functional::new(FunctionalId::HybGgaXcB3lyp5, Spin::Polarized).unwrap();
assert_eq!(f.exx_fraction(), 0.20);
assert_eq!(f.info().name, "hyb_gga_xc_b3lyp5");
let comp = |id| Functional::new(id, Spin::Polarized).unwrap();
let lx = comp(FunctionalId::LdaX);
let b88 = comp(FunctionalId::GgaXB88);
let vwn5 = comp(FunctionalId::LdaCVwn);
let lyp = comp(FunctionalId::GgaCLyp);
let r = [0.6, 0.3];
let s = [0.1, 0.05, 0.08];
let inp = XcInput::gga(&r, &s);
let h = f.eval(1, &inp).unwrap();
let (clx, cax, cac, cc) = (1.0 - 0.20 - 0.72, 0.72, 1.0 - 0.81, 0.81);
let want = clx * lx.eval(1, &inp).unwrap().exc[0]
+ cax * b88.eval(1, &inp).unwrap().exc[0]
+ cac * vwn5.eval(1, &inp).unwrap().exc[0]
+ cc * lyp.eval(1, &inp).unwrap().exc[0];
assert!((h.exc[0] - want).abs() <= 1e-13 * want.abs());
}
#[test]
fn b3lyp_uses_vwn_rpa_not_vwn5() {
let f = Functional::new(FunctionalId::HybGgaXcB3lyp, Spin::Polarized).unwrap();
assert_eq!(f.exx_fraction(), 0.20);
assert_eq!(f.info().name, "hyb_gga_xc_b3lyp");
let comp = |id| Functional::new(id, Spin::Polarized).unwrap();
let lx = comp(FunctionalId::LdaX);
let b88 = comp(FunctionalId::GgaXB88);
let vwn_rpa = comp(FunctionalId::LdaCVwnRpa);
let lyp = comp(FunctionalId::GgaCLyp);
let r = [0.7, 0.3];
let s = [0.2, 0.1, 0.05];
let inp = XcInput::gga(&r, &s);
let h = f.eval(1, &inp).unwrap();
let (clx, cax, cac, cc) = (1.0 - 0.20 - 0.72, 0.72, 1.0 - 0.81, 0.81);
let want = clx * lx.eval(1, &inp).unwrap().exc[0]
+ cax * b88.eval(1, &inp).unwrap().exc[0]
+ cac * vwn_rpa.eval(1, &inp).unwrap().exc[0]
+ cc * lyp.eval(1, &inp).unwrap().exc[0];
assert!((h.exc[0] - want).abs() <= 1e-13 * want.abs());
let f5 = Functional::new(FunctionalId::HybGgaXcB3lyp5, Spin::Polarized).unwrap();
let h5 = f5.eval(1, &inp).unwrap();
assert!(
(h.exc[0] - h5.exc[0]).abs() > 1e-9 * h.exc[0].abs(),
"B3LYP (VWN_RPA) must differ from B3LYP5 (VWN5): {} vs {}",
h.exc[0],
h5.exc[0]
);
}
#[test]
fn pbe0_unpol_pol_consistent_at_zero_polarization() {
let up = Functional::new(FunctionalId::HybGgaXcPbeh, Spin::Unpolarized).unwrap();
let po = Functional::new(FunctionalId::HybGgaXcPbeh, Spin::Polarized).unwrap();
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-11 * ou.exc[0].abs());
assert!((ou.vrho[0] - op.vrho[0]).abs() <= 1e-10 * ou.vrho[0].abs().max(1.0));
}
}