use rustebra::krylov::{ConvergenceError, inverse_power_iteration, power_iteration};
use rustebra::storage::StaticStorage;
use crate::common::{
ALGORITHM_TOL, ASSERTION_TOL, SINGULAR_TOL, approx_eq_eigenvector, fixed_similarity_3,
max_iter_for,
};
fn residual_norm(a: &[f64], n: usize, eigenvalue: f64, eigenvector: &[f64]) -> f64 {
let mut sum = 0.0;
for r in 0..n {
let av: f64 = (0..n).map(|c| a[r * n + c] * eigenvector[c]).sum();
let diff = av - eigenvalue * eigenvector[r];
sum += diff * diff;
}
sum.sqrt()
}
#[test]
fn power_iteration_on_a_1x1_matrix() {
let a = StaticStorage::new([-7.5]);
let v0 = StaticStorage::new([3.0]);
let mut eigenvector = [0.0; 1];
let mut scratch = [0.0; 1];
let eigenvalue = power_iteration(
&a,
1,
&v0,
10,
ALGORITHM_TOL,
&mut eigenvector,
&mut scratch,
)
.unwrap();
assert!((eigenvalue + 7.5).abs() <= ASSERTION_TOL * 7.5);
assert!((eigenvector[0].abs() - 1.0).abs() <= ASSERTION_TOL);
}
#[test]
fn inverse_power_iteration_on_a_1x1_matrix() {
let a = StaticStorage::new([3.0]);
let v0 = StaticStorage::new([-2.0]);
let mut eigenvector = [0.0; 1];
let mut factor = [0.0; 1];
let mut pivots = [0_usize; 1];
let mut scratch = [0.0; 1];
let eigenvalue = inverse_power_iteration(
&a,
1,
&v0,
0.0,
10,
ALGORITHM_TOL,
SINGULAR_TOL,
&mut eigenvector,
&mut factor,
&mut pivots,
&mut scratch,
)
.unwrap();
assert!((eigenvalue - 3.0).abs() <= ASSERTION_TOL * 3.0);
assert!((eigenvector[0].abs() - 1.0).abs() <= ASSERTION_TOL);
}
#[test]
fn repeated_dominant_eigenvalue_converges_to_a_vector_in_the_eigenspace() {
let a = fixed_similarity_3([5.0, 5.0, 2.0]);
let v0 = StaticStorage::new([1.0, 0.4, -0.3]);
let mut eigenvector = [0.0; 3];
let mut scratch = [0.0; 3];
let eigenvalue = power_iteration(
&StaticStorage::new(a),
3,
&v0,
500,
ALGORITHM_TOL,
&mut eigenvector,
&mut scratch,
)
.unwrap();
assert!(
(eigenvalue - 5.0).abs() <= ASSERTION_TOL * 5.0,
"expected the repeated dominant eigenvalue 5, got {eigenvalue}"
);
assert!(
residual_norm(&a, 3, eigenvalue, &eigenvector) <= ASSERTION_TOL * 5.0,
"returned vector does not satisfy A v = λ v"
);
}
#[test]
fn dominant_complex_conjugate_pair_exhausts_the_iteration_budget() {
let a = StaticStorage::new([
0.0, -2.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 1.0,
]);
let v0 = StaticStorage::new([1.0, 1.0, 1.0]);
let mut eigenvector = [0.0; 3];
let mut scratch = [0.0; 3];
let result = power_iteration(
&a,
3,
&v0,
300,
ALGORITHM_TOL,
&mut eigenvector,
&mut scratch,
);
assert_eq!(result, Err(ConvergenceError::MaxIterationsExceeded));
}
#[test]
fn shift_exactly_at_an_eigenvalue_is_a_singular_shift_error() {
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 factor = [0.0; 4];
let mut pivots = [0_usize; 2];
let mut scratch = [0.0; 2];
let result = inverse_power_iteration(
&a,
2,
&v0,
3.0,
100,
ALGORITHM_TOL,
SINGULAR_TOL,
&mut eigenvector,
&mut factor,
&mut pivots,
&mut scratch,
);
assert_eq!(result, Err(ConvergenceError::SingularShift));
}
#[test]
fn shift_far_from_the_spectrum_still_selects_the_nearest_eigenvalue() {
let a = StaticStorage::new([2.0, 0.0, 0.0, 1.0]);
let v0 = StaticStorage::new([1.0, 1.0]);
let mut eigenvector = [0.0; 2];
let mut factor = [0.0; 4];
let mut pivots = [0_usize; 2];
let mut scratch = [0.0; 2];
let eigenvalue = inverse_power_iteration(
&a,
2,
&v0,
-50.0,
max_iter_for(51.0 / 52.0, ALGORITHM_TOL),
ALGORITHM_TOL,
SINGULAR_TOL,
&mut eigenvector,
&mut factor,
&mut pivots,
&mut scratch,
)
.unwrap();
assert!((eigenvalue - 1.0).abs() <= ASSERTION_TOL);
assert!(approx_eq_eigenvector(
&eigenvector,
&[0.0, 1.0],
ASSERTION_TOL
));
}
#[test]
fn non_finite_matrix_entries_are_an_error_not_a_panic_or_a_spurious_ok() {
for poison in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
let a = StaticStorage::new([2.0, poison, 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,
100,
ALGORITHM_TOL,
&mut eigenvector,
&mut scratch,
);
assert!(
result.is_err(),
"power_iteration accepted a {poison} entry: {result:?}"
);
let mut factor = [0.0; 4];
let mut pivots = [0_usize; 2];
let result = inverse_power_iteration(
&a,
2,
&v0,
0.5,
100,
ALGORITHM_TOL,
SINGULAR_TOL,
&mut eigenvector,
&mut factor,
&mut pivots,
&mut scratch,
);
assert!(
result.is_err(),
"inverse_power_iteration accepted a {poison} entry: {result:?}"
);
}
}
#[test]
fn non_finite_initial_vector_is_a_non_finite_error() {
for poison in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
let a = StaticStorage::new([2.0, 0.0, 0.0, 1.0]);
let v0 = StaticStorage::new([poison, 1.0]);
let mut eigenvector = [0.0; 2];
let mut scratch = [0.0; 2];
let result = power_iteration(
&a,
2,
&v0,
100,
ALGORITHM_TOL,
&mut eigenvector,
&mut scratch,
);
assert_eq!(result, Err(ConvergenceError::NonFinite), "poison {poison}");
let mut factor = [0.0; 4];
let mut pivots = [0_usize; 2];
let result = inverse_power_iteration(
&a,
2,
&v0,
0.5,
100,
ALGORITHM_TOL,
SINGULAR_TOL,
&mut eigenvector,
&mut factor,
&mut pivots,
&mut scratch,
);
assert_eq!(result, Err(ConvergenceError::NonFinite), "poison {poison}");
}
}
#[test]
fn zero_initial_vector_is_a_zero_vector_error_for_both_iterations() {
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,
100,
ALGORITHM_TOL,
&mut eigenvector,
&mut scratch,
);
assert_eq!(result, Err(ConvergenceError::ZeroVector));
let mut factor = [0.0; 4];
let mut pivots = [0_usize; 2];
let result = inverse_power_iteration(
&a,
2,
&v0,
0.5,
100,
ALGORITHM_TOL,
SINGULAR_TOL,
&mut eigenvector,
&mut factor,
&mut pivots,
&mut scratch,
);
assert_eq!(result, Err(ConvergenceError::ZeroVector));
}
#[test]
fn dimension_mismatches_are_an_error_for_both_iterations() {
let a = StaticStorage::new([2.0, 0.0, 0.0, 1.0]);
let v0 = StaticStorage::new([1.0, 0.0, 0.0]);
let mut eigenvector = [0.0; 3];
let mut scratch = [0.0; 3];
let result = power_iteration(
&a,
3,
&v0,
100,
ALGORITHM_TOL,
&mut eigenvector,
&mut scratch,
);
assert_eq!(result, Err(ConvergenceError::DimensionMismatch));
let mut factor = [0.0; 9];
let mut pivots = [0_usize; 3];
let result = inverse_power_iteration(
&a,
3,
&v0,
0.5,
100,
ALGORITHM_TOL,
SINGULAR_TOL,
&mut eigenvector,
&mut factor,
&mut pivots,
&mut scratch,
);
assert_eq!(result, Err(ConvergenceError::DimensionMismatch));
let v0_short = StaticStorage::new([1.0]);
let mut eigenvector2 = [0.0; 2];
let mut scratch2 = [0.0; 2];
let result = power_iteration(
&a,
2,
&v0_short,
100,
ALGORITHM_TOL,
&mut eigenvector2,
&mut scratch2,
);
assert_eq!(result, Err(ConvergenceError::DimensionMismatch));
let mut factor2 = [0.0; 4];
let mut pivots2 = [0_usize; 2];
let result = inverse_power_iteration(
&a,
2,
&v0_short,
0.5,
100,
ALGORITHM_TOL,
SINGULAR_TOL,
&mut eigenvector2,
&mut factor2,
&mut pivots2,
&mut scratch2,
);
assert_eq!(result, Err(ConvergenceError::DimensionMismatch));
}