use nalgebra::DMatrix;
pub fn lowdin_orthogonalization(s: &DMatrix<f64>, threshold: f64) -> (DMatrix<f64>, usize) {
let n = s.nrows();
let eigen = s.clone().symmetric_eigen();
let n_independent = eigen.eigenvalues.iter().filter(|&&e| e > threshold).count();
let mut x = DMatrix::zeros(n, n);
for k in 0..n {
let eigenval = eigen.eigenvalues[k];
if eigenval > threshold {
let inv_sqrt = 1.0 / eigenval.sqrt();
for i in 0..n {
for j in 0..n {
x[(i, j)] += inv_sqrt * eigen.eigenvectors[(i, k)] * eigen.eigenvectors[(j, k)];
}
}
}
}
(x, n_independent)
}
pub fn transform_to_orthogonal(m: &DMatrix<f64>, x: &DMatrix<f64>) -> DMatrix<f64> {
x.transpose() * m * x
}
pub fn back_transform(c_prime: &DMatrix<f64>, x: &DMatrix<f64>) -> DMatrix<f64> {
x * c_prime
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lowdin_identity() {
let s = DMatrix::identity(3, 3);
let (x, n_indep) = lowdin_orthogonalization(&s, 1e-10);
assert_eq!(n_indep, 3);
for i in 0..3 {
for j in 0..3 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!((x[(i, j)] - expected).abs() < 1e-10);
}
}
}
#[test]
fn test_lowdin_produces_orthonormal_basis() {
let mut s = DMatrix::identity(2, 2);
s[(0, 1)] = 0.5;
s[(1, 0)] = 0.5;
let (x, _) = lowdin_orthogonalization(&s, 1e-10);
let identity_check = x.transpose() * &s * &x;
for i in 0..2 {
for j in 0..2 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(identity_check[(i, j)] - expected).abs() < 1e-10,
"X†SX[{},{}] = {}, expected {}",
i,
j,
identity_check[(i, j)],
expected
);
}
}
}
#[test]
fn test_lowdin_singular_overlap() {
let mut s = DMatrix::identity(3, 3);
s[(0, 1)] = 0.99;
s[(1, 0)] = 0.99;
s[(0, 2)] = 0.5;
s[(2, 0)] = 0.5;
s[(1, 2)] = 0.5;
s[(2, 1)] = 0.5;
let (x, n_indep) = lowdin_orthogonalization(&s, 1e-6);
assert!(n_indep <= 3);
assert!(n_indep >= 2);
let check = x.transpose() * &s * &x;
for i in 0..check.nrows() {
assert!(
(check[(i, i)] - 1.0).abs() < 1e-8 || check[(i, i)].abs() < 1e-8,
"Diagonal element [{},{}] = {} unexpected",
i,
i,
check[(i, i)]
);
}
}
#[test]
fn test_transform_and_back_transform() {
let m = DMatrix::from_row_slice(2, 2, &[1.0, 0.3, 0.3, 2.0]);
let x = DMatrix::from_row_slice(2, 2, &[0.8, 0.2, 0.2, 0.9]);
let m_prime = transform_to_orthogonal(&m, &x);
let c_prime = DMatrix::identity(2, 2);
let c = back_transform(&c_prime, &x);
assert!((c - &x).norm() < 1e-14);
let expected = x.transpose() * &m * &x;
assert!((m_prime - expected).norm() < 1e-14);
}
}