sci_form/scf/
orthogonalization.rs1use nalgebra::DMatrix;
18
19pub fn lowdin_orthogonalization(s: &DMatrix<f64>, threshold: f64) -> (DMatrix<f64>, usize) {
28 let n = s.nrows();
29 let eigen = s.clone().symmetric_eigen();
30
31 let n_independent = eigen.eigenvalues.iter().filter(|&&e| e > threshold).count();
33
34 let mut x = DMatrix::zeros(n, n);
36
37 for k in 0..n {
38 let eigenval = eigen.eigenvalues[k];
39 if eigenval > threshold {
40 let inv_sqrt = 1.0 / eigenval.sqrt();
41 for i in 0..n {
43 for j in 0..n {
44 x[(i, j)] += inv_sqrt * eigen.eigenvectors[(i, k)] * eigen.eigenvectors[(j, k)];
45 }
46 }
47 }
48 }
49
50 (x, n_independent)
51}
52
53pub fn transform_to_orthogonal(m: &DMatrix<f64>, x: &DMatrix<f64>) -> DMatrix<f64> {
55 x.transpose() * m * x
56}
57
58pub fn back_transform(c_prime: &DMatrix<f64>, x: &DMatrix<f64>) -> DMatrix<f64> {
60 x * c_prime
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn test_lowdin_identity() {
69 let s = DMatrix::identity(3, 3);
71 let (x, n_indep) = lowdin_orthogonalization(&s, 1e-10);
72 assert_eq!(n_indep, 3);
73
74 for i in 0..3 {
75 for j in 0..3 {
76 let expected = if i == j { 1.0 } else { 0.0 };
77 assert!((x[(i, j)] - expected).abs() < 1e-10);
78 }
79 }
80 }
81
82 #[test]
83 fn test_lowdin_produces_orthonormal_basis() {
84 let mut s = DMatrix::identity(2, 2);
86 s[(0, 1)] = 0.5;
87 s[(1, 0)] = 0.5;
88
89 let (x, _) = lowdin_orthogonalization(&s, 1e-10);
90
91 let identity_check = x.transpose() * &s * &x;
93 for i in 0..2 {
94 for j in 0..2 {
95 let expected = if i == j { 1.0 } else { 0.0 };
96 assert!(
97 (identity_check[(i, j)] - expected).abs() < 1e-10,
98 "X†SX[{},{}] = {}, expected {}",
99 i,
100 j,
101 identity_check[(i, j)],
102 expected
103 );
104 }
105 }
106 }
107
108 #[test]
109 fn test_lowdin_singular_overlap() {
110 let mut s = DMatrix::identity(3, 3);
112 s[(0, 1)] = 0.99;
113 s[(1, 0)] = 0.99;
114 s[(0, 2)] = 0.5;
115 s[(2, 0)] = 0.5;
116 s[(1, 2)] = 0.5;
117 s[(2, 1)] = 0.5;
118
119 let (x, n_indep) = lowdin_orthogonalization(&s, 1e-6);
120
121 assert!(n_indep <= 3);
123 assert!(n_indep >= 2);
124
125 let check = x.transpose() * &s * &x;
127 for i in 0..check.nrows() {
128 assert!(
130 (check[(i, i)] - 1.0).abs() < 1e-8 || check[(i, i)].abs() < 1e-8,
131 "Diagonal element [{},{}] = {} unexpected",
132 i,
133 i,
134 check[(i, i)]
135 );
136 }
137 }
138
139 #[test]
140 fn test_transform_and_back_transform() {
141 let m = DMatrix::from_row_slice(2, 2, &[1.0, 0.3, 0.3, 2.0]);
142 let x = DMatrix::from_row_slice(2, 2, &[0.8, 0.2, 0.2, 0.9]);
143
144 let m_prime = transform_to_orthogonal(&m, &x);
145 let c_prime = DMatrix::identity(2, 2);
146 let c = back_transform(&c_prime, &x);
147
148 assert!((c - &x).norm() < 1e-14);
150
151 let expected = x.transpose() * &m * &x;
153 assert!((m_prime - expected).norm() < 1e-14);
154 }
155}