use nalgebra::DMatrix;
use nalgebra_lapack::{Eigen, LU, QR};
pub fn eigenvalues(elements: &[Vec<f64>]) -> Result<Vec<f64>, String> {
let n = elements.len();
if n == 0 {
return Ok(vec![]);
}
let flat: Vec<f64> = (0..n)
.flat_map(|col| (0..n).map(move |row| elements[row][col]))
.collect();
let matrix = DMatrix::from_vec(n, n, flat);
let eigen = Eigen::new(matrix, false, false)
.ok_or_else(|| "LAPACK eigenvalue decomposition failed".to_string())?;
let real_eigenvalues = eigen.eigenvalues_re;
Ok(real_eigenvalues.as_slice().to_vec())
}
pub fn eigenvector(elements: &[Vec<f64>], eigenvalue: f64) -> Result<Vec<f64>, String> {
let n = elements.len();
if n == 0 {
return Err("Empty matrix".to_string());
}
let flat: Vec<f64> = (0..n)
.flat_map(|col| (0..n).map(move |row| elements[row][col]))
.collect();
let matrix = DMatrix::from_vec(n, n, flat);
let eigen = Eigen::new(matrix, false, true)
.ok_or_else(|| "LAPACK eigenvalue decomposition failed".to_string())?;
let eigenvectors = eigen
.eigenvectors
.ok_or_else(|| "LAPACK did not compute eigenvectors".to_string())?;
let eigenvalues = eigen.eigenvalues_re.as_slice();
let mut best_idx = 0;
let mut best_diff = f64::INFINITY;
for (i, &ev) in eigenvalues.iter().enumerate() {
let diff = (ev - eigenvalue).abs();
if diff < best_diff {
best_diff = diff;
best_idx = i;
}
}
Ok(eigenvectors.column(best_idx).iter().copied().collect())
}
pub fn eigenpairs(elements: &[Vec<f64>]) -> Result<Vec<(f64, Vec<f64>)>, String> {
let n = elements.len();
if n == 0 {
return Ok(vec![]);
}
let flat: Vec<f64> = (0..n)
.flat_map(|col| (0..n).map(move |row| elements[row][col]))
.collect();
let matrix = DMatrix::from_vec(n, n, flat);
let eigen = Eigen::new(matrix, false, true)
.ok_or_else(|| "LAPACK eigenvalue decomposition failed".to_string())?;
let eigenvectors = eigen
.eigenvectors
.ok_or_else(|| "LAPACK did not compute eigenvectors".to_string())?;
let eigenvalues = eigen.eigenvalues_re.as_slice();
let mut pairs = Vec::with_capacity(n);
for (i, &ev) in eigenvalues.iter().enumerate() {
let vec: Vec<f64> = eigenvectors.column(i).iter().copied().collect();
pairs.push((ev, vec));
}
Ok(pairs)
}
pub fn qr_decomposition(a: &[Vec<f64>]) -> (Vec<Vec<f64>>, Vec<Vec<f64>>) {
let n = a.len();
if n == 0 {
return (vec![], vec![]);
}
let flat: Vec<f64> = (0..n)
.flat_map(|col| (0..n).map(move |row| a[row][col]))
.collect();
let matrix = DMatrix::from_vec(n, n, flat);
let qr = QR::new(matrix);
let q_mat = qr.q();
let r_mat = qr.r();
let q: Vec<Vec<f64>> = (0..n)
.map(|i| (0..n).map(|j| q_mat[(i, j)]).collect())
.collect();
let r: Vec<Vec<f64>> = (0..n)
.map(|i| (0..n).map(|j| r_mat[(i, j)]).collect())
.collect();
(q, r)
}
pub fn solve_linear_system(a: &[Vec<f64>], b: &[f64]) -> Vec<f64> {
let n = a.len();
if n == 0 {
return vec![];
}
let flat: Vec<f64> = (0..n)
.flat_map(|col| (0..n).map(move |row| a[row][col]))
.collect();
let matrix = DMatrix::from_vec(n, n, flat);
let b_vec = nalgebra::DVector::from_vec(b.to_vec());
let lu = LU::new(matrix);
match lu.solve(&b_vec) {
Some(x) => x.as_slice().to_vec(),
None => {
vec![0.0; n]
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
#[test]
fn test_eigenvalues_2x2() {
let m = vec![vec![2.0, 1.0], vec![1.0, 2.0]];
let mut evs = eigenvalues(&m).unwrap();
evs.sort_by(|a, b| a.partial_cmp(b).unwrap());
assert!(approx_eq(evs[0], 1.0, 1e-10));
assert!(approx_eq(evs[1], 3.0, 1e-10));
}
#[test]
fn test_eigenvalues_3x3_diagonal() {
let m = vec![
vec![1.0, 0.0, 0.0],
vec![0.0, 2.0, 0.0],
vec![0.0, 0.0, 3.0],
];
let mut evs = eigenvalues(&m).unwrap();
evs.sort_by(|a, b| a.partial_cmp(b).unwrap());
assert!(approx_eq(evs[0], 1.0, 1e-10));
assert!(approx_eq(evs[1], 2.0, 1e-10));
assert!(approx_eq(evs[2], 3.0, 1e-10));
}
#[test]
fn test_eigenvector() {
let m = vec![vec![2.0, 1.0], vec![1.0, 2.0]];
let ev = eigenvector(&m, 3.0).unwrap();
let ratio = ev[0] / ev[1];
assert!(approx_eq(ratio.abs(), 1.0, 1e-10));
}
#[test]
fn test_eigenpairs() {
let m = vec![vec![2.0, 1.0], vec![1.0, 2.0]];
let pairs = eigenpairs(&m).unwrap();
assert_eq!(pairs.len(), 2);
for (eigenvalue, eigenvector) in &pairs {
for i in 0..2 {
let av: f64 = (0..2).map(|j| m[i][j] * eigenvector[j]).sum();
let lv = eigenvalue * eigenvector[i];
assert!(approx_eq(av, lv, 1e-10));
}
}
}
#[test]
fn test_qr_decomposition() {
let a = vec![vec![1.0, 2.0], vec![3.0, 4.0]];
let (q, r) = qr_decomposition(&a);
let n = q.len();
for i in 0..n {
for j in 0..n {
let dot: f64 = (0..n).map(|k| q[k][i] * q[k][j]).sum();
let expected = if i == j { 1.0 } else { 0.0 };
assert!(approx_eq(dot, expected, 1e-10));
}
}
for i in 0..n {
for j in 0..n {
let qr_ij: f64 = (0..n).map(|k| q[i][k] * r[k][j]).sum();
assert!(approx_eq(qr_ij, a[i][j], 1e-10));
}
}
}
#[test]
fn test_solve_linear_system() {
let a = vec![vec![2.0, 1.0], vec![1.0, 3.0]];
let b = vec![5.0, 10.0];
let x = solve_linear_system(&a, &b);
assert!(approx_eq(x[0], 1.0, 1e-10));
assert!(approx_eq(x[1], 3.0, 1e-10));
}
#[test]
fn test_large_eigenvalues() {
let m = vec![
vec![5.0, 1.0, 0.0, 0.0, 0.0],
vec![1.0, 4.0, 1.0, 0.0, 0.0],
vec![0.0, 1.0, 3.0, 1.0, 0.0],
vec![0.0, 0.0, 1.0, 2.0, 1.0],
vec![0.0, 0.0, 0.0, 1.0, 1.0],
];
let evs = eigenvalues(&m).unwrap();
assert_eq!(evs.len(), 5);
for ev in &evs {
assert!(*ev > 0.0);
}
}
}