use crate::error::{SparseError, SparseResult};
const MAX_SWEEPS: usize = 100;
pub fn jacobi_eigh(a: &[f64], n: usize) -> SparseResult<(Vec<f64>, Vec<f64>)> {
if a.len() != n * n {
return Err(SparseError::InvalidArgument(format!(
"matrix length {} does not match n*n = {}",
a.len(),
n * n
)));
}
if n == 0 {
return Ok((Vec::new(), Vec::new()));
}
if n == 1 {
return Ok((vec![a[0]], vec![1.0]));
}
let mut m = vec![0.0f64; n * n];
for i in 0..n {
for j in 0..n {
m[i * n + j] = 0.5 * (a[i * n + j] + a[j * n + i]);
}
}
let mut v = vec![0.0f64; n * n];
for i in 0..n {
v[i * n + i] = 1.0;
}
for _sweep in 0..MAX_SWEEPS {
let mut off = 0.0f64;
for p in 0..n {
for q in (p + 1)..n {
off += m[p * n + q] * m[p * n + q];
}
}
if off.sqrt() <= 1e-15 {
return Ok(finalize(m, v, n));
}
for p in 0..n {
for q in (p + 1)..n {
let apq = m[p * n + q];
if apq.abs() <= 1e-300 {
continue;
}
let app = m[p * n + p];
let aqq = m[q * n + q];
let tau = (aqq - app) / (2.0 * apq);
let t = if tau >= 0.0 {
1.0 / (tau + (1.0 + tau * tau).sqrt())
} else {
-1.0 / (-tau + (1.0 + tau * tau).sqrt())
};
let c = 1.0 / (1.0 + t * t).sqrt();
let s = t * c;
for k in 0..n {
let mkp = m[k * n + p];
let mkq = m[k * n + q];
m[k * n + p] = c * mkp - s * mkq;
m[k * n + q] = s * mkp + c * mkq;
}
for k in 0..n {
let mpk = m[p * n + k];
let mqk = m[q * n + k];
m[p * n + k] = c * mpk - s * mqk;
m[q * n + k] = s * mpk + c * mqk;
}
for k in 0..n {
let vkp = v[k * n + p];
let vkq = v[k * n + q];
v[k * n + p] = c * vkp - s * vkq;
v[k * n + q] = s * vkp + c * vkq;
}
}
}
}
Err(SparseError::ConvergenceFailure(
"cyclic Jacobi eigensolver did not converge".to_string(),
))
}
fn finalize(m: Vec<f64>, v: Vec<f64>, n: usize) -> (Vec<f64>, Vec<f64>) {
let mut eigenvalues = vec![0.0f64; n];
for (i, slot) in eigenvalues.iter_mut().enumerate() {
*slot = m[i * n + i];
}
let mut order: Vec<usize> = (0..n).collect();
order.sort_by(|&i, &j| {
eigenvalues[i]
.partial_cmp(&eigenvalues[j])
.unwrap_or(std::cmp::Ordering::Equal)
});
let mut sorted_vals = vec![0.0f64; n];
let mut sorted_vecs = vec![0.0f64; n * n];
for (new_j, &old_j) in order.iter().enumerate() {
sorted_vals[new_j] = eigenvalues[old_j];
for i in 0..n {
sorted_vecs[i * n + new_j] = v[i * n + old_j];
}
}
(sorted_vals, sorted_vecs)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn diagonal_matrix() {
let a = vec![3.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 2.0];
let (vals, vecs) = jacobi_eigh(&a, 3).expect("eigh");
assert!((vals[0] - 1.0).abs() < 1e-12);
assert!((vals[1] - 2.0).abs() < 1e-12);
assert!((vals[2] - 3.0).abs() < 1e-12);
for j in 0..3 {
let mut norm = 0.0;
for i in 0..3 {
norm += vecs[i * 3 + j] * vecs[i * 3 + j];
}
assert!((norm - 1.0).abs() < 1e-12);
}
}
#[test]
fn two_by_two_known() {
let a = vec![2.0, 1.0, 1.0, 2.0];
let (vals, _vecs) = jacobi_eigh(&a, 2).expect("eigh");
assert!((vals[0] - 1.0).abs() < 1e-12);
assert!((vals[1] - 3.0).abs() < 1e-12);
}
#[test]
fn eigenvectors_reconstruct_matrix() {
let a = vec![4.0, 1.0, 2.0, 1.0, 5.0, 3.0, 2.0, 3.0, 6.0];
let n = 3;
let (vals, vecs) = jacobi_eigh(&a, n).expect("eigh");
let mut recon = vec![0.0f64; n * n];
for i in 0..n {
for j in 0..n {
let mut acc = 0.0;
for k in 0..n {
acc += vecs[i * n + k] * vals[k] * vecs[j * n + k];
}
recon[i * n + j] = acc;
}
}
for idx in 0..n * n {
assert!(
(recon[idx] - a[idx]).abs() < 1e-9,
"mismatch at {idx}: {} vs {}",
recon[idx],
a[idx]
);
}
}
#[test]
fn ascending_order() {
let a = vec![5.0, 0.0, 0.0, 4.0];
let (vals, _) = jacobi_eigh(&a, 2).expect("eigh");
assert!(vals[0] <= vals[1]);
assert!((vals[0] - 4.0).abs() < 1e-12);
}
#[test]
fn wrong_size_errors() {
let a = vec![1.0, 2.0, 3.0];
assert!(jacobi_eigh(&a, 2).is_err());
}
#[test]
fn single_element() {
let (vals, vecs) = jacobi_eigh(&[7.0], 1).expect("eigh");
assert_eq!(vals, vec![7.0]);
assert_eq!(vecs, vec![1.0]);
}
}