1use super::basis::{BasisSet, ShellType};
8use nalgebra::DMatrix;
9
10pub fn compute_nuclear_matrix(
12 basis: &BasisSet,
13 elements: &[u8],
14 positions_bohr: &[[f64; 3]],
15) -> DMatrix<f64> {
16 let n = basis.n_basis();
17 let mut v_mat = DMatrix::zeros(n, n);
18 let mut mu = 0;
19
20 for shell_i in &basis.shells {
21 let ni = shell_i.n_functions();
22 let mut nu = 0;
23
24 for shell_j in &basis.shells {
25 let nj = shell_j.n_functions();
26 let block = nuclear_shell_pair(shell_i, shell_j, elements, positions_bohr);
27
28 for i in 0..ni {
29 for j in 0..nj {
30 v_mat[(mu + i, nu + j)] = block[i * nj + j];
31 }
32 }
33 nu += nj;
34 }
35 mu += ni;
36 }
37 v_mat
38}
39
40fn nuclear_shell_pair(
41 a: &super::basis::Shell,
42 b: &super::basis::Shell,
43 elements: &[u8],
44 atoms_bohr: &[[f64; 3]],
45) -> Vec<f64> {
46 let na = a.n_functions();
47 let nb = b.n_functions();
48 let mut result = vec![0.0; na * nb];
49 let ab2 = dist_sq(&a.center, &b.center);
50
51 for (&ea, &ca) in a.exponents.iter().zip(&a.coefficients) {
52 for (&eb, &cb) in b.exponents.iter().zip(&b.coefficients) {
53 let gamma = ea + eb;
54 let p = [
55 (ea * a.center[0] + eb * b.center[0]) / gamma,
56 (ea * a.center[1] + eb * b.center[1]) / gamma,
57 (ea * a.center[2] + eb * b.center[2]) / gamma,
58 ];
59
60 let exp_ab = (-ea * eb / gamma * ab2).exp();
61
62 for (atom_idx, &z) in elements.iter().enumerate() {
63 let c = &atoms_bohr[atom_idx];
64 let pc2 = dist_sq(&p, c);
65 let t = gamma * pc2;
66 let f0 = boys_function(0, t);
67
68 let prefactor = -2.0 * std::f64::consts::PI / gamma * exp_ab * z as f64 * ca * cb;
69
70 match (a.shell_type, b.shell_type) {
71 (ShellType::S, ShellType::S) => {
72 result[0] += prefactor * f0;
73 }
74 (ShellType::S, ShellType::P) => {
75 let f1 = boys_function(1, t);
76 for d in 0..3 {
77 let pb = p[d] - b.center[d];
78 let pc = p[d] - c[d];
79 result[d] += prefactor * (pb * f0 - pc * f1);
80 }
81 }
82 (ShellType::P, ShellType::S) => {
83 let f1 = boys_function(1, t);
84 for d in 0..3 {
85 let pa = p[d] - a.center[d];
86 let pc = p[d] - c[d];
87 result[d * nb] += prefactor * (pa * f0 - pc * f1);
88 }
89 }
90 (ShellType::P, ShellType::P) => {
91 let f1 = boys_function(1, t);
92 let f2 = boys_function(2, t);
93 for i in 0..3 {
94 for j in 0..3 {
95 let pa = p[i] - a.center[i];
96 let pb = p[j] - b.center[j];
97 let pc_i = p[i] - c[i];
98 let pc_j = p[j] - c[j];
99 let mut val = pa * pb * f0 - pa * pc_j * f1 - pb * pc_i * f1
100 + pc_i * pc_j * f2;
101 if i == j {
102 val += (f0 - f1) / (2.0 * gamma);
103 }
104 result[i * nb + j] += prefactor * val;
105 }
106 }
107 }
108 }
109 }
110 }
111 }
112 result
113}
114
115pub fn boys_function(n: usize, t: f64) -> f64 {
119 if t < 1e-15 {
120 return 1.0 / (2.0 * n as f64 + 1.0);
121 }
122 if t > 30.0 {
123 let _nf = n as f64;
125 return double_factorial(2 * n) as f64 / 2.0f64.powi(n as i32 + 1)
126 * (std::f64::consts::PI / t.powi(2 * n as i32 + 1)).sqrt();
127 }
128
129 let mut sum = 0.0;
131 let mut term = 1.0 / (2 * n + 1) as f64;
132 sum += term;
133 for k in 1..100 {
134 term *= t / (n as f64 + k as f64 + 0.5);
135 sum += term;
136 if term.abs() < 1e-15 * sum.abs() {
137 break;
138 }
139 }
140 sum * (-t).exp()
141}
142
143fn double_factorial(n: usize) -> u64 {
144 if n <= 1 {
145 return 1;
146 }
147 let mut result = 1u64;
148 let mut k = n;
149 while k > 1 {
150 result *= k as u64;
151 k -= 2;
152 }
153 result
154}
155
156#[inline]
157fn dist_sq(a: &[f64; 3], b: &[f64; 3]) -> f64 {
158 let dx = a[0] - b[0];
159 let dy = a[1] - b[1];
160 let dz = a[2] - b[2];
161 dx * dx + dy * dy + dz * dz
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn test_boys_f0_zero() {
170 let f = boys_function(0, 0.0);
171 assert!((f - 1.0).abs() < 1e-10, "F_0(0) should be 1.0, got {f}");
172 }
173
174 #[test]
175 fn test_boys_f0_large() {
176 let f = boys_function(0, 50.0);
177 let expected = (std::f64::consts::PI / 50.0).sqrt() * 0.5;
178 assert!(
179 (f - expected).abs() < 0.01 * expected,
180 "F_0(50) = {f}, expected ≈ {expected}"
181 );
182 }
183}