use crate::tensor::{Matrix, Vector};
use nalgebra::DMatrix;
pub fn eigenvalues(matrix: &Matrix) -> Result<Vec<f64>, String> {
if matrix.shape[0] != matrix.shape[1] {
return Err("Matrix must be square to compute eigenvalues".to_string());
}
let n = matrix.shape[0];
let mut data = Vec::with_capacity(n * n);
for i in 0..n {
for j in 0..n {
data.push(matrix.get([i, j]).unwrap());
}
}
let dmatrix = DMatrix::from_row_slice(n, n, &data);
let eigen = dmatrix.symmetric_eigen();
Ok(eigen.eigenvalues.iter().copied().collect())
}
pub fn eigenvectors(matrix: &Matrix) -> Result<(Vec<f64>, Matrix), String> {
if matrix.shape[0] != matrix.shape[1] {
return Err("Matrix must be square to compute eigenvectors".to_string());
}
let n = matrix.shape[0];
let mut data = Vec::with_capacity(n * n);
for i in 0..n {
for j in 0..n {
data.push(matrix.get([i, j]).unwrap());
}
}
let dmatrix = DMatrix::from_row_slice(n, n, &data);
let eigen = dmatrix.symmetric_eigen();
let eigenvals: Vec<f64> = eigen.eigenvalues.iter().copied().collect();
let eigenvecs_data: Vec<f64> = eigen.eigenvectors.iter().copied().collect();
let eigenvecs = Matrix::from_data([n, n], eigenvecs_data)?;
Ok((eigenvals, eigenvecs))
}
pub fn power_iteration(
matrix: &Matrix,
max_iterations: usize,
tolerance: f64,
) -> Result<(f64, Vector), String> {
if matrix.shape[0] != matrix.shape[1] {
return Err("Matrix must be square".to_string());
}
let n = matrix.shape[0];
let mut v = Vector::from_data([n], vec![1.0; n])?;
v = v.normalize();
let mut lambda = 0.0;
for _ in 0..max_iterations {
let v_new = matrix.matvec(&v)?;
let v_new_normalized = v_new.normalize();
let av = matrix.matvec(&v_new_normalized)?;
let lambda_new = v_new_normalized.dot(&av)?;
if (lambda_new - lambda).abs() < tolerance {
return Ok((lambda_new, v_new_normalized));
}
lambda = lambda_new;
v = v_new_normalized;
}
Ok((lambda, v))
}
pub fn spectral_radius(matrix: &Matrix) -> Result<f64, String> {
let eigenvals = eigenvalues(matrix)?;
Ok(eigenvals
.iter()
.map(|x| x.abs())
.fold(f64::NEG_INFINITY, f64::max))
}
pub fn is_positive_definite(matrix: &Matrix) -> Result<bool, String> {
let eigenvals = eigenvalues(matrix)?;
Ok(eigenvals.iter().all(|&x| x > 0.0))
}
pub fn is_positive_semidefinite(matrix: &Matrix) -> Result<bool, String> {
let eigenvals = eigenvalues(matrix)?;
Ok(eigenvals.iter().all(|&x| x >= -1e-10)) }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_eigenvalues_identity() {
let m = Matrix::identity(3);
let eigenvals = eigenvalues(&m).unwrap();
for &val in &eigenvals {
assert!((val - 1.0).abs() < 1e-10);
}
}
#[test]
fn test_eigenvalues_symmetric() {
let m = Matrix::from_data([2, 2], vec![2.0, 1.0, 1.0, 2.0]).unwrap();
let eigenvals = eigenvalues(&m).unwrap();
assert_eq!(eigenvals.len(), 2);
let mut sorted = eigenvals.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
assert!((sorted[0] - 1.0).abs() < 1e-10);
assert!((sorted[1] - 3.0).abs() < 1e-10);
}
#[test]
fn test_eigenvectors() {
let m = Matrix::from_data([2, 2], vec![2.0, 0.0, 0.0, 3.0]).unwrap();
let result = eigenvectors(&m);
assert!(result.is_ok());
let (eigenvals, eigenvecs) = result.unwrap();
assert_eq!(eigenvals.len(), 2);
assert_eq!(eigenvecs.shape(), &[2, 2]);
}
#[test]
fn test_power_iteration() {
let m = Matrix::from_data([2, 2], vec![2.0, 1.0, 1.0, 2.0]).unwrap();
let result = power_iteration(&m, 100, 1e-10);
assert!(result.is_ok());
let (lambda, _v) = result.unwrap();
assert!((lambda - 3.0).abs() < 1e-6);
}
#[test]
fn test_is_positive_definite() {
let m = Matrix::from_data([2, 2], vec![2.0, 1.0, 1.0, 2.0]).unwrap();
assert!(is_positive_definite(&m).unwrap());
}
}