use crate::error::LinalgError;
use crate::linear_algebra::{Matrix, Vector};
use crate::scalar::Numeric;
#[derive(Debug, Clone, Copy)]
#[must_use]
pub struct SymmetricEigendecomposition<const N: usize, T = f64> {
pub(crate) eigenvalues: Vector<N, T>,
pub(crate) eigenvectors: Matrix<N, N, T>,
}
impl<const N: usize, T: Numeric> Matrix<N, N, T> {
pub fn symmetric_eigendecomposition(
self,
) -> Result<SymmetricEigendecomposition<N, T>, LinalgError> {
if !self.is_finite() {
return Err(LinalgError::NonFinite);
}
if !self.is_symmetric() {
return Err(LinalgError::NotSymmetric);
}
let mut largest_entry = T::ZERO;
for row in 0..N {
for column in 0..N {
largest_entry = largest_entry.max(self[(row, column)].abs());
}
}
let threshold = T::EPSILON * largest_entry;
let mut working = self;
let mut eigenvectors = Matrix::<N, N, T>::identity();
let max_sweeps = 60;
for _ in 0..max_sweeps {
let mut off_max = T::ZERO;
for p in 0..N {
for q in (p + 1)..N {
let off_diagonal = working[(p, q)];
off_max = off_max.max(off_diagonal.abs());
if off_diagonal.abs() <= threshold {
continue;
}
let alpha = working[(p, p)];
let beta = working[(q, q)];
let gamma = off_diagonal;
let zeta = (beta - alpha) / (T::TWO * gamma);
let sign = if zeta < T::ZERO { -T::ONE } else { T::ONE };
let t = sign / (zeta.abs() + (T::ONE + zeta * zeta).sqrt());
let c = T::ONE / (T::ONE + t * t).sqrt();
let s = c * t;
working[(p, p)] = alpha - t * gamma;
working[(q, q)] = beta + t * gamma;
working[(p, q)] = T::ZERO;
working[(q, p)] = T::ZERO;
for i in 0..N {
if i == p || i == q {
continue;
}
let old = working[(i, p)];
let other = working[(i, q)];
working[(i, p)] = c * old - s * other;
working[(p, i)] = working[(i, p)];
working[(i, q)] = s * old + c * other;
working[(q, i)] = working[(i, q)];
}
for i in 0..N {
let old = eigenvectors[(i, p)];
let other = eigenvectors[(i, q)];
eigenvectors[(i, p)] = c * old - s * other;
eigenvectors[(i, q)] = s * old + c * other;
}
}
}
if off_max <= threshold {
break;
}
}
let mut eigenvalues = Vector::<N, T>::zeros();
for index in 0..N {
eigenvalues[index] = working[(index, index)];
}
for k in 0..N {
let mut top = k;
for j in (k + 1)..N {
if eigenvalues[j] > eigenvalues[top] {
top = j;
}
}
if top != k {
let tmp = eigenvalues[k];
eigenvalues[k] = eigenvalues[top];
eigenvalues[top] = tmp;
for i in 0..N {
let tmp = eigenvectors[(i, k)];
eigenvectors[(i, k)] = eigenvectors[(i, top)];
eigenvectors[(i, top)] = tmp;
}
}
}
for k in 0..N {
let mut row = 0;
let mut best = T::ZERO;
for i in 0..N {
let magnitude = eigenvectors[(i, k)].abs();
if magnitude > best {
best = magnitude;
row = i;
}
}
if eigenvectors[(row, k)] < T::ZERO {
for i in 0..N {
eigenvectors[(i, k)] = -eigenvectors[(i, k)];
}
}
}
Ok(SymmetricEigendecomposition {
eigenvalues,
eigenvectors,
})
}
}
impl<const N: usize, T: Numeric> SymmetricEigendecomposition<N, T> {
pub fn eigenvalues(&self) -> Vector<N, T> {
self.eigenvalues
}
pub fn eigenvectors(&self) -> Matrix<N, N, T> {
self.eigenvectors
}
#[inline]
#[must_use]
pub fn determinant(&self) -> T {
let mut product = T::ONE;
for index in 0..N {
product *= self.eigenvalues[index];
}
product
}
#[inline]
#[must_use]
pub fn condition_number(&self) -> T {
if N == 0 {
return T::INFINITY;
}
let mut largest = T::ZERO;
let mut smallest = T::INFINITY;
for index in 0..N {
let magnitude = self.eigenvalues[index].abs();
largest = largest.max(magnitude);
smallest = smallest.min(magnitude);
}
if smallest <= T::ZERO {
T::INFINITY
} else {
largest / smallest
}
}
#[inline]
#[must_use]
pub fn is_positive_definite(&self) -> bool {
if N == 0 {
return true;
}
self.eigenvalues[N - 1] > T::ZERO
}
pub fn clamped(&self, minimum_eigenvalue: T) -> Matrix<N, N, T> {
let mut raised = Vector::<N, T>::zeros();
for index in 0..N {
raised[index] = self.eigenvalues[index].max(minimum_eigenvalue);
}
let mut result = Matrix::<N, N, T>::zeros();
for row in 0..N {
for column in row..N {
let mut sum = T::ZERO;
for k in 0..N {
sum += self.eigenvectors[(row, k)] * raised[k] * self.eigenvectors[(column, k)];
}
result[(row, column)] = sum;
result[(column, row)] = sum;
}
}
result
}
}