rustyqlib/core/linalg/decomp/
eigen.rs1pub fn symmetric_eigen(a: &[Vec<f64>]) -> (Vec<f64>, Vec<Vec<f64>>) {
10 let n = a.len();
11 let mut m = a.to_vec();
12 let mut v: Vec<Vec<f64>> = (0..n)
13 .map(|i| (0..n).map(|j| if i == j { 1.0 } else { 0.0 }).collect())
14 .collect();
15 for _sweep in 0..100 {
16 let off: f64 = (0..n)
17 .flat_map(|i| ((i + 1)..n).map(move |j| (i, j)))
18 .map(|(i, j)| m[i][j] * m[i][j])
19 .sum();
20 if off < 1e-24 {
21 break;
22 }
23 for p in 0..n {
24 for q in (p + 1)..n {
25 if m[p][q].abs() < 1e-300 {
26 continue;
27 }
28 let theta = (m[q][q] - m[p][p]) / (2.0 * m[p][q]);
29 let t = theta.signum() / (theta.abs() + (theta * theta + 1.0).sqrt());
30 let c = 1.0 / (t * t + 1.0).sqrt();
31 let s = t * c;
32 for k in 0..n {
33 let (mkp, mkq) = (m[k][p], m[k][q]);
34 m[k][p] = c * mkp - s * mkq;
35 m[k][q] = s * mkp + c * mkq;
36 }
37 for k in 0..n {
38 let (mpk, mqk) = (m[p][k], m[q][k]);
39 m[p][k] = c * mpk - s * mqk;
40 m[q][k] = s * mpk + c * mqk;
41 }
42 for row in v.iter_mut() {
43 let (vp, vq) = (row[p], row[q]);
44 row[p] = c * vp - s * vq;
45 row[q] = s * vp + c * vq;
46 }
47 }
48 }
49 }
50 ((0..n).map(|i| m[i][i]).collect(), v)
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56
57 #[test]
58 fn reproduces_known_eigenvalues() {
59 let (mut vals, _) = symmetric_eigen(&[vec![2.0, 1.0], vec![1.0, 2.0]]);
61 vals.sort_by(f64::total_cmp);
62 assert!((vals[0] - 1.0).abs() < 1e-10 && (vals[1] - 3.0).abs() < 1e-10);
63 }
64
65 #[test]
66 fn reconstructs_the_matrix() {
67 let a = vec![
68 vec![3.0, 1.0, 0.5],
69 vec![1.0, 2.0, -0.4],
70 vec![0.5, -0.4, 1.5],
71 ];
72 let (vals, vecs) = symmetric_eigen(&a);
73 for i in 0..3 {
74 for j in 0..3 {
75 let recon: f64 = (0..3).map(|k| vecs[i][k] * vals[k] * vecs[j][k]).sum();
76 assert!((recon - a[i][j]).abs() < 1e-10, "[{i}][{j}]");
77 }
78 }
79 }
80}