use super::ConvergenceError;
use crate::algorithm::matrix::{abs, mul_vector};
use crate::algorithm::vector::{dot, norm};
use crate::scalar::Scalar;
use crate::storage::Storage;
pub(super) struct Slice<'a, T> {
pub(super) data: &'a [T],
}
impl<T> Storage for Slice<'_, T> {
type Item = T;
fn len(&self) -> usize {
self.data.len()
}
fn get(&self, index: usize) -> Option<&Self::Item> {
self.data.get(index)
}
}
pub fn power_iteration<S, V, T>(
a: &S,
n: usize,
v0: &V,
max_iter: usize,
tol: T,
out_eigenvector: &mut [T],
scratch: &mut [T],
) -> Result<T, ConvergenceError>
where
S: Storage<Item = T>,
V: Storage<Item = T>,
T: Scalar + PartialOrd,
{
if a.len() != n * n || v0.len() != n || out_eigenvector.len() != n || scratch.len() != n {
return Err(ConvergenceError::DimensionMismatch);
}
for (i, slot) in out_eigenvector.iter_mut().enumerate() {
let Some(&x) = v0.get(i) else {
return Err(ConvergenceError::DimensionMismatch);
};
*slot = x;
}
normalize(out_eigenvector)?;
let mut prev_eigenvalue: Option<T> = None;
for _ in 0..max_iter {
if mul_vector(
a,
n,
n,
&Slice {
data: &*out_eigenvector,
},
scratch,
)
.is_err()
{
return Err(ConvergenceError::DimensionMismatch);
}
let Ok(eigenvalue) = dot(
&Slice {
data: &*out_eigenvector,
},
&Slice { data: &*scratch },
) else {
return Err(ConvergenceError::DimensionMismatch);
};
let mut residual_sq = T::zero();
for (&w_i, &v_i) in scratch.iter().zip(out_eigenvector.iter()) {
let r = w_i.sub(eigenvalue.mul(v_i));
residual_sq = residual_sq.add(r.mul(r));
}
let residual = residual_sq.sqrt();
for (slot, &w_i) in out_eigenvector.iter_mut().zip(scratch.iter()) {
*slot = w_i;
}
normalize(out_eigenvector)?;
let scale = abs(eigenvalue);
let eigenvalue_stable = match prev_eigenvalue {
Some(prev) => abs(eigenvalue.sub(prev)) <= tol.mul(scale),
None => false,
};
if eigenvalue_stable && residual <= tol.mul(scale) {
return Ok(eigenvalue);
}
prev_eigenvalue = Some(eigenvalue);
}
Err(ConvergenceError::MaxIterationsExceeded)
}
pub(super) fn normalize<T: Scalar + PartialOrd>(v: &mut [T]) -> Result<(), ConvergenceError> {
let length = norm(&Slice { data: &*v });
if length > T::zero() {
let inv = T::one().div(length);
for slot in v.iter_mut() {
*slot = slot.mul(inv);
}
Ok(())
} else if length == T::zero() {
Err(ConvergenceError::ZeroVector)
} else {
Err(ConvergenceError::NonFinite)
}
}
#[cfg(test)]
mod tests {
use super::power_iteration;
use crate::krylov::ConvergenceError;
use crate::storage::StaticStorage;
fn assert_close(actual: f64, expected: f64, tol: f64) {
assert!(
(actual - expected).abs() < tol,
"expected {expected}, got {actual}"
);
}
#[test]
fn symmetric_2x2_dominant_eigenpair() {
let a = StaticStorage::new([2.0, 1.0, 1.0, 2.0]);
let v0 = StaticStorage::new([1.0, 0.0]);
let mut eigenvector = [0.0; 2];
let mut scratch = [0.0; 2];
let eigenvalue =
power_iteration(&a, 2, &v0, 500, 1e-12, &mut eigenvector, &mut scratch).unwrap();
assert_close(eigenvalue, 3.0, 1e-9);
let inv_sqrt2 = 1.0 / core::f64::consts::SQRT_2;
assert_close(eigenvector[0].abs(), inv_sqrt2, 1e-6);
assert_close(eigenvector[1].abs(), inv_sqrt2, 1e-6);
assert!(eigenvector[0] * eigenvector[1] > 0.0);
}
#[test]
fn one_by_one_matrix() {
let a = StaticStorage::new([-4.0]);
let v0 = StaticStorage::new([2.0]);
let mut eigenvector = [0.0; 1];
let mut scratch = [0.0; 1];
let eigenvalue =
power_iteration(&a, 1, &v0, 10, 1e-12, &mut eigenvector, &mut scratch).unwrap();
assert_close(eigenvalue, -4.0, 1e-12);
assert_close(eigenvector[0].abs(), 1.0, 1e-12);
}
#[test]
fn negative_dominant_eigenvalue() {
let a = StaticStorage::new([-3.0, 0.0, 0.0, 1.0]);
let v0 = StaticStorage::new([1.0, 1.0]);
let mut eigenvector = [0.0; 2];
let mut scratch = [0.0; 2];
let eigenvalue =
power_iteration(&a, 2, &v0, 500, 1e-12, &mut eigenvector, &mut scratch).unwrap();
assert_close(eigenvalue, -3.0, 1e-9);
assert_close(eigenvector[0].abs(), 1.0, 1e-6);
assert_close(eigenvector[1].abs(), 0.0, 1e-6);
}
#[test]
fn repeated_dominant_eigenvalue_settles_in_the_eigenspace() {
let a = StaticStorage::new([1.0, 0.0, 0.0, 1.0]);
let v0 = StaticStorage::new([3.0, 4.0]);
let mut eigenvector = [0.0; 2];
let mut scratch = [0.0; 2];
let eigenvalue =
power_iteration(&a, 2, &v0, 10, 1e-12, &mut eigenvector, &mut scratch).unwrap();
assert_close(eigenvalue, 1.0, 1e-12);
assert_close(eigenvector[0], 0.6, 1e-12);
assert_close(eigenvector[1], 0.8, 1e-12);
}
#[test]
fn equal_magnitude_opposite_sign_spectrum_never_converges() {
let a = StaticStorage::new([0.0, 1.0, 1.0, 0.0]);
let v0 = StaticStorage::new([1.0, 0.0]);
let mut eigenvector = [0.0; 2];
let mut scratch = [0.0; 2];
let result = power_iteration(&a, 2, &v0, 200, 1e-12, &mut eigenvector, &mut scratch);
assert_eq!(result, Err(ConvergenceError::MaxIterationsExceeded));
}
#[test]
fn degenerate_spectrum_still_converges_from_an_exact_eigenvector() {
let a = StaticStorage::new([0.0, 1.0, 1.0, 0.0]);
let v0 = StaticStorage::new([1.0, 1.0]);
let mut eigenvector = [0.0; 2];
let mut scratch = [0.0; 2];
let eigenvalue =
power_iteration(&a, 2, &v0, 10, 1e-12, &mut eigenvector, &mut scratch).unwrap();
assert_close(eigenvalue, 1.0, 1e-12);
}
#[test]
fn v0_orthogonal_to_dominant_eigenvector_finds_the_reachable_subspace() {
let a = StaticStorage::new([2.0, 0.0, 0.0, 1.0]);
let v0 = StaticStorage::new([0.0, 1.0]);
let mut eigenvector = [0.0; 2];
let mut scratch = [0.0; 2];
let eigenvalue =
power_iteration(&a, 2, &v0, 10, 1e-12, &mut eigenvector, &mut scratch).unwrap();
assert_close(eigenvalue, 1.0, 1e-12);
assert_close(eigenvector[0], 0.0, 1e-12);
assert_close(eigenvector[1].abs(), 1.0, 1e-12);
}
#[test]
fn returned_eigenvector_satisfies_the_eigenpair_equation() {
let a = [3.0, 2.0, 1.0, 2.0];
let v0 = StaticStorage::new([1.0, 0.0]);
let mut eigenvector = [0.0; 2];
let mut scratch = [0.0; 2];
let eigenvalue = power_iteration(
&StaticStorage::new(a),
2,
&v0,
500,
1e-12,
&mut eigenvector,
&mut scratch,
)
.unwrap();
assert_close(eigenvalue, 4.0, 1e-9);
for row in 0..2 {
let av = a[row * 2] * eigenvector[0] + a[row * 2 + 1] * eigenvector[1];
assert_close(av, eigenvalue * eigenvector[row], 1e-8);
}
}
#[test]
fn zero_initial_vector_is_an_error_not_a_panic() {
let a = StaticStorage::new([2.0, 0.0, 0.0, 1.0]);
let v0 = StaticStorage::new([0.0, 0.0]);
let mut eigenvector = [0.0; 2];
let mut scratch = [0.0; 2];
let result = power_iteration(&a, 2, &v0, 10, 1e-12, &mut eigenvector, &mut scratch);
assert_eq!(result, Err(ConvergenceError::ZeroVector));
}
#[test]
fn iterate_mapped_into_the_null_space_is_an_error_not_a_panic() {
let a = StaticStorage::new([0.0, 0.0, 0.0, 0.0]);
let v0 = StaticStorage::new([1.0, 1.0]);
let mut eigenvector = [0.0; 2];
let mut scratch = [0.0; 2];
let result = power_iteration(&a, 2, &v0, 10, 1e-12, &mut eigenvector, &mut scratch);
assert_eq!(result, Err(ConvergenceError::ZeroVector));
}
#[test]
fn non_finite_initial_vector_is_a_distinct_error_from_zero_vector() {
let a = StaticStorage::new([2.0, 0.0, 0.0, 1.0]);
let v0 = StaticStorage::new([f64::NAN, 0.0]);
let mut eigenvector = [0.0; 2];
let mut scratch = [0.0; 2];
let result = power_iteration(&a, 2, &v0, 10, 1e-12, &mut eigenvector, &mut scratch);
assert_eq!(result, Err(ConvergenceError::NonFinite));
}
#[test]
fn iterate_poisoned_by_a_non_finite_matrix_entry_is_a_distinct_error_from_zero_vector() {
let a = StaticStorage::new([f64::NAN, 0.0, 0.0, 1.0]);
let v0 = StaticStorage::new([1.0, 1.0]);
let mut eigenvector = [0.0; 2];
let mut scratch = [0.0; 2];
let result = power_iteration(&a, 2, &v0, 10, 1e-12, &mut eigenvector, &mut scratch);
assert_eq!(result, Err(ConvergenceError::NonFinite));
}
#[test]
fn too_small_iteration_budget_is_an_error_not_a_panic() {
let a = StaticStorage::new([2.0, 0.0, 0.0, 1.0]);
let v0 = StaticStorage::new([1.0, 0.0]);
let mut eigenvector = [0.0; 2];
let mut scratch = [0.0; 2];
for max_iter in [0, 1] {
let result =
power_iteration(&a, 2, &v0, max_iter, 1e-12, &mut eigenvector, &mut scratch);
assert_eq!(result, Err(ConvergenceError::MaxIterationsExceeded));
}
}
#[test]
fn mismatched_dimensions_are_an_error_not_a_panic() {
let a = StaticStorage::new([2.0, 0.0, 0.0, 1.0]);
let v0 = StaticStorage::new([1.0, 0.0]);
let mut eigenvector3 = [0.0; 3];
let mut scratch3 = [0.0; 3];
let v0_3 = StaticStorage::new([1.0, 0.0, 0.0]);
assert_eq!(
power_iteration(&a, 3, &v0_3, 10, 1e-12, &mut eigenvector3, &mut scratch3),
Err(ConvergenceError::DimensionMismatch)
);
let v0_short = StaticStorage::new([1.0]);
let mut eigenvector = [0.0; 2];
let mut scratch = [0.0; 2];
assert_eq!(
power_iteration(&a, 2, &v0_short, 10, 1e-12, &mut eigenvector, &mut scratch),
Err(ConvergenceError::DimensionMismatch)
);
let mut eigenvector_short = [0.0; 1];
assert_eq!(
power_iteration(&a, 2, &v0, 10, 1e-12, &mut eigenvector_short, &mut scratch),
Err(ConvergenceError::DimensionMismatch)
);
let mut scratch_short = [0.0; 1];
assert_eq!(
power_iteration(&a, 2, &v0, 10, 1e-12, &mut eigenvector, &mut scratch_short),
Err(ConvergenceError::DimensionMismatch)
);
}
}