use super::super::gaussian_integrals::{boys_fn, GaussianBasis};
use std::f64::consts::PI;
#[inline]
pub fn boys_function_n0(x: f64) -> f64 {
boys_fn(0, x)
}
pub fn compute_eri_ssss(
alpha: f64,
a: [f64; 3],
beta: f64,
b: [f64; 3],
gamma: f64,
c: [f64; 3],
delta: f64,
d: [f64; 3],
) -> f64 {
let zeta = alpha + beta; let eta = gamma + delta;
let p = [
(alpha * a[0] + beta * b[0]) / zeta,
(alpha * a[1] + beta * b[1]) / zeta,
(alpha * a[2] + beta * b[2]) / zeta,
];
let q = [
(gamma * c[0] + delta * d[0]) / eta,
(gamma * c[1] + delta * d[1]) / eta,
(gamma * c[2] + delta * d[2]) / eta,
];
let rho = zeta * eta / (zeta + eta);
let pq2: f64 = (0..3).map(|i| (p[i] - q[i]).powi(2)).sum();
let t = rho * pq2;
let ab2: f64 = (0..3).map(|i| (a[i] - b[i]).powi(2)).sum();
let cd2: f64 = (0..3).map(|i| (c[i] - d[i]).powi(2)).sum();
let exp_ab = (-alpha * beta * ab2 / zeta).exp();
let exp_cd = (-gamma * delta * cd2 / eta).exp();
let prefactor = 2.0 * PI.powf(2.5) / (zeta * eta * (zeta + eta).sqrt());
prefactor * boys_function_n0(t) * exp_ab * exp_cd
}
pub fn build_eri_tensor(basis: &[GaussianBasis]) -> Vec<f64> {
let n = basis.len();
let n4 = n * n * n * n;
let mut eri = vec![0.0_f64; n4];
for mu in 0..n {
for nu in 0..mu + 1 {
for lam in 0..n {
for sig in 0..lam + 1 {
let b_mu = &basis[mu];
let b_nu = &basis[nu];
let b_lam = &basis[lam];
let b_sig = &basis[sig];
if b_mu.l() != 0 || b_nu.l() != 0 || b_lam.l() != 0 || b_sig.l() != 0 {
continue;
}
let val = compute_eri_ssss(
b_mu.exponent,
b_mu.center,
b_nu.exponent,
b_nu.center,
b_lam.exponent,
b_lam.center,
b_sig.exponent,
b_sig.center,
) * b_mu.coefficient
* b_nu.coefficient
* b_lam.coefficient
* b_sig.coefficient;
let indices = [
(mu, nu, lam, sig),
(nu, mu, lam, sig),
(mu, nu, sig, lam),
(nu, mu, sig, lam),
(lam, sig, mu, nu),
(sig, lam, mu, nu),
(lam, sig, nu, mu),
(sig, lam, nu, mu),
];
for (i, j, k, l) in indices {
eri[i * n * n * n + j * n * n + k * n + l] = val;
}
}
}
}
}
eri
}
#[inline]
pub fn get_eri(eri: &[f64], n: usize, mu: usize, nu: usize, lam: usize, sig: usize) -> f64 {
eri[mu * n * n * n + nu * n * n + lam * n + sig]
}
pub fn schwarz_screening(
eri: &[f64],
n: usize,
mu: usize,
nu: usize,
lam: usize,
sig: usize,
) -> bool {
let q_munu = get_eri(eri, n, mu, nu, mu, nu);
let q_lamsig = get_eri(eri, n, lam, sig, lam, sig);
let bound = (q_munu.abs() * q_lamsig.abs()).sqrt();
bound >= 1e-12
}
#[cfg(test)]
mod tests {
use super::*;
use crate::specialized::quantum::gaussian_integrals::normalized_s_gto;
const ORIGIN: [f64; 3] = [0.0, 0.0, 0.0];
#[test]
fn test_boys_function_f0_at_zero() {
let f = boys_function_n0(0.0);
assert!((f - 1.0).abs() < 1e-8, "F₀(0) should be 1.0, got {f}");
}
#[test]
fn test_boys_function_continuity() {
let eps = 1e-9;
let f_eps = boys_function_n0(eps);
let f_zero = boys_function_n0(0.0);
assert!(
(f_eps - f_zero).abs() < 1e-5,
"F₀ should be continuous near 0: f(ε)={f_eps}, f(0)={f_zero}"
);
}
#[test]
fn test_eri_ssss_known_value() {
let val = compute_eri_ssss(1.0, ORIGIN, 1.0, ORIGIN, 1.0, ORIGIN, 1.0, ORIGIN);
let expected = 2.0 * PI.powf(2.5) / (2.0 * 2.0 * (4.0_f64).sqrt());
assert!(
(val - expected).abs() < 1e-10,
"ERI (ss|ss): {val}, expected {expected}"
);
}
#[test]
fn test_eri_8fold_symmetry() {
let a = normalized_s_gto(ORIGIN, 1.0);
let b = normalized_s_gto([1.4, 0.0, 0.0], 0.8);
let basis = vec![a, b];
let n = basis.len();
let eri = build_eri_tensor(&basis);
let v0110 = get_eri(&eri, n, 0, 1, 1, 0);
let v1001 = get_eri(&eri, n, 1, 0, 0, 1);
assert!(
(v0110 - v1001).abs() < 1e-12,
"(01|10)={v0110} != (10|01)={v1001}"
);
let v0011 = get_eri(&eri, n, 0, 0, 1, 1);
let v1100 = get_eri(&eri, n, 1, 1, 0, 0);
assert!(
(v0011 - v1100).abs() < 1e-12,
"(00|11)={v0011} != (11|00)={v1100}"
);
}
#[test]
fn test_eri_self_repulsion_positive() {
let a = normalized_s_gto(ORIGIN, 1.0);
let basis = vec![a];
let eri = build_eri_tensor(&basis);
let v = get_eri(&eri, 1, 0, 0, 0, 0);
assert!(
v > 0.0,
"Self-repulsion (00|00) should be positive, got {v}"
);
}
}