use crate::error::{NumRs2Error, Result};
pub(crate) fn symmetric_eigendecomposition(
matrix: &[f64],
n: usize,
) -> Result<(Vec<f64>, Vec<f64>)> {
if matrix.len() != n * n {
return Err(NumRs2Error::InvalidInput(format!(
"Matrix size {} does not match dimension {}x{}",
matrix.len(),
n,
n
)));
}
if n == 1 {
return Ok((vec![matrix[0]], vec![1.0]));
}
let mut a = matrix.to_vec();
let mut v = vec![0.0; n * n];
for i in 0..n {
v[i * n + i] = 1.0;
}
let max_sweeps = 100 * n * n;
let tolerance = 1e-15;
for _sweep in 0..max_sweeps {
let mut off_diag_sq = 0.0;
for i in 0..n {
for j in (i + 1)..n {
off_diag_sq += a[i * n + j] * a[i * n + j];
}
}
let off_diag = (2.0 * off_diag_sq).sqrt();
let diag_norm: f64 = (0..n)
.map(|i| a[i * n + i] * a[i * n + i])
.sum::<f64>()
.sqrt();
if off_diag < tolerance * diag_norm.max(1.0) {
break;
}
for p in 0..n {
for q in (p + 1)..n {
let a_pq = a[p * n + q];
if a_pq.abs() < 1e-30 {
continue;
}
let a_pp = a[p * n + p];
let a_qq = a[q * n + q];
let tau = (a_qq - a_pp) / (2.0 * a_pq);
let t = if tau.abs() > 1e15 {
1.0 / (2.0 * tau)
} else {
let sign_tau = if tau >= 0.0 { 1.0 } else { -1.0 };
sign_tau / (tau.abs() + (1.0 + tau * tau).sqrt())
};
let c = 1.0 / (1.0 + t * t).sqrt();
let s = t * c;
apply_jacobi_rotation_matrix(&mut a, n, p, q, c, s);
apply_jacobi_rotation_vectors(&mut v, n, p, q, c, s);
}
}
}
let eigenvalues: Vec<f64> = (0..n).map(|i| a[i * n + i]).collect();
Ok((eigenvalues, v))
}
fn apply_jacobi_rotation_matrix(a: &mut [f64], n: usize, p: usize, q: usize, c: f64, s: f64) {
let a_pp = a[p * n + p];
let a_qq = a[q * n + q];
let a_pq = a[p * n + q];
a[p * n + p] = c * c * a_pp - 2.0 * s * c * a_pq + s * s * a_qq;
a[q * n + q] = s * s * a_pp + 2.0 * s * c * a_pq + c * c * a_qq;
a[p * n + q] = 0.0;
a[q * n + p] = 0.0;
for r in 0..n {
if r == p || r == q {
continue;
}
let a_rp = a[r * n + p];
let a_rq = a[r * n + q];
let new_rp = c * a_rp - s * a_rq;
let new_rq = s * a_rp + c * a_rq;
a[r * n + p] = new_rp;
a[p * n + r] = new_rp;
a[r * n + q] = new_rq;
a[q * n + r] = new_rq;
}
}
fn apply_jacobi_rotation_vectors(v: &mut [f64], n: usize, p: usize, q: usize, c: f64, s: f64) {
for r in 0..n {
let v_rp = v[r * n + p];
let v_rq = v[r * n + q];
v[r * n + p] = c * v_rp - s * v_rq;
v[r * n + q] = s * v_rp + c * v_rq;
}
}