1use super::super::shared::zero_heap_algebra::ZeroHeapMatrix;
10
11pub const DIIS_SUBSPACE_SIZE: usize = 8;
13pub const SCF_CONVERGENCE_THRESHOLD: f64 = 1e-8;
14pub const MAX_SCF_ITERATIONS: usize = 100;
15
16#[derive(Debug, Clone, Copy, PartialEq)]
17pub enum ScfFormalism {
18 Restricted, Unrestricted, }
21
22#[derive(Debug)]
23pub enum ScfError {
24 ConvergenceFailed,
25 SingularDiisMatrix,
26 InvalidEigenvalueDecomposition,
27}
28
29pub fn gaussian_elimination<const N: usize>(
33 mut a: ZeroHeapMatrix<f64, N, N>,
34 mut b: [f64; N],
35) -> Result<[f64; N], ScfError> {
36 for i in 0..N {
38 let mut max_row = i;
40 let mut max_val = a.get(i, i).abs();
41 for k in (i + 1)..N {
42 let val = a.get(k, i).abs();
43 if val > max_val {
44 max_val = val;
45 max_row = k;
46 }
47 }
48
49 if max_val < 1e-14 {
50 return Err(ScfError::SingularDiisMatrix);
51 }
52
53 if i != max_row {
55 for j in i..N {
56 let temp = a.get(i, j);
57 a.set(i, j, a.get(max_row, j));
58 a.set(max_row, j, temp);
59 }
60 let temp_b = b[i];
61 b[i] = b[max_row];
62 b[max_row] = temp_b;
63 }
64
65 for k in (i + 1)..N {
67 let factor = a.get(k, i) / a.get(i, i);
68 for j in i..N {
69 let new_val = a.get(k, j) - factor * a.get(i, j);
70 a.set(k, j, new_val);
71 }
72 b[k] -= factor * b[i];
73 }
74 }
75
76 let mut x = [0.0; N];
78 for i in (0..N).rev() {
79 let mut sum = 0.0;
80 for j in (i + 1)..N {
81 sum += a.get(i, j) * x[j];
82 }
83 x[i] = (b[i] - sum) / a.get(i, i);
84 }
85
86 Ok(x)
87}
88
89pub fn jacobi_diagonalization<const N: usize>(
92 matrix: &ZeroHeapMatrix<f64, N, N>,
93) -> Result<([f64; N], ZeroHeapMatrix<f64, N, N>), ScfError> {
94 let mut a = *matrix;
95 let mut v = ZeroHeapMatrix::<f64, N, N>::zeros();
96 for i in 0..N {
97 v.set(i, i, 1.0);
98 }
99
100 let max_sweeps = 50;
101 let eps = 1e-15;
102
103 for _sweep in 0..max_sweeps {
104 let mut max_off_diag: f64 = 0.0;
105
106 for p in 0..N {
107 for q in (p + 1)..N {
108 max_off_diag = f64::max(max_off_diag, a.get(p, q).abs());
109 }
110 }
111
112 if max_off_diag < eps {
113 let mut eigenvalues = [0.0; N];
114 for i in 0..N {
115 eigenvalues[i] = a.get(i, i);
116 }
117 for i in 0..N {
119 for j in (i + 1)..N {
120 if eigenvalues[i] > eigenvalues[j] {
121 let temp_val = eigenvalues[i];
122 eigenvalues[i] = eigenvalues[j];
123 eigenvalues[j] = temp_val;
124
125 for k in 0..N {
126 let temp_v = v.get(k, i);
127 v.set(k, i, v.get(k, j));
128 v.set(k, j, temp_v);
129 }
130 }
131 }
132 }
133 return Ok((eigenvalues, v));
134 }
135
136 for p in 0..N {
137 for q in (p + 1)..N {
138 let apq = a.get(p, q);
139 if apq.abs() > eps {
140 let app = a.get(p, p);
141 let aqq = a.get(q, q);
142 let theta = 0.5 * (2.0 * apq).atan2(aqq - app);
143 let c = theta.cos();
144 let s = theta.sin();
145
146 for i in 0..N {
147 if i != p && i != q {
148 let aip = a.get(i, p);
149 let aiq = a.get(i, q);
150 a.set(i, p, c * aip - s * aiq);
151 a.set(p, i, a.get(i, p));
152
153 a.set(i, q, s * aip + c * aiq);
154 a.set(q, i, a.get(i, q));
155 }
156
157 let vip = v.get(i, p);
158 let viq = v.get(i, q);
159 v.set(i, p, c * vip - s * viq);
160 v.set(i, q, s * vip + c * viq);
161 }
162
163 let a_pp_new = c * c * app - 2.0 * s * c * apq + s * s * aqq;
164 let a_qq_new = s * s * app + 2.0 * s * c * apq + c * c * aqq;
165
166 a.set(p, p, a_pp_new);
167 a.set(q, q, a_qq_new);
168 a.set(p, q, 0.0);
169 a.set(q, p, 0.0);
170 }
171 }
172 }
173 }
174
175 Err(ScfError::InvalidEigenvalueDecomposition)
176}
177
178pub fn transpose<const N: usize>(m: &ZeroHeapMatrix<f64, N, N>) -> ZeroHeapMatrix<f64, N, N> {
180 let mut out = ZeroHeapMatrix::<f64, N, N>::zeros();
181 for i in 0..N {
182 for j in 0..N {
183 out.set(i, j, m.get(j, i));
184 }
185 }
186 out
187}
188
189pub fn orthogonalization_matrix<const N: usize>(
191 s: &ZeroHeapMatrix<f64, N, N>,
192) -> Result<ZeroHeapMatrix<f64, N, N>, ScfError> {
193 let (evals, evecs) = jacobi_diagonalization(s)?;
194
195 let mut d_inv_sqrt = ZeroHeapMatrix::<f64, N, N>::zeros();
197 for i in 0..N {
198 if evals[i] < 1e-12 {
199 d_inv_sqrt.set(i, i, 0.0);
201 } else {
202 d_inv_sqrt.set(i, i, 1.0 / evals[i].sqrt());
203 }
204 }
205
206 let evecs_t = transpose(&evecs);
208 let x = evecs * d_inv_sqrt * evecs_t;
209 Ok(x)
210}
211
212pub fn solve_rhf_scf<const N: usize>(
214 h_core: &ZeroHeapMatrix<f64, N, N>, s: &ZeroHeapMatrix<f64, N, N>, eri: &ZeroHeapMatrix<f64, N, N>, num_electrons: usize, ) -> Result<f64, ScfError> {
219 let x = orthogonalization_matrix(s)?;
220 let x_t = transpose(&x);
221 let mut density = ZeroHeapMatrix::<f64, N, N>::zeros();
222 let mut old_energy = 0.0;
223
224 let mut error_vectors = [ZeroHeapMatrix::<f64, N, N>::zeros(); DIIS_SUBSPACE_SIZE];
226 let mut fock_history = [ZeroHeapMatrix::<f64, N, N>::zeros(); DIIS_SUBSPACE_SIZE];
227 let mut diis_count = 0;
228 let mut diis_index = 0;
229
230 for iter in 0..MAX_SCF_ITERATIONS {
231 let mut fock = *h_core;
233 for mu in 0..N {
234 for nu in 0..N {
235 let mut g = 0.0;
236 for lam in 0..N {
237 for sig in 0..N {
238 let eri_val = eri.get((mu + lam) % N, (nu + sig) % N);
241 g += density.get(lam, sig)
243 * (eri_val - 0.5 * eri.get((mu + sig) % N, (nu + lam) % N));
244 }
245 }
246 fock.set(mu, nu, fock.get(mu, nu) + g);
247 }
248 }
249
250 let fds = fock * density * (*s);
252 let sdf = (*s) * density * fock;
253 let mut err_vec = ZeroHeapMatrix::<f64, N, N>::zeros();
254 for i in 0..N {
255 for j in 0..N {
256 err_vec.set(i, j, fds.get(i, j) - sdf.get(i, j));
257 }
258 }
259
260 let err_ortho = x_t * err_vec * x;
262 let mut max_err: f64 = 0.0;
263 for i in 0..N {
264 for j in 0..N {
265 max_err = max_err.max(err_ortho.get(i, j).abs());
266 }
267 }
268
269 fock_history[diis_index] = fock;
271 error_vectors[diis_index] = err_ortho;
272 if diis_count < DIIS_SUBSPACE_SIZE {
273 diis_count += 1;
274 }
275 diis_index = (diis_index + 1) % DIIS_SUBSPACE_SIZE;
276
277 let mut fock_extrapolated = fock;
278 if diis_count > 1 {
279 let _b_size = diis_count + 1;
281 let mut b_matrix = ZeroHeapMatrix::<
282 f64,
283 { DIIS_SUBSPACE_SIZE + 1 },
284 { DIIS_SUBSPACE_SIZE + 1 },
285 >::zeros();
286 for i in 0..diis_count {
287 for j in 0..diis_count {
288 let mut dot = 0.0;
289 for mu in 0..N {
290 for nu in 0..N {
291 dot += error_vectors[i].get(mu, nu) * error_vectors[j].get(mu, nu);
292 }
293 }
294 b_matrix.set(i, j, dot);
295 }
296 b_matrix.set(i, diis_count, -1.0);
297 b_matrix.set(diis_count, i, -1.0);
298 }
299 b_matrix.set(diis_count, diis_count, 0.0);
300
301 let mut rhs = [0.0; DIIS_SUBSPACE_SIZE + 1];
302 rhs[diis_count] = -1.0;
303
304 if let Ok(c) = gaussian_elimination(b_matrix, rhs) {
306 fock_extrapolated = ZeroHeapMatrix::<f64, N, N>::zeros();
307 for i in 0..diis_count {
308 for mu in 0..N {
309 for nu in 0..N {
310 fock_extrapolated.set(
311 mu,
312 nu,
313 fock_extrapolated.get(mu, nu) + c[i] * fock_history[i].get(mu, nu),
314 );
315 }
316 }
317 }
318 }
319 }
320
321 let f_prime = x_t * fock_extrapolated * x;
323
324 let (_evals, c_prime) = jacobi_diagonalization(&f_prime)?;
326
327 let c = x * c_prime;
329
330 let mut new_density = ZeroHeapMatrix::<f64, N, N>::zeros();
332 let num_occ = num_electrons / 2;
333 for mu in 0..N {
334 for nu in 0..N {
335 let mut sum = 0.0;
336 for a in 0..num_occ {
337 sum += c.get(mu, a) * c.get(nu, a);
338 }
339 new_density.set(mu, nu, 2.0 * sum);
340 }
341 }
342
343 let mut energy = 0.0;
345 for mu in 0..N {
346 for nu in 0..N {
347 energy += 0.5
348 * new_density.get(mu, nu)
349 * (h_core.get(mu, nu) + fock_extrapolated.get(mu, nu));
350 }
351 }
352
353 if iter > 0 && (energy - old_energy).abs() < SCF_CONVERGENCE_THRESHOLD && max_err < 1e-6 {
355 return Ok(energy);
356 }
357 old_energy = energy;
358 density = new_density;
359 }
360
361 Err(ScfError::ConvergenceFailed)
362}
363
364#[derive(Debug, Clone, Copy)]
368pub struct RhfResult<const N: usize> {
369 pub electronic_energy: f64,
371 pub orbital_energies: [f64; N],
374 pub density: ZeroHeapMatrix<f64, N, N>,
376 pub coefficients: ZeroHeapMatrix<f64, N, N>,
378 pub num_occ: usize,
380 pub iterations: usize,
382}
383
384pub fn solve_rhf_scf_4index<const N: usize>(
397 h_core: &ZeroHeapMatrix<f64, N, N>,
398 s: &ZeroHeapMatrix<f64, N, N>,
399 eri: &[[[[f64; N]; N]; N]; N],
400 num_electrons: usize,
401) -> Result<RhfResult<N>, ScfError> {
402 let x = orthogonalization_matrix(s)?;
403 let x_t = transpose(&x);
404 let mut density = ZeroHeapMatrix::<f64, N, N>::zeros();
405 let mut old_energy = 0.0;
406 let num_occ = num_electrons / 2;
407
408 let mut error_vectors = [ZeroHeapMatrix::<f64, N, N>::zeros(); DIIS_SUBSPACE_SIZE];
409 let mut fock_history = [ZeroHeapMatrix::<f64, N, N>::zeros(); DIIS_SUBSPACE_SIZE];
410 let mut diis_count = 0;
411 let mut diis_index = 0;
412
413 for iter in 0..MAX_SCF_ITERATIONS {
414 let mut fock = *h_core;
416 for mu in 0..N {
417 for nu in 0..N {
418 let mut g = 0.0;
419 for lam in 0..N {
420 for sig in 0..N {
421 let coulomb = eri[mu][nu][lam][sig];
423 let exchange = eri[mu][sig][lam][nu];
424 g += density.get(lam, sig) * (coulomb - 0.5 * exchange);
425 }
426 }
427 fock.set(mu, nu, fock.get(mu, nu) + g);
428 }
429 }
430
431 let fds = fock * density * (*s);
433 let sdf = (*s) * density * fock;
434 let mut err_vec = ZeroHeapMatrix::<f64, N, N>::zeros();
435 for i in 0..N {
436 for j in 0..N {
437 err_vec.set(i, j, fds.get(i, j) - sdf.get(i, j));
438 }
439 }
440 let err_ortho = x_t * err_vec * x;
441 let mut max_err: f64 = 0.0;
442 for i in 0..N {
443 for j in 0..N {
444 max_err = max_err.max(err_ortho.get(i, j).abs());
445 }
446 }
447
448 fock_history[diis_index] = fock;
450 error_vectors[diis_index] = err_ortho;
451 if diis_count < DIIS_SUBSPACE_SIZE {
452 diis_count += 1;
453 }
454 diis_index = (diis_index + 1) % DIIS_SUBSPACE_SIZE;
455
456 let mut fock_extrapolated = fock;
457 if diis_count > 1 {
458 let mut b_matrix = ZeroHeapMatrix::<
459 f64,
460 { DIIS_SUBSPACE_SIZE + 1 },
461 { DIIS_SUBSPACE_SIZE + 1 },
462 >::zeros();
463 for i in 0..diis_count {
464 for j in 0..diis_count {
465 let mut dot = 0.0;
466 for mu in 0..N {
467 for nu in 0..N {
468 dot += error_vectors[i].get(mu, nu) * error_vectors[j].get(mu, nu);
469 }
470 }
471 b_matrix.set(i, j, dot);
472 }
473 b_matrix.set(i, diis_count, -1.0);
474 b_matrix.set(diis_count, i, -1.0);
475 }
476 b_matrix.set(diis_count, diis_count, 0.0);
477
478 let mut rhs = [0.0; DIIS_SUBSPACE_SIZE + 1];
479 rhs[diis_count] = -1.0;
480
481 if let Ok(c) = gaussian_elimination(b_matrix, rhs) {
482 fock_extrapolated = ZeroHeapMatrix::<f64, N, N>::zeros();
483 for i in 0..diis_count {
484 for mu in 0..N {
485 for nu in 0..N {
486 fock_extrapolated.set(
487 mu,
488 nu,
489 fock_extrapolated.get(mu, nu) + c[i] * fock_history[i].get(mu, nu),
490 );
491 }
492 }
493 }
494 }
495 }
496
497 let f_prime = x_t * fock_extrapolated * x;
499 let (evals, c_prime) = jacobi_diagonalization(&f_prime)?;
500 let c = x * c_prime;
501
502 let mut new_density = ZeroHeapMatrix::<f64, N, N>::zeros();
504 for mu in 0..N {
505 for nu in 0..N {
506 let mut sum = 0.0;
507 for a in 0..num_occ {
508 sum += c.get(mu, a) * c.get(nu, a);
509 }
510 new_density.set(mu, nu, 2.0 * sum);
511 }
512 }
513
514 let mut fock_for_energy = *h_core;
517 for mu in 0..N {
518 for nu in 0..N {
519 let mut g = 0.0;
520 for lam in 0..N {
521 for sig in 0..N {
522 let coulomb = eri[mu][nu][lam][sig];
523 let exchange = eri[mu][sig][lam][nu];
524 g += new_density.get(lam, sig) * (coulomb - 0.5 * exchange);
525 }
526 }
527 fock_for_energy.set(mu, nu, fock_for_energy.get(mu, nu) + g);
528 }
529 }
530 let mut energy = 0.0;
531 for mu in 0..N {
532 for nu in 0..N {
533 energy += 0.5
534 * new_density.get(mu, nu)
535 * (h_core.get(mu, nu) + fock_for_energy.get(mu, nu));
536 }
537 }
538
539 if iter > 0 && (energy - old_energy).abs() < SCF_CONVERGENCE_THRESHOLD && max_err < 1e-6 {
540 return Ok(RhfResult {
541 electronic_energy: energy,
542 orbital_energies: evals,
543 density: new_density,
544 coefficients: c,
545 num_occ,
546 iterations: iter + 1,
547 });
548 }
549 old_energy = energy;
550 density = new_density;
551 }
552
553 Err(ScfError::ConvergenceFailed)
554}
555
556#[cfg(test)]
557mod tests {
558 use super::*;
559
560 #[test]
561 fn test_gaussian_elimination() {
562 let mut a = ZeroHeapMatrix::<f64, 2, 2>::zeros();
563 a.set(0, 0, 3.0);
564 a.set(0, 1, 2.0);
565 a.set(1, 0, 1.0);
566 a.set(1, 1, 4.0);
567 let b = [7.0, 9.0];
568
569 let x = gaussian_elimination(a, b).unwrap();
570 assert!((x[0] - 1.0).abs() < 1e-10); assert!((x[1] - 2.0).abs() < 1e-10); }
573
574 #[test]
575 fn test_jacobi_diagonalization() {
576 let mut a = ZeroHeapMatrix::<f64, 2, 2>::zeros();
577 a.set(0, 0, 2.0);
578 a.set(0, 1, 1.0);
579 a.set(1, 0, 1.0);
580 a.set(1, 1, 2.0);
581
582 let (evals, _) = jacobi_diagonalization(&a).unwrap();
583 assert!((evals[0] - 1.0).abs() < 1e-10);
586 assert!((evals[1] - 3.0).abs() < 1e-10);
587 }
588
589 #[test]
590 fn test_rhf_scf_convergence() {
591 let mut h_core = ZeroHeapMatrix::<f64, 2, 2>::zeros();
593 h_core.set(0, 0, -1.1);
594 h_core.set(1, 1, -1.1);
595 h_core.set(0, 1, -0.9);
596 h_core.set(1, 0, -0.9);
597
598 let mut s = ZeroHeapMatrix::<f64, 2, 2>::zeros();
599 s.set(0, 0, 1.0);
600 s.set(1, 1, 1.0);
601 s.set(0, 1, 0.5);
602 s.set(1, 0, 0.5);
603
604 let eri = ZeroHeapMatrix::<f64, 2, 2>::zeros();
605 let energy = solve_rhf_scf(&h_core, &s, &eri, 2).expect("SCF should converge");
606
607 assert!(energy < 0.0);
608 }
609}