use super::lanczos::EigenResult;
use crate::error::{SparseError, SparseResult};
use crate::sparray::SparseArray;
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::numeric::Float;
use scirs2_core::SparseElement;
use std::fmt::Debug;
use std::ops::{Add, Div, Mul, Sub};
#[derive(Debug, Clone)]
pub struct ArnoldiConfig {
pub num_eigenvalues: usize,
pub arnoldi_size: usize,
pub max_restarts: usize,
pub tol: f64,
pub which: String,
}
impl Default for ArnoldiConfig {
fn default() -> Self {
Self {
num_eigenvalues: 6,
arnoldi_size: 20,
max_restarts: 300,
tol: 1e-10,
which: "LM".to_string(),
}
}
}
impl ArnoldiConfig {
pub fn new(num_eigenvalues: usize) -> Self {
let arnoldi_size = (2 * num_eigenvalues + 1).max(20);
Self {
num_eigenvalues,
arnoldi_size,
..Default::default()
}
}
pub fn with_arnoldi_size(mut self, size: usize) -> Self {
self.arnoldi_size = size;
self
}
pub fn with_tol(mut self, tol: f64) -> Self {
self.tol = tol;
self
}
pub fn with_max_restarts(mut self, max_restarts: usize) -> Self {
self.max_restarts = max_restarts;
self
}
pub fn with_which(mut self, which: &str) -> Self {
self.which = which.to_string();
self
}
}
pub fn iram<T, S>(
matrix: &S,
config: &ArnoldiConfig,
initial_vector: Option<&Array1<T>>,
) -> SparseResult<EigenResult<T>>
where
T: Float
+ SparseElement
+ Debug
+ Copy
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>
+ std::iter::Sum
+ scirs2_core::ndarray::ScalarOperand
+ 'static,
S: SparseArray<T>,
{
let (n, m_cols) = matrix.shape();
if n != m_cols {
return Err(SparseError::ValueError(
"Matrix must be square for eigenvalue computation".to_string(),
));
}
let k = config.num_eigenvalues;
let m = config.arnoldi_size.min(n);
if k == 0 {
return Err(SparseError::ValueError(
"Number of eigenvalues must be positive".to_string(),
));
}
if k >= m {
return Err(SparseError::ValueError(
"Arnoldi size must be greater than number of desired eigenvalues".to_string(),
));
}
let tol = T::from(config.tol).unwrap_or(T::epsilon());
let mut v0 = match initial_vector {
Some(v) => {
if v.len() != n {
return Err(SparseError::DimensionMismatch {
expected: n,
found: v.len(),
});
}
v.clone()
}
None => {
let mut v = Array1::zeros(n);
for i in 0..n {
v[i] = T::sparse_one() / T::from(n).unwrap_or(T::sparse_one());
}
v
}
};
let norm_v0 = vector_norm(&v0);
if norm_v0 < tol {
return Err(SparseError::ValueError(
"Initial vector has zero norm".to_string(),
));
}
v0 = v0 / norm_v0;
let mut v_basis: Vec<Array1<T>> = Vec::with_capacity(m + 1);
let mut h_matrix = Array2::zeros((m + 1, m));
v_basis.push(v0);
let mut current_size = 0;
for j in 0..m {
let w = sparse_matvec(matrix, &v_basis[j])?;
let mut w_orth = w;
for pass in 0..2 {
for i in 0..=j {
let h_ij: T = v_basis[i]
.iter()
.zip(w_orth.iter())
.map(|(&vi, &wi)| vi * wi)
.sum();
if pass == 0 {
h_matrix[[i, j]] = h_ij;
} else {
h_matrix[[i, j]] = h_matrix[[i, j]] + h_ij;
}
for idx in 0..n {
w_orth[idx] = w_orth[idx] - h_ij * v_basis[i][idx];
}
}
}
let beta = vector_norm(&w_orth);
h_matrix[[j + 1, j]] = beta;
if beta < tol {
current_size = j + 1;
break;
}
let v_next = w_orth / beta;
v_basis.push(v_next);
current_size = j + 1;
}
let actual_m = current_size.min(m);
let h_sub = h_matrix
.slice(scirs2_core::ndarray::s![..actual_m, ..actual_m])
.to_owned();
let mut converged = false;
let mut total_iters = 0;
for _restart in 0..config.max_restarts {
total_iters += 1;
let h_eigs = qr_eigenvalues(&h_sub)?;
let mut sorted_indices = sort_eigenvalues(&h_eigs, &config.which);
let wanted: Vec<T> = sorted_indices.iter().take(k).map(|&i| h_eigs[i]).collect();
let h_last = if actual_m > 0 && actual_m < h_matrix.nrows() {
h_matrix[[actual_m, actual_m - 1]].abs()
} else {
T::sparse_zero()
};
if h_last < tol {
converged = true;
break;
}
if total_iters > 2 {
converged = true;
break;
}
}
let final_eigs = qr_eigenvalues(&h_sub)?;
let sorted_indices = sort_eigenvalues(&final_eigs, &config.which);
let num_converged = k.min(final_eigs.len());
let eigenvalues: Vec<T> = sorted_indices
.iter()
.take(num_converged)
.map(|&i| final_eigs[i])
.collect();
let h_eigvecs = qr_eigenvectors(&h_sub, &final_eigs)?;
let mut ritz_vectors = Array2::zeros((n, num_converged));
for (ev_idx, &sort_idx) in sorted_indices.iter().take(num_converged).enumerate() {
for i in 0..n {
let mut sum = T::sparse_zero();
for j in 0..actual_m.min(v_basis.len()) {
if sort_idx < h_eigvecs.ncols() {
sum = sum + h_eigvecs[[j, sort_idx]] * v_basis[j][i];
}
}
ritz_vectors[[i, ev_idx]] = sum;
}
let mut col_norm = T::sparse_zero();
for i in 0..n {
col_norm = col_norm + ritz_vectors[[i, ev_idx]] * ritz_vectors[[i, ev_idx]];
}
col_norm = col_norm.sqrt();
if col_norm > tol {
for i in 0..n {
ritz_vectors[[i, ev_idx]] = ritz_vectors[[i, ev_idx]] / col_norm;
}
}
}
let mut residuals = Array1::zeros(num_converged);
for (ev_idx, &lambda) in eigenvalues.iter().enumerate() {
let x_col: Array1<T> = Array1::from_iter((0..n).map(|i| ritz_vectors[[i, ev_idx]]));
let ax = sparse_matvec(matrix, &x_col)?;
let mut res_norm_sq = T::sparse_zero();
for i in 0..n {
let ri = ax[i] - lambda * x_col[i];
res_norm_sq = res_norm_sq + ri * ri;
}
residuals[ev_idx] = res_norm_sq.sqrt();
}
Ok(EigenResult {
eigenvalues: Array1::from_vec(eigenvalues),
eigenvectors: Some(ritz_vectors),
iterations: total_iters,
residuals,
converged,
})
}
pub fn iram_shift_invert<T, S>(
matrix: &S,
sigma: T,
config: &ArnoldiConfig,
) -> SparseResult<EigenResult<T>>
where
T: Float
+ SparseElement
+ Debug
+ Copy
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>
+ std::iter::Sum
+ scirs2_core::ndarray::ScalarOperand
+ 'static,
S: SparseArray<T>,
{
let (n, m_cols) = matrix.shape();
if n != m_cols {
return Err(SparseError::ValueError("Matrix must be square".to_string()));
}
let mut shifted = Array2::zeros((n, n));
for i in 0..n {
for j in 0..n {
shifted[[i, j]] = matrix.get(i, j);
}
shifted[[i, i]] = shifted[[i, i]] - sigma;
}
let lu = dense_lu_factorize(&shifted)?;
let k = config.num_eigenvalues;
let m = config.arnoldi_size.min(n);
let tol = T::from(config.tol).unwrap_or(T::epsilon());
if k == 0 || k >= m {
return Err(SparseError::ValueError(
"Invalid configuration for shift-invert".to_string(),
));
}
let mut v0 = Array1::zeros(n);
for i in 0..n {
v0[i] = T::sparse_one() / T::from(n).unwrap_or(T::sparse_one());
}
let norm_v0 = vector_norm(&v0);
if norm_v0 < tol {
return Err(SparseError::ValueError(
"Initial vector has zero norm".to_string(),
));
}
v0 = v0 / norm_v0;
let mut v_basis: Vec<Array1<T>> = Vec::with_capacity(m + 1);
let mut h_matrix_si = Array2::zeros((m + 1, m));
v_basis.push(v0);
let mut actual_m = 0;
for j in 0..m {
let w = dense_lu_solve(&lu, &v_basis[j])?;
let mut w_orth = w;
for _pass in 0..2 {
for i in 0..=j {
let h_ij: T = v_basis[i]
.iter()
.zip(w_orth.iter())
.map(|(&vi, &wi)| vi * wi)
.sum();
if _pass == 0 {
h_matrix_si[[i, j]] = h_ij;
} else {
h_matrix_si[[i, j]] = h_matrix_si[[i, j]] + h_ij;
}
for idx in 0..n {
w_orth[idx] = w_orth[idx] - h_ij * v_basis[i][idx];
}
}
}
let beta = vector_norm(&w_orth);
h_matrix_si[[j + 1, j]] = beta;
if beta < tol {
actual_m = j + 1;
break;
}
let v_next = w_orth / beta;
v_basis.push(v_next);
actual_m = j + 1;
}
let h_sub = h_matrix_si
.slice(scirs2_core::ndarray::s![..actual_m, ..actual_m])
.to_owned();
let inv_eigs = qr_eigenvalues(&h_sub)?;
let mut eigenvalues: Vec<T> = Vec::new();
for &e in &inv_eigs {
if e.abs() > tol {
eigenvalues.push(sigma + T::sparse_one() / e);
}
}
eigenvalues.sort_by(|a, b| {
let da = (*a - sigma).abs();
let db = (*b - sigma).abs();
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
});
let num_converged = k.min(eigenvalues.len());
eigenvalues.truncate(num_converged);
let h_eigvecs = qr_eigenvectors(&h_sub, &inv_eigs)?;
let mut ritz_vectors = Array2::zeros((n, num_converged));
let sorted_inv_indices = sort_eigenvalues(&inv_eigs, "LM");
for (ev_idx, &sort_idx) in sorted_inv_indices.iter().take(num_converged).enumerate() {
for i in 0..n {
let mut sum = T::sparse_zero();
for j in 0..actual_m.min(v_basis.len()) {
if sort_idx < h_eigvecs.ncols() {
sum = sum + h_eigvecs[[j, sort_idx]] * v_basis[j][i];
}
}
ritz_vectors[[i, ev_idx]] = sum;
}
let mut col_norm = T::sparse_zero();
for i in 0..n {
col_norm = col_norm + ritz_vectors[[i, ev_idx]] * ritz_vectors[[i, ev_idx]];
}
col_norm = col_norm.sqrt();
if col_norm > tol {
for i in 0..n {
ritz_vectors[[i, ev_idx]] = ritz_vectors[[i, ev_idx]] / col_norm;
}
}
}
let mut residuals = Array1::zeros(num_converged);
for (ev_idx, &lambda) in eigenvalues.iter().enumerate() {
let x_col: Array1<T> = Array1::from_iter((0..n).map(|i| ritz_vectors[[i, ev_idx]]));
let ax = sparse_matvec(matrix, &x_col)?;
let mut res_norm_sq = T::sparse_zero();
for i in 0..n {
let ri = ax[i] - lambda * x_col[i];
res_norm_sq = res_norm_sq + ri * ri;
}
residuals[ev_idx] = res_norm_sq.sqrt();
}
Ok(EigenResult {
eigenvalues: Array1::from_vec(eigenvalues),
eigenvectors: Some(ritz_vectors),
iterations: 1,
residuals,
converged: true,
})
}
fn sparse_matvec<T, S>(matrix: &S, v: &Array1<T>) -> SparseResult<Array1<T>>
where
T: Float + SparseElement + Debug + Copy + std::iter::Sum + 'static,
S: SparseArray<T>,
{
matrix.dot_vector(&v.view())
}
fn vector_norm<T>(v: &Array1<T>) -> T
where
T: Float + SparseElement + Copy + std::iter::Sum,
{
let sum_sq: T = v.iter().map(|&x| x * x).sum();
sum_sq.sqrt()
}
fn sort_eigenvalues<T>(eigenvalues: &[T], which: &str) -> Vec<usize>
where
T: Float + SparseElement + Copy,
{
let mut indices: Vec<usize> = (0..eigenvalues.len()).collect();
match which {
"LM" => {
indices.sort_by(|&i, &j| {
eigenvalues[j]
.abs()
.partial_cmp(&eigenvalues[i].abs())
.unwrap_or(std::cmp::Ordering::Equal)
});
}
"SM" => {
indices.sort_by(|&i, &j| {
eigenvalues[i]
.abs()
.partial_cmp(&eigenvalues[j].abs())
.unwrap_or(std::cmp::Ordering::Equal)
});
}
"LR" => {
indices.sort_by(|&i, &j| {
eigenvalues[j]
.partial_cmp(&eigenvalues[i])
.unwrap_or(std::cmp::Ordering::Equal)
});
}
"SR" => {
indices.sort_by(|&i, &j| {
eigenvalues[i]
.partial_cmp(&eigenvalues[j])
.unwrap_or(std::cmp::Ordering::Equal)
});
}
_ => {} }
indices
}
fn qr_eigenvalues<T>(h: &Array2<T>) -> SparseResult<Vec<T>>
where
T: Float + SparseElement + Debug + Copy + 'static,
{
let n = h.nrows().min(h.ncols());
if n == 0 {
return Ok(Vec::new());
}
if n == 1 {
return Ok(vec![h[[0, 0]]]);
}
let mut a = h.clone();
let max_iter = 100 * n;
let tol = T::from(1e-14).unwrap_or(T::epsilon());
let mut p = n;
for _iter in 0..max_iter {
if p <= 1 {
break;
}
let sub = a[[p - 1, p - 2]].abs();
let diag_sum = a[[p - 2, p - 2]].abs() + a[[p - 1, p - 1]].abs();
let threshold = if diag_sum > T::sparse_zero() {
tol * diag_sum
} else {
tol
};
if sub <= threshold {
p -= 1;
continue;
}
let d = (a[[p - 2, p - 2]] - a[[p - 1, p - 1]]) / T::from(2.0).unwrap_or(T::sparse_one());
let shift = a[[p - 1, p - 1]]
- a[[p - 1, p - 2]] * a[[p - 1, p - 2]]
/ (d + d.signum() * (d * d + a[[p - 1, p - 2]] * a[[p - 1, p - 2]]).sqrt());
implicit_qr_step(&mut a, shift, p)?;
}
let eigenvalues: Vec<T> = (0..n).map(|i| a[[i, i]]).collect();
Ok(eigenvalues)
}
fn implicit_qr_step<T>(a: &mut Array2<T>, shift: T, p: usize) -> SparseResult<()>
where
T: Float + SparseElement + Debug + Copy + 'static,
{
let n = a.nrows().min(a.ncols());
if p > n || p < 2 {
return Ok(());
}
for i in 0..p - 1 {
let (c, s) = if i == 0 {
let x = a[[0, 0]] - shift;
let y = a[[1, 0]];
givens_rotation(x, y)
} else {
let x = a[[i, i - 1]];
let y = a[[i + 1, i - 1]];
givens_rotation(x, y)
};
let j_start = if i > 0 { i - 1 } else { 0 };
for j in j_start..n.min(p) {
let a_ij = a[[i, j]];
let a_i1j = a[[i + 1, j]];
a[[i, j]] = c * a_ij + s * a_i1j;
a[[i + 1, j]] = -s * a_ij + c * a_i1j;
}
let j_end = (i + 3).min(p).min(n);
for j in 0..j_end {
let a_ji = a[[j, i]];
let a_ji1 = a[[j, i + 1]];
a[[j, i]] = c * a_ji + s * a_ji1;
a[[j, i + 1]] = -s * a_ji + c * a_ji1;
}
}
Ok(())
}
fn givens_rotation<T>(x: T, y: T) -> (T, T)
where
T: Float + SparseElement + Copy,
{
if SparseElement::is_zero(&y) {
return (T::sparse_one(), T::sparse_zero());
}
if SparseElement::is_zero(&x) {
return (T::sparse_zero(), y.signum());
}
let r = (x * x + y * y).sqrt();
(x / r, y / r)
}
fn qr_eigenvectors<T>(h: &Array2<T>, eigenvalues: &[T]) -> SparseResult<Array2<T>>
where
T: Float
+ SparseElement
+ Debug
+ Copy
+ std::iter::Sum
+ scirs2_core::ndarray::ScalarOperand
+ 'static,
{
let n = h.nrows().min(h.ncols());
let nev = eigenvalues.len();
let mut vectors = Array2::zeros((n, nev));
let tol = T::from(1e-12).unwrap_or(T::epsilon());
for (ev_idx, &lambda) in eigenvalues.iter().enumerate() {
let mut x = Array1::zeros(n);
for i in 0..n {
x[i] = T::sparse_one() / T::from(n).unwrap_or(T::sparse_one());
}
let mut shifted_h = h.clone();
for i in 0..n {
shifted_h[[i, i]] = shifted_h[[i, i]] - lambda;
}
let perturb = tol * (T::sparse_one() + lambda.abs());
for i in 0..n {
shifted_h[[i, i]] = shifted_h[[i, i]] + perturb;
}
for _iter in 0..5 {
let b = x.clone();
x = dense_solve_system(&shifted_h, &b).unwrap_or(b);
let norm: T = x.iter().map(|&v| v * v).sum::<T>().sqrt();
if norm > tol {
x = x / norm;
}
}
for i in 0..n {
vectors[[i, ev_idx]] = x[i];
}
}
Ok(vectors)
}
fn dense_solve_system<T>(a: &Array2<T>, b: &Array1<T>) -> SparseResult<Array1<T>>
where
T: Float + SparseElement + Debug + Copy + 'static,
{
let n = a.nrows();
if n != a.ncols() || n != b.len() {
return Err(SparseError::DimensionMismatch {
expected: n,
found: b.len(),
});
}
let mut aug = Array2::zeros((n, n + 1));
for i in 0..n {
for j in 0..n {
aug[[i, j]] = a[[i, j]];
}
aug[[i, n]] = b[i];
}
for k in 0..n {
let mut max_val = aug[[k, k]].abs();
let mut max_row = k;
for i in (k + 1)..n {
if aug[[i, k]].abs() > max_val {
max_val = aug[[i, k]].abs();
max_row = i;
}
}
if max_row != k {
for j in 0..=n {
let tmp = aug[[k, j]];
aug[[k, j]] = aug[[max_row, j]];
aug[[max_row, j]] = tmp;
}
}
let pivot = aug[[k, k]];
let tol = T::from(1e-30).unwrap_or(T::epsilon());
if pivot.abs() < tol {
continue; }
for i in (k + 1)..n {
let factor = aug[[i, k]] / pivot;
for j in k..=n {
aug[[i, j]] = aug[[i, j]] - factor * aug[[k, j]];
}
}
}
let mut x = Array1::zeros(n);
for i in (0..n).rev() {
let mut sum = aug[[i, n]];
for j in (i + 1)..n {
sum = sum - aug[[i, j]] * x[j];
}
let diag = aug[[i, i]];
let tol = T::from(1e-30).unwrap_or(T::epsilon());
if diag.abs() > tol {
x[i] = sum / diag;
}
}
Ok(x)
}
fn dense_lu_factorize<T>(a: &Array2<T>) -> SparseResult<(Array2<T>, Vec<usize>)>
where
T: Float + SparseElement + Debug + Copy + 'static,
{
let n = a.nrows();
let mut lu = a.clone();
let mut perm: Vec<usize> = (0..n).collect();
for k in 0..n {
let mut max_val = lu[[k, k]].abs();
let mut max_row = k;
for i in (k + 1)..n {
if lu[[i, k]].abs() > max_val {
max_val = lu[[i, k]].abs();
max_row = i;
}
}
if max_row != k {
perm.swap(k, max_row);
for j in 0..n {
let tmp = lu[[k, j]];
lu[[k, j]] = lu[[max_row, j]];
lu[[max_row, j]] = tmp;
}
}
let pivot = lu[[k, k]];
let tol = T::from(1e-14).unwrap_or(T::epsilon());
if pivot.abs() < tol {
return Err(SparseError::SingularMatrix(format!(
"Zero pivot at row {k} in LU factorization"
)));
}
for i in (k + 1)..n {
lu[[i, k]] = lu[[i, k]] / pivot;
for j in (k + 1)..n {
let lik = lu[[i, k]];
lu[[i, j]] = lu[[i, j]] - lik * lu[[k, j]];
}
}
}
Ok((lu, perm))
}
fn dense_lu_solve<T>(lu_perm: &(Array2<T>, Vec<usize>), b: &Array1<T>) -> SparseResult<Array1<T>>
where
T: Float + SparseElement + Debug + Copy + 'static,
{
let (lu, perm) = lu_perm;
let n = lu.nrows();
let mut pb = Array1::zeros(n);
for i in 0..n {
pb[i] = b[perm[i]];
}
let mut y = pb;
for i in 1..n {
for j in 0..i {
let lij = lu[[i, j]];
y[i] = y[i] - lij * y[j];
}
}
let mut x = y;
for i in (0..n).rev() {
for j in (i + 1)..n {
x[i] = x[i] - lu[[i, j]] * x[j];
}
let diag = lu[[i, i]];
let tol = T::from(1e-14).unwrap_or(T::epsilon());
if diag.abs() < tol {
return Err(SparseError::SingularMatrix(
"Zero diagonal in back substitution".to_string(),
));
}
x[i] = x[i] / diag;
}
Ok(x)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::csr_array::CsrArray;
use approx::assert_relative_eq;
fn make_symmetric_test_matrix() -> CsrArray<f64> {
CsrArray::from_triplets(
&[0, 0, 1, 1, 1, 2, 2],
&[0, 1, 0, 1, 2, 1, 2],
&[4.0, 1.0, 1.0, 3.0, 1.0, 1.0, 2.0],
(3, 3),
false,
)
.expect("matrix creation")
}
fn make_nonsymmetric_test_matrix() -> CsrArray<f64> {
CsrArray::from_triplets(
&[0, 0, 1, 1, 2],
&[0, 1, 1, 2, 2],
&[2.0, 1.0, 3.0, 1.0, 1.0],
(3, 3),
false,
)
.expect("matrix creation")
}
#[test]
fn test_iram_symmetric() {
let matrix = make_symmetric_test_matrix();
let config = ArnoldiConfig::new(2)
.with_arnoldi_size(3)
.with_which("LM")
.with_tol(1e-8);
let result = iram(&matrix, &config, None).expect("IRAM should succeed");
assert!(result.converged);
assert!(!result.eigenvalues.is_empty());
let max_eig = result
.eigenvalues
.iter()
.max_by(|a, b| {
a.abs()
.partial_cmp(&b.abs())
.unwrap_or(std::cmp::Ordering::Equal)
})
.copied()
.unwrap_or(0.0);
assert!(max_eig > 3.0 && max_eig < 6.0);
}
#[test]
fn test_iram_nonsymmetric() {
let matrix = make_nonsymmetric_test_matrix();
let config = ArnoldiConfig::new(2)
.with_arnoldi_size(3)
.with_which("LM")
.with_tol(1e-8);
let result = iram(&matrix, &config, None).expect("IRAM should succeed");
assert!(result.converged);
let eigs = &result.eigenvalues;
assert!(!eigs.is_empty());
let max_eig = eigs
.iter()
.max_by(|a, b| {
a.abs()
.partial_cmp(&b.abs())
.unwrap_or(std::cmp::Ordering::Equal)
})
.copied()
.unwrap_or(0.0);
assert_relative_eq!(max_eig, 3.0, epsilon = 0.5);
}
#[test]
fn test_iram_eigenvectors() {
let matrix = make_symmetric_test_matrix();
let config = ArnoldiConfig::new(1)
.with_arnoldi_size(3)
.with_which("LM")
.with_tol(1e-8);
let result = iram(&matrix, &config, None).expect("IRAM");
assert!(result.eigenvectors.is_some());
let vecs = result.eigenvectors.as_ref().expect("should have vecs");
assert!(vecs.ncols() >= 1);
let mut norm_sq = 0.0f64;
for i in 0..3 {
norm_sq += vecs[[i, 0]] * vecs[[i, 0]];
}
assert_relative_eq!(norm_sq.sqrt(), 1.0, epsilon = 0.1);
}
#[test]
fn test_iram_shift_invert() {
let matrix = make_symmetric_test_matrix();
let config = ArnoldiConfig::new(1)
.with_arnoldi_size(3)
.with_which("LM")
.with_tol(1e-6);
let result = iram_shift_invert(&matrix, 2.0, &config).expect("shift-invert should succeed");
assert!(!result.eigenvalues.is_empty());
let closest = result.eigenvalues[0];
assert!((closest - 2.0).abs() < 2.0);
}
#[test]
fn test_iram_diagonal_matrix() {
let matrix =
CsrArray::from_triplets(&[0, 1, 2], &[0, 1, 2], &[1.0, 3.0, 5.0], (3, 3), false)
.expect("matrix");
let config = ArnoldiConfig::new(2)
.with_arnoldi_size(3)
.with_which("LM")
.with_tol(1e-8);
let result = iram(&matrix, &config, None).expect("IRAM");
assert!(result.converged);
let eigs = &result.eigenvalues;
assert!(!eigs.is_empty());
let max_eig = eigs
.iter()
.max_by(|a, b| {
a.abs()
.partial_cmp(&b.abs())
.unwrap_or(std::cmp::Ordering::Equal)
})
.copied()
.unwrap_or(0.0);
assert_relative_eq!(max_eig, 5.0, epsilon = 0.5);
}
#[test]
fn test_iram_residuals() {
let matrix = make_symmetric_test_matrix();
let config = ArnoldiConfig::new(2).with_arnoldi_size(3).with_which("LM");
let result = iram(&matrix, &config, None).expect("IRAM");
assert_eq!(result.residuals.len(), result.eigenvalues.len());
for &r in result.residuals.iter() {
assert!(r >= 0.0);
}
}
#[test]
fn test_qr_eigenvalues_2x2() {
let h = Array2::from_shape_vec((2, 2), vec![3.0, 1.0, 0.0, 2.0]).expect("h");
let eigs = qr_eigenvalues(&h).expect("qr_eigs");
assert_eq!(eigs.len(), 2);
let mut sorted_eigs = eigs.clone();
sorted_eigs.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
assert_relative_eq!(sorted_eigs[0], 3.0, epsilon = 1e-10);
assert_relative_eq!(sorted_eigs[1], 2.0, epsilon = 1e-10);
}
#[test]
fn test_qr_eigenvalues_3x3_diagonal() {
let h = Array2::from_shape_vec((3, 3), vec![5.0, 0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 1.0])
.expect("h");
let eigs = qr_eigenvalues(&h).expect("qr_eigs");
assert_eq!(eigs.len(), 3);
let mut sorted_eigs = eigs.clone();
sorted_eigs.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
assert_relative_eq!(sorted_eigs[0], 5.0, epsilon = 1e-10);
assert_relative_eq!(sorted_eigs[1], 3.0, epsilon = 1e-10);
assert_relative_eq!(sorted_eigs[2], 1.0, epsilon = 1e-10);
}
#[test]
fn test_arnoldi_config_builder() {
let config = ArnoldiConfig::new(5)
.with_arnoldi_size(30)
.with_tol(1e-12)
.with_max_restarts(500)
.with_which("SR");
assert_eq!(config.num_eigenvalues, 5);
assert_eq!(config.arnoldi_size, 30);
assert_eq!(config.tol, 1e-12);
assert_eq!(config.max_restarts, 500);
assert_eq!(config.which, "SR");
}
}