use pounce_common::types::Number;
pub fn symmetric_eigen(
a: &[Number],
n: usize,
eigenvalues: &mut [Number],
eigenvectors: &mut [Number],
) -> bool {
if a.len() != n * n || eigenvalues.len() != n || eigenvectors.len() != n * n {
return false;
}
if n == 0 {
return true;
}
let mut m = a.to_vec();
for i in 0..n {
for j in 0..i {
let upper = m[i * n + j];
let lower = m[j * n + i];
let v = 0.5 * (upper + lower);
m[i * n + j] = v;
m[j * n + i] = v;
}
}
for v in eigenvectors.iter_mut() {
*v = 0.0;
}
for k in 0..n {
eigenvectors[k * n + k] = 1.0;
}
let mut frob_sq = 0.0;
for &x in m.iter() {
frob_sq += x * x;
}
let tol = (1e-28 * frob_sq).max(1e-300);
let max_sweeps = 50;
for _sweep in 0..max_sweeps {
let mut off = 0.0;
for i in 0..n {
for j in 0..n {
if i != j {
off += m[i * n + j] * m[i * n + j];
}
}
}
if off < tol {
break;
}
for p in 0..n {
for q in (p + 1)..n {
let app = m[p * n + p];
let aqq = m[q * n + q];
let apq = m[p * n + q];
if apq.abs() < 1e-300 {
continue;
}
let (c, s) = jacobi_cs(app, aqq, apq);
for i in 0..n {
let mip = m[i + n * p];
let miq = m[i + n * q];
m[i + n * p] = c * mip - s * miq;
m[i + n * q] = s * mip + c * miq;
}
for j in 0..n {
let mpj = m[p + n * j];
let mqj = m[q + n * j];
m[p + n * j] = c * mpj - s * mqj;
m[q + n * j] = s * mpj + c * mqj;
}
m[p * n + q] = 0.0;
m[q * n + p] = 0.0;
for i in 0..n {
let vip = eigenvectors[i + n * p];
let viq = eigenvectors[i + n * q];
eigenvectors[i + n * p] = c * vip - s * viq;
eigenvectors[i + n * q] = s * vip + c * viq;
}
}
}
}
let mut idx: Vec<usize> = (0..n).collect();
let diag: Vec<Number> = (0..n).map(|k| m[k * n + k]).collect();
idx.sort_by(|&i, &j| {
diag[i]
.partial_cmp(&diag[j])
.unwrap_or(std::cmp::Ordering::Equal)
});
let v_in = eigenvectors.to_vec();
for (new_pos, &old_pos) in idx.iter().enumerate() {
eigenvalues[new_pos] = diag[old_pos];
for row in 0..n {
eigenvectors[row + n * new_pos] = v_in[row + n * old_pos];
}
}
true
}
fn jacobi_cs(app: Number, aqq: Number, apq: Number) -> (Number, Number) {
if apq == 0.0 {
return (1.0, 0.0);
}
let theta = (aqq - app) / (2.0 * apq);
let t = if theta >= 0.0 {
1.0 / (theta + (1.0 + theta * theta).sqrt())
} else {
1.0 / (theta - (1.0 + theta * theta).sqrt())
};
let c = 1.0 / (1.0 + t * t).sqrt();
let s = t * c;
(c, s)
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_close(a: Number, b: Number, tol: Number, label: &str) {
assert!(
(a - b).abs() < tol,
"{label}: {a} vs {b} (|d|={})",
(a - b).abs()
);
}
#[test]
fn eigen_diagonal_matrix() {
let a = vec![3.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 2.0];
let mut w = vec![0.0; 3];
let mut v = vec![0.0; 9];
assert!(symmetric_eigen(&a, 3, &mut w, &mut v));
assert_close(w[0], 1.0, 1e-12, "w0");
assert_close(w[1], 2.0, 1e-12, "w1");
assert_close(w[2], 3.0, 1e-12, "w2");
}
#[test]
fn eigen_2x2_known() {
let a = vec![2.0, 1.0, 1.0, 2.0];
let mut w = vec![0.0; 2];
let mut v = vec![0.0; 4];
assert!(symmetric_eigen(&a, 2, &mut w, &mut v));
assert_close(w[0], 1.0, 1e-12, "w0");
assert_close(w[1], 3.0, 1e-12, "w1");
let s = 1.0 / 2f64.sqrt();
assert!(
((v[0] - s).abs() < 1e-10 && (v[1] + s).abs() < 1e-10)
|| ((v[0] + s).abs() < 1e-10 && (v[1] - s).abs() < 1e-10)
);
}
#[test]
fn eigen_reconstructs_matrix() {
let a = vec![
4.0, 1.0, 2.0, 0.5, 1.0, 3.0, 0.7, 1.5, 2.0, 0.7, 5.0, 0.3, 0.5, 1.5, 0.3, 2.0,
];
let n = 4;
let mut w = vec![0.0; n];
let mut v = vec![0.0; n * n];
assert!(symmetric_eigen(&a, n, &mut w, &mut v));
for j in 0..n {
let mut av = vec![0.0; n];
for row in 0..n {
let mut s = 0.0;
for col in 0..n {
s += a[row + n * col] * v[col + n * j];
}
av[row] = s;
}
for row in 0..n {
let lv = w[j] * v[row + n * j];
assert_close(av[row], lv, 1e-9, &format!("A v_{j} row {row}"));
}
}
for k in 1..n {
assert!(w[k - 1] <= w[k] + 1e-12, "not sorted at {k}");
}
}
#[test]
fn eigen_handles_indefinite() {
let a = vec![1.0, 2.0, 2.0, 1.0];
let mut w = vec![0.0; 2];
let mut v = vec![0.0; 4];
assert!(symmetric_eigen(&a, 2, &mut w, &mut v));
assert_close(w[0], -1.0, 1e-12, "w0");
assert_close(w[1], 3.0, 1e-12, "w1");
}
#[test]
fn eigen_rejects_wrong_buffer_size() {
let a = vec![1.0; 4];
let mut w = vec![0.0; 3];
let mut v = vec![0.0; 4];
assert!(!symmetric_eigen(&a, 2, &mut w, &mut v));
}
#[test]
fn eigen_zero_dimension() {
let mut w: Vec<Number> = vec![];
let mut v: Vec<Number> = vec![];
assert!(symmetric_eigen(&[], 0, &mut w, &mut v));
}
}