1use nalgebra::DMatrix;
7use std::f64::consts::PI;
8
9use super::basis::{orbital_cartesian_terms, AtomicOrbital};
10
11pub fn build_overlap_matrix(basis: &[AtomicOrbital]) -> DMatrix<f64> {
13 let n = basis.len();
14 let mut s = DMatrix::zeros(n, n);
15
16 for i in 0..n {
17 s[(i, i)] = 1.0; for j in (i + 1)..n {
19 let sij = overlap_integral(&basis[i], &basis[j]);
20 s[(i, j)] = sij;
21 s[(j, i)] = sij;
22 }
23 }
24
25 s
26}
27
28fn overlap_integral(a: &AtomicOrbital, b: &AtomicOrbital) -> f64 {
31 let dx = a.center[0] - b.center[0];
32 let dy = a.center[1] - b.center[1];
33 let dz = a.center[2] - b.center[2];
34 let r2_ab = dx * dx + dy * dy + dz * dz;
35
36 let mut s = 0.0;
37
38 let terms_a = orbital_cartesian_terms(a.l, a.m);
39 let terms_b = orbital_cartesian_terms(b.l, b.m);
40
41 for ga in &a.gaussians {
42 for gb in &b.gaussians {
43 let alpha = ga.alpha;
44 let beta = gb.alpha;
45 let gamma = alpha + beta;
46
47 let px = (alpha * a.center[0] + beta * b.center[0]) / gamma;
48 let py = (alpha * a.center[1] + beta * b.center[1]) / gamma;
49 let pz = (alpha * a.center[2] + beta * b.center[2]) / gamma;
50
51 let pa = [px - a.center[0], py - a.center[1], pz - a.center[2]];
52 let pb = [px - b.center[0], py - b.center[1], pz - b.center[2]];
53 let pre = (-alpha * beta / gamma * r2_ab).exp();
54
55 let mut prim_sum = 0.0;
56 for (coef_a, [lax, lay, laz]) in &terms_a {
57 for (coef_b, [lbx, lby, lbz]) in &terms_b {
58 let ix = overlap_1d(*lax as usize, *lbx as usize, pa[0], pb[0], gamma);
59 let iy = overlap_1d(*lay as usize, *lby as usize, pa[1], pb[1], gamma);
60 let iz = overlap_1d(*laz as usize, *lbz as usize, pa[2], pb[2], gamma);
61
62 let norm_a = cart_norm(alpha, *lax as i32, *lay as i32, *laz as i32);
63 let norm_b = cart_norm(beta, *lbx as i32, *lby as i32, *lbz as i32);
64 prim_sum += coef_a * coef_b * norm_a * norm_b * ix * iy * iz;
65 }
66 }
67
68 s += ga.coeff * gb.coeff * pre * prim_sum;
69 }
70 }
71
72 s
73}
74
75fn odd_double_factorial(n: i32) -> f64 {
76 if n <= 0 {
77 return 1.0;
78 }
79 let mut acc = 1.0;
80 let mut k = n;
81 while k > 0 {
82 acc *= k as f64;
83 k -= 2;
84 }
85 acc
86}
87
88fn cart_norm(alpha: f64, lx: i32, ly: i32, lz: i32) -> f64 {
89 let lsum = lx + ly + lz;
90 let pref = (2.0 * alpha / PI).powf(0.75);
91 let ang = (4.0 * alpha).powf(lsum as f64 / 2.0);
92 let denom = (odd_double_factorial(2 * lx - 1)
93 * odd_double_factorial(2 * ly - 1)
94 * odd_double_factorial(2 * lz - 1))
95 .sqrt();
96 pref * ang / denom
97}
98
99fn overlap_1d(la: usize, lb: usize, pa: f64, pb: f64, gamma: f64) -> f64 {
100 let mut e = vec![vec![0.0f64; lb + 1]; la + 1];
101 e[0][0] = (PI / gamma).sqrt();
102
103 for i in 1..=la {
104 let term1 = pa * e[i - 1][0];
105 let term2 = if i > 1 {
106 (i as f64 - 1.0) * e[i - 2][0] / (2.0 * gamma)
107 } else {
108 0.0
109 };
110 e[i][0] = term1 + term2;
111 }
112
113 for j in 1..=lb {
114 let term1 = pb * e[0][j - 1];
115 let term2 = if j > 1 {
116 (j as f64 - 1.0) * e[0][j - 2] / (2.0 * gamma)
117 } else {
118 0.0
119 };
120 e[0][j] = term1 + term2;
121 }
122
123 for i in 1..=la {
124 for j in 1..=lb {
125 let t1 = pb * e[i][j - 1];
126 let t2 = if j > 1 {
127 (j as f64 - 1.0) * e[i][j - 2] / (2.0 * gamma)
128 } else {
129 0.0
130 };
131 let t3 = (i as f64) * e[i - 1][j - 1] / (2.0 * gamma);
132 e[i][j] = t1 + t2 + t3;
133 }
134 }
135
136 e[la][lb]
137}
138
139#[cfg(test)]
140mod tests {
141 use super::super::basis::build_basis;
142 use super::*;
143
144 #[test]
145 fn test_overlap_diagonal_is_one() {
146 let elements = [8u8, 1, 1];
147 let positions = [[0.0, 0.0, 0.0], [0.757, 0.586, 0.0], [-0.757, 0.586, 0.0]];
148 let basis = build_basis(&elements, &positions);
149 let s = build_overlap_matrix(&basis);
150 for i in 0..s.nrows() {
151 assert!(
152 (s[(i, i)] - 1.0).abs() < 1e-10,
153 "S[{},{}] = {}",
154 i,
155 i,
156 s[(i, i)]
157 );
158 }
159 }
160
161 #[test]
162 fn test_overlap_symmetry() {
163 let elements = [8u8, 1, 1];
164 let positions = [[0.0, 0.0, 0.0], [0.757, 0.586, 0.0], [-0.757, 0.586, 0.0]];
165 let basis = build_basis(&elements, &positions);
166 let s = build_overlap_matrix(&basis);
167 for i in 0..s.nrows() {
168 for j in 0..s.ncols() {
169 assert!(
170 (s[(i, j)] - s[(j, i)]).abs() < 1e-14,
171 "S not symmetric at ({},{})",
172 i,
173 j,
174 );
175 }
176 }
177 }
178
179 #[test]
180 fn test_overlap_off_diagonal_range() {
181 let elements = [8u8, 1, 1];
182 let positions = [[0.0, 0.0, 0.0], [0.757, 0.586, 0.0], [-0.757, 0.586, 0.0]];
183 let basis = build_basis(&elements, &positions);
184 let s = build_overlap_matrix(&basis);
185 for i in 0..s.nrows() {
187 for j in 0..s.ncols() {
188 if i != j {
189 assert!(
190 s[(i, j)].abs() <= 1.0 + 1e-10,
191 "S[{},{}] = {} exceeds 1.0",
192 i,
193 j,
194 s[(i, j)]
195 );
196 }
197 }
198 }
199 }
200
201 #[test]
202 fn test_overlap_h2_ss() {
203 let elements = [1u8, 1];
205 let positions = [[0.0, 0.0, 0.0], [0.74, 0.0, 0.0]];
206 let basis = build_basis(&elements, &positions);
207 let s = build_overlap_matrix(&basis);
208 assert_eq!(s.nrows(), 2);
209 let s12 = s[(0, 1)];
210 assert!(s12 > 0.3 && s12 < 0.9, "S_12 for H2 = {}", s12);
211 }
212
213 #[test]
214 fn test_metal_overlap_matrix_is_finite() {
215 let elements = [26u8, 17, 17];
216 let positions = [[0.0, 0.0, 0.0], [2.2, 0.0, 0.0], [-2.2, 0.0, 0.0]];
217 let basis = build_basis(&elements, &positions);
218 let s = build_overlap_matrix(&basis);
219
220 for i in 0..s.nrows() {
221 for j in 0..s.ncols() {
222 assert!(s[(i, j)].is_finite(), "S[{},{}] is not finite", i, j);
223 }
224 }
225 }
226}