use super::super::jacobi::{
self, JacobiRotation, LinalgElement, apply_rotation_to_columns, apply_two_sided_rotation,
argsort_by_magnitude_desc, identity_matrix, permute_columns,
};
use super::super::{CpuClient, CpuRuntime};
use crate::algorithm::linalg::{
EigenDecomposition, linalg_demote, linalg_promote, validate_linalg_dtype,
validate_square_matrix,
};
use crate::dtype::{DType, Element};
use crate::error::Result;
use crate::runtime::RuntimeClient;
use crate::tensor::Tensor;
pub fn eig_decompose_symmetric_impl(
client: &CpuClient,
a: &Tensor<CpuRuntime>,
) -> Result<EigenDecomposition<CpuRuntime>> {
validate_linalg_dtype(a.dtype())?;
let (a, original_dtype) = linalg_promote(client, a)?;
let n = validate_square_matrix(a.shape())?;
let result = match a.dtype() {
DType::F32 => eig_decompose_symmetric_typed::<f32>(client, &a, n),
DType::F64 => eig_decompose_symmetric_typed::<f64>(client, &a, n),
_ => unreachable!(),
}?;
Ok(EigenDecomposition {
eigenvalues: linalg_demote(client, result.eigenvalues, original_dtype)?,
eigenvectors: linalg_demote(client, result.eigenvectors, original_dtype)?,
})
}
fn eig_decompose_symmetric_typed<T: Element + LinalgElement>(
client: &CpuClient,
a: &Tensor<CpuRuntime>,
n: usize,
) -> Result<EigenDecomposition<CpuRuntime>> {
let device = client.device();
if n == 0 {
let eigenvalues = Tensor::<CpuRuntime>::from_slice::<T>(&[], &[0], device);
let eigenvectors = Tensor::<CpuRuntime>::from_slice::<T>(&[], &[0, 0], device);
return Ok(EigenDecomposition {
eigenvalues,
eigenvectors,
});
}
if n == 1 {
let a_data: Vec<T> = a.to_vec();
let eigenvalues = Tensor::<CpuRuntime>::from_slice(&[a_data[0]], &[1], device);
let eigenvectors = Tensor::<CpuRuntime>::from_slice(&[T::one()], &[1, 1], device);
return Ok(EigenDecomposition {
eigenvalues,
eigenvectors,
});
}
let a_data: Vec<T> = a.to_vec();
let mut work: Vec<T> = vec![T::zero(); n * n];
for i in 0..n {
for j in 0..=i {
let val = a_data[i * n + j];
work[i * n + j] = val;
work[j * n + i] = val;
}
}
let mut v: Vec<T> = identity_matrix(n);
let eps = T::epsilon_val();
let tol = (n as f64) * eps;
let max_sweeps = 30;
for _sweep in 0..max_sweeps {
let mut max_off_diag = 0.0f64;
for i in 0..n {
for j in (i + 1)..n {
let val = work[i * n + j].abs_val().to_f64();
if val > max_off_diag {
max_off_diag = val;
}
}
}
if max_off_diag < tol {
break;
}
for p in 0..n {
for q in (p + 1)..n {
let a_pq = work[p * n + q];
if a_pq.abs_val().to_f64() < tol {
continue;
}
let a_pp = work[p * n + p];
let a_qq = work[q * n + q];
let rot = JacobiRotation::compute(a_pp.to_f64(), a_qq.to_f64(), a_pq.to_f64());
apply_two_sided_rotation(&mut work, n, p, q, &rot, a_pp, a_qq, a_pq);
apply_rotation_to_columns(&mut v, n, n, p, q, &rot);
}
}
}
let mut eigenvalues: Vec<T> = vec![T::zero(); n];
for i in 0..n {
eigenvalues[i] = work[i * n + i];
}
let indices = argsort_by_magnitude_desc(&eigenvalues);
let eigenvalues_sorted = jacobi::permute_vector(&eigenvalues, &indices);
let v_sorted = permute_columns(&v, n, n, &indices, n);
let eigenvalues_tensor = Tensor::<CpuRuntime>::from_slice(&eigenvalues_sorted, &[n], device);
let eigenvectors_tensor = Tensor::<CpuRuntime>::from_slice(&v_sorted, &[n, n], device);
Ok(EigenDecomposition {
eigenvalues: eigenvalues_tensor,
eigenvectors: eigenvectors_tensor,
})
}