use crate::error::{SparseError, SparseResult};
use crate::sym_csr::SymCsrMatrix;
use crate::sym_ops::sym_csr_matvec;
use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
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 PowerIterationOptions {
pub max_iter: usize,
pub tol: f64,
pub normalize: bool,
}
impl Default for PowerIterationOptions {
fn default() -> Self {
Self {
max_iter: 100,
tol: 1e-8,
normalize: true,
}
}
}
#[derive(Debug, Clone)]
pub struct EigenResult<T>
where
T: Float + Debug + Copy,
{
pub eigenvalues: Array1<T>,
pub eigenvectors: Option<Array2<T>>,
pub iterations: usize,
pub residuals: Array1<T>,
pub converged: bool,
}
#[allow(dead_code)]
pub fn power_iteration<T>(
matrix: &SymCsrMatrix<T>,
options: &PowerIterationOptions,
initial_guess: Option<ArrayView1<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::simd_ops::SimdUnifiedOps
+ Send
+ Sync
+ 'static,
{
let (n, _) = matrix.shape();
let mut x = match initial_guess {
Some(v) => {
if v.len() != n {
return Err(SparseError::DimensionMismatch {
expected: n,
found: v.len(),
});
}
let mut x_arr = Array1::zeros(n);
for i in 0..n {
x_arr[i] = v[i];
}
x_arr
}
None => {
let mut x_arr = Array1::zeros(n);
x_arr[0] = T::sparse_one(); x_arr
}
};
if options.normalize {
let norm = (x.iter().map(|&v| v * v).sum::<T>()).sqrt();
if !SparseElement::is_zero(&norm) {
for i in 0..n {
x[i] = x[i] / norm;
}
}
}
let mut lambda = T::sparse_zero();
let mut prev_lambda = T::sparse_zero();
let mut converged = false;
let mut iter = 0;
while iter < options.max_iter {
let y = sym_csr_matvec(matrix, &x.view())?;
let rayleigh_numerator = x.iter().zip(y.iter()).map(|(&xi, &yi)| xi * yi).sum::<T>();
if options.normalize {
lambda = rayleigh_numerator;
let diff = (lambda - prev_lambda).abs();
if diff < T::from(options.tol).expect("Operation failed") {
converged = true;
break;
}
let norm = (y.iter().map(|&v| v * v).sum::<T>()).sqrt();
if !SparseElement::is_zero(&norm) {
for i in 0..n {
x[i] = y[i] / norm;
}
}
} else {
x = y;
let norm_x = (x.iter().map(|&v| v * v).sum::<T>()).sqrt();
if !SparseElement::is_zero(&norm_x) {
lambda = rayleigh_numerator / (norm_x * norm_x);
}
let diff = (lambda - prev_lambda).abs();
if diff < T::from(options.tol).expect("Operation failed") {
converged = true;
break;
}
}
prev_lambda = lambda;
iter += 1;
}
let ax = sym_csr_matvec(matrix, &x.view())?;
let mut residual = Array1::zeros(n);
for i in 0..n {
residual[i] = ax[i] - lambda * x[i];
}
let residual_norm = (residual.iter().map(|&v| v * v).sum::<T>()).sqrt();
let eigenvectors = {
let mut vecs = Array2::zeros((n, 1));
for i in 0..n {
vecs[[i, 0]] = x[i];
}
Some(vecs)
};
let result = EigenResult {
eigenvalues: Array1::from_vec(vec![lambda]),
eigenvectors,
iterations: iter,
residuals: Array1::from_vec(vec![residual_norm]),
converged,
};
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sym_csr::SymCsrMatrix;
use scirs2_core::ndarray::Array1;
#[test]
fn test_power_iteration_simple() {
let data = vec![2.0, 1.0, 2.0];
let indices = vec![0, 0, 1]; let indptr = vec![0, 1, 3]; let matrix = SymCsrMatrix::new(data, indptr, indices, (2, 2)).expect("Operation failed");
let options = PowerIterationOptions::default();
let result = power_iteration(&matrix, &options, None).expect("Operation failed");
assert!(result.converged);
assert_eq!(result.eigenvalues.len(), 1);
assert!((result.eigenvalues[0] - 3.0).abs() < 1e-6);
}
#[test]
fn test_power_iteration_with_initial_guess() {
let data = vec![4.0, 1.0, 3.0];
let indptr = vec![0, 1, 3];
let indices = vec![0, 0, 1];
let matrix = SymCsrMatrix::new(data, indptr, indices, (2, 2)).expect("Operation failed");
let initial_guess = Array1::from_vec(vec![1.0, 1.0]);
let options = PowerIterationOptions::default();
let result = power_iteration(&matrix, &options, Some(initial_guess.view()))
.expect("Operation failed");
assert!(result.converged);
assert_eq!(result.eigenvalues.len(), 1);
}
#[test]
fn test_power_iteration_convergence() {
let data = vec![5.0, 2.0, 4.0];
let indptr = vec![0, 1, 3];
let indices = vec![0, 0, 1];
let matrix = SymCsrMatrix::new(data, indptr, indices, (2, 2)).expect("Operation failed");
let options = PowerIterationOptions {
max_iter: 50,
tol: 1e-10,
normalize: true,
};
let result = power_iteration(&matrix, &options, None).expect("Operation failed");
assert!(result.converged);
assert!(result.iterations <= 50);
assert!(result.residuals[0] < 1e-4);
}
}