#[path = "property/krylov/common.rs"]
#[allow(dead_code)]
mod common;
use rustebra::krylov::{
ConvergenceError, arnoldi, inverse_power_iteration, lanczos, power_iteration,
};
use rustebra::storage::{Basis, StaticStorage};
use common::{
ALGORITHM_TOL, ASSERTION_TOL, SINGULAR_TOL, approx_eq_eigenvector, fixed_eigenvector_3,
fixed_similarity_3, max_iter_for,
};
#[test]
fn power_iteration_converges_at_the_gap_ratio_boundary_within_the_derived_budget() {
let a = StaticStorage::new([100.0, 0.0, 0.0, 98.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,
max_iter_for(0.98, ALGORITHM_TOL),
ALGORITHM_TOL,
&mut eigenvector,
&mut scratch,
)
.unwrap();
assert!((eigenvalue - 100.0).abs() <= ASSERTION_TOL * 100.0);
assert!(approx_eq_eigenvector(
&eigenvector,
&[1.0, 0.0],
ASSERTION_TOL
));
}
#[test]
fn power_iteration_reports_exhaustion_when_the_budget_falls_short_of_the_gap() {
let a = StaticStorage::new([100.0, 0.0, 0.0, 98.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,
500,
ALGORITHM_TOL,
&mut eigenvector,
&mut scratch,
);
assert_eq!(result, Err(ConvergenceError::MaxIterationsExceeded));
}
#[test]
fn inverse_power_iteration_converges_at_the_rate_boundary_within_the_derived_budget() {
let a = StaticStorage::new([
1.0, 0.0, 0.0, 0.0, 1.02, 0.0, 0.0, 0.0, -3.0,
]);
let v0 = StaticStorage::new([1.0, 1.0, 1.0]);
let mut eigenvector = [0.0; 3];
let mut factor = [0.0; 9];
let mut pivots = [0_usize; 3];
let mut scratch = [0.0; 3];
let eigenvalue = inverse_power_iteration(
&a,
3,
&v0,
0.0,
max_iter_for(1.0 / 1.02, 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,
&[1.0, 0.0, 0.0],
ASSERTION_TOL
));
}
#[test]
fn inverse_power_iteration_reports_exhaustion_when_the_budget_falls_short_of_the_rate() {
let a = StaticStorage::new([
1.0, 0.0, 0.0, 0.0, 1.02, 0.0, 0.0, 0.0, -3.0,
]);
let v0 = StaticStorage::new([1.0, 1.0, 1.0]);
let mut eigenvector = [0.0; 3];
let mut factor = [0.0; 9];
let mut pivots = [0_usize; 3];
let mut scratch = [0.0; 3];
let result = inverse_power_iteration(
&a,
3,
&v0,
0.0,
500,
ALGORITHM_TOL,
SINGULAR_TOL,
&mut eigenvector,
&mut factor,
&mut pivots,
&mut scratch,
);
assert_eq!(result, Err(ConvergenceError::MaxIterationsExceeded));
}
#[test]
fn power_iteration_finds_the_dominant_eigenpair_of_an_ill_conditioned_matrix() {
let a = fixed_similarity_3([100.0, 1e-6, -50.0]);
let v0 = StaticStorage::new([1.0, 1.0, 1.0]);
let mut eigenvector = [0.0; 3];
let mut scratch = [0.0; 3];
let eigenvalue = power_iteration(
&StaticStorage::new(a),
3,
&v0,
max_iter_for(0.5, ALGORITHM_TOL),
ALGORITHM_TOL,
&mut eigenvector,
&mut scratch,
)
.unwrap();
assert!((eigenvalue - 100.0).abs() <= ASSERTION_TOL * 100.0);
assert!(approx_eq_eigenvector(
&eigenvector,
&fixed_eigenvector_3(0),
ASSERTION_TOL
));
}
#[test]
fn inverse_power_iteration_resolves_the_tiny_eigenvalue_of_an_ill_conditioned_matrix() {
let a = fixed_similarity_3([100.0, 1e-6, -50.0]);
let v0 = StaticStorage::new([1.0, 1.0, 1.0]);
let mut eigenvector = [0.0; 3];
let mut factor = [0.0; 9];
let mut pivots = [0_usize; 3];
let mut scratch = [0.0; 3];
let eigenvalue = inverse_power_iteration(
&StaticStorage::new(a),
3,
&v0,
0.0,
50,
ALGORITHM_TOL,
SINGULAR_TOL,
&mut eigenvector,
&mut factor,
&mut pivots,
&mut scratch,
)
.unwrap();
assert!(
(eigenvalue - 1e-6).abs() <= ASSERTION_TOL,
"got {eigenvalue}"
);
assert!(approx_eq_eigenvector(
&eigenvector,
&fixed_eigenvector_3(1),
ASSERTION_TOL
));
}
#[test]
fn power_iteration_handles_entries_at_the_positive_magnitude_boundary() {
let a = StaticStorage::new([100.0, -99.0, -99.0, 100.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,
200,
ALGORITHM_TOL,
&mut eigenvector,
&mut scratch,
)
.unwrap();
assert!((eigenvalue - 199.0).abs() <= ASSERTION_TOL * 199.0);
assert!(approx_eq_eigenvector(
&eigenvector,
&[1.0, -1.0],
ASSERTION_TOL
));
}
#[test]
fn power_iteration_handles_entries_at_the_negative_magnitude_boundary() {
let a = StaticStorage::new([-100.0, 99.0, 99.0, -100.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,
200,
ALGORITHM_TOL,
&mut eigenvector,
&mut scratch,
)
.unwrap();
assert!((eigenvalue + 199.0).abs() <= ASSERTION_TOL * 199.0);
assert!(approx_eq_eigenvector(
&eigenvector,
&[1.0, -1.0],
ASSERTION_TOL
));
}
#[test]
fn inverse_power_iteration_handles_entries_at_the_magnitude_boundary() {
let a = StaticStorage::new([100.0, -99.0, -99.0, 100.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 eigenvalue = inverse_power_iteration(
&a,
2,
&v0,
0.0,
200,
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,
&[1.0, 1.0],
ASSERTION_TOL
));
}
#[test]
fn lanczos_recovers_the_full_spectrum_of_an_ill_conditioned_matrix() {
let a = fixed_similarity_3([100.0, 1e-6, -50.0]);
let v0 = StaticStorage::new([1.0, 1.0, 1.0]);
let mut buffer = [0.0; 9];
let mut basis = Basis::<f64, 3>::new(&mut buffer, 3).unwrap();
let mut scratch = [0.0; 3];
let t = lanczos(
&StaticStorage::new(a),
3,
&v0,
ALGORITHM_TOL,
&mut basis,
&mut scratch,
)
.unwrap();
let mut dense = nalgebra::Matrix3::zeros();
for j in 0..3 {
dense[(j, j)] = t.diagonal()[j];
}
for (j, &beta) in t.off_diagonal().iter().enumerate() {
dense[(j, j + 1)] = beta;
dense[(j + 1, j)] = beta;
}
let mut actual: [f64; 3] = dense.symmetric_eigen().eigenvalues.into();
actual.sort_by(|x, y| x.total_cmp(y));
let mut expected: [f64; 3] = [100.0, 1e-6, -50.0];
expected.sort_by(|x, y| x.total_cmp(y));
for (got, want) in actual.iter().zip(expected.iter()) {
assert!(
(got - want).abs() <= ASSERTION_TOL,
"got {actual:?}, want {expected:?}"
);
}
}
#[test]
fn arnoldi_recovers_the_full_spectrum_of_an_ill_conditioned_non_symmetric_matrix() {
let roots = [100.0_f64, 1e-6, -50.0];
let sum = roots[0] + roots[1] + roots[2];
let pair_sum = roots[0] * roots[1] + roots[0] * roots[2] + roots[1] * roots[2];
let product = roots[0] * roots[1] * roots[2];
let a = StaticStorage::new([
0.0, 1.0, 0.0, 0.0, 0.0, 1.0, product, -pair_sum, sum,
]);
let v0 = StaticStorage::new([1.0, 1.0, 1.0]);
let mut buffer = [0.0; 9];
let mut basis = Basis::<f64, 3>::new(&mut buffer, 3).unwrap();
let mut scratch = [0.0; 3];
let (h, reached) = arnoldi(&a, 3, &v0, ALGORITHM_TOL, &mut basis, &mut scratch).unwrap();
assert_eq!(reached, 3);
let mut dense = nalgebra::Matrix3::zeros();
for r in 0..3 {
for c in 0..3 {
dense[(r, c)] = h.entry(r, c).unwrap();
}
}
let mut actual: [f64; 3] = dense.complex_eigenvalues().map(|z| z.re).into();
actual.sort_by(|x, y| x.total_cmp(y));
let mut expected: [f64; 3] = [100.0, 1e-6, -50.0];
expected.sort_by(|x, y| x.total_cmp(y));
for (got, want) in actual.iter().zip(expected.iter()) {
assert!(
(got - want).abs() <= ASSERTION_TOL,
"got {actual:?}, want {expected:?}"
);
}
}