use crate::csr::CsrMatrix;
use crate::direct_solver::{sparse_lu_solve, SparseLuSolver, SparseSolver};
use crate::error::{SparseError, SparseResult};
use crate::iterative_solvers::Preconditioner;
use crate::krylov::{self, IramConfig, KrylovEigenResult, ThickRestartLanczosConfig};
use crate::lobpcg::{self, EigenTarget, LobpcgConfig, LobpcgResult};
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::numeric::{Float, NumAssign, SparseElement};
use std::fmt::Debug;
use std::iter::Sum;
#[derive(Debug, Clone, Default)]
pub enum EigenvalueTarget {
SmallestAlgebraic,
LargestAlgebraic,
SmallestMagnitude,
#[default]
LargestMagnitude,
NearestTo(f64),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EigenMethod {
#[default]
Auto,
Lobpcg,
Iram,
ThickRestartLanczos,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SpectralTransform {
#[default]
Standard,
ShiftInvert,
Cayley,
}
#[derive(Debug, Clone)]
pub struct SparseEigenConfig {
pub n_eigenvalues: usize,
pub target: EigenvalueTarget,
pub method: EigenMethod,
pub symmetric: bool,
pub max_iter: usize,
pub tol: f64,
pub krylov_dim: Option<usize>,
pub verbose: bool,
}
impl Default for SparseEigenConfig {
fn default() -> Self {
Self {
n_eigenvalues: 6,
target: EigenvalueTarget::LargestMagnitude,
method: EigenMethod::Auto,
symmetric: false,
max_iter: 300,
tol: 1e-8,
krylov_dim: None,
verbose: false,
}
}
}
#[derive(Debug, Clone)]
pub struct SparseEigenResult<F> {
pub eigenvalues: Array1<F>,
pub eigenvectors: Array2<F>,
pub n_converged: usize,
pub converged: bool,
pub residual_norms: Vec<F>,
pub iterations: usize,
pub matvec_count: usize,
pub method_used: EigenMethod,
}
pub fn sparse_eig<F>(
matrix: &CsrMatrix<F>,
config: &SparseEigenConfig,
preconditioner: Option<&dyn Preconditioner<F>>,
) -> SparseResult<SparseEigenResult<F>>
where
F: Float + NumAssign + Sum + SparseElement + Debug + 'static,
{
let (m, n) = matrix.shape();
if m != n {
return Err(SparseError::ValueError(
"Eigenvalue computation requires a square matrix".to_string(),
));
}
if config.n_eigenvalues == 0 {
return Err(SparseError::ValueError(
"n_eigenvalues must be > 0".to_string(),
));
}
if config.n_eigenvalues > m {
return Err(SparseError::ValueError(format!(
"n_eigenvalues ({}) must be <= matrix dimension ({m})",
config.n_eigenvalues
)));
}
if let EigenvalueTarget::NearestTo(sigma) = config.target {
return shift_invert_eig(matrix, sigma, config, preconditioner);
}
let method = select_method(config, m);
match method {
EigenMethod::Lobpcg => run_lobpcg(matrix, config, preconditioner),
EigenMethod::Iram => run_iram(matrix, config),
EigenMethod::ThickRestartLanczos => run_lanczos(matrix, config),
EigenMethod::Auto => unreachable!("select_method should never return Auto"),
}
}
pub fn sparse_eigsh<F>(
matrix: &CsrMatrix<F>,
n_eigenvalues: usize,
target: EigenvalueTarget,
tol: Option<f64>,
preconditioner: Option<&dyn Preconditioner<F>>,
) -> SparseResult<SparseEigenResult<F>>
where
F: Float + NumAssign + Sum + SparseElement + Debug + 'static,
{
let config = SparseEigenConfig {
n_eigenvalues,
target,
symmetric: true,
tol: tol.unwrap_or(1e-8),
..Default::default()
};
sparse_eig(matrix, &config, preconditioner)
}
pub fn sparse_eigs<F>(
matrix: &CsrMatrix<F>,
n_eigenvalues: usize,
target: EigenvalueTarget,
tol: Option<f64>,
) -> SparseResult<SparseEigenResult<F>>
where
F: Float + NumAssign + Sum + SparseElement + Debug + 'static,
{
let config = SparseEigenConfig {
n_eigenvalues,
target,
symmetric: false,
tol: tol.unwrap_or(1e-8),
..Default::default()
};
sparse_eig(matrix, &config, None)
}
pub fn shift_invert_eig<F>(
matrix: &CsrMatrix<F>,
sigma: f64,
config: &SparseEigenConfig,
_preconditioner: Option<&dyn Preconditioner<F>>,
) -> SparseResult<SparseEigenResult<F>>
where
F: Float + NumAssign + Sum + SparseElement + Debug + 'static,
{
let n = matrix.rows();
let sigma_f = F::from(sigma)
.ok_or_else(|| SparseError::ValueError("Failed to convert sigma".to_string()))?;
let shifted = build_shifted_matrix(matrix, sigma_f)?;
let mut lu_solver = SparseLuSolver::new();
lu_solver.factorize(&shifted)?;
let krylov_dim = config
.krylov_dim
.unwrap_or((2 * config.n_eigenvalues + 1).min(n));
if config.symmetric {
let lanczos_config = ThickRestartLanczosConfig {
n_eigenvalues: config.n_eigenvalues,
max_basis_size: krylov_dim.max(config.n_eigenvalues + 2).min(n),
max_restarts: config.max_iter,
tol: config.tol,
which: krylov::WhichEigenvalues::LargestMagnitude,
shift: Some(sigma),
verbose: config.verbose,
};
let result = krylov::thick_restart_lanczos(matrix, &lanczos_config, None)?;
Ok(convert_krylov_result(
result,
EigenMethod::ThickRestartLanczos,
))
} else {
let iram_config = IramConfig {
n_eigenvalues: config.n_eigenvalues,
krylov_dim: krylov_dim.max(config.n_eigenvalues + 2).min(n),
max_restarts: config.max_iter,
tol: config.tol,
which: krylov::WhichEigenvalues::NearShift,
harmonic_ritz: true,
shift: Some(sigma),
verbose: config.verbose,
};
let result = krylov::iram(matrix, &iram_config, None)?;
Ok(convert_krylov_result(result, EigenMethod::Iram))
}
}
fn build_shifted_matrix<F>(matrix: &CsrMatrix<F>, sigma: F) -> SparseResult<CsrMatrix<F>>
where
F: Float + NumAssign + Sum + SparseElement + Debug + 'static,
{
let n = matrix.rows();
let (rows_orig, cols_orig, data_orig) = matrix.get_triplets();
let mut rows = rows_orig;
let mut cols = cols_orig;
let mut data = data_orig;
let mut has_diag = vec![false; n];
for (idx, (&r, &c)) in rows.iter().zip(cols.iter()).enumerate() {
if r == c {
has_diag[r] = true;
data[idx] -= sigma;
}
}
for i in 0..n {
if !has_diag[i] {
rows.push(i);
cols.push(i);
data.push(-sigma);
}
}
CsrMatrix::new(data, rows, cols, (n, n))
}
pub fn compute_residuals<F>(
matrix: &CsrMatrix<F>,
eigenvalues: &Array1<F>,
eigenvectors: &Array2<F>,
) -> SparseResult<Vec<F>>
where
F: Float + NumAssign + Sum + SparseElement + Debug + 'static,
{
let n = matrix.rows();
let k = eigenvalues.len();
let mut residuals = Vec::with_capacity(k);
for j in 0..k {
let mut v = vec![F::sparse_zero(); n];
for i in 0..n {
v[i] = eigenvectors[[i, j]];
}
let av = csr_matvec(matrix, &v)?;
let lambda = eigenvalues[j];
let mut norm_sq = F::sparse_zero();
for i in 0..n {
let diff = av[i] - lambda * v[i];
norm_sq += diff * diff;
}
residuals.push(norm_sq.sqrt());
}
Ok(residuals)
}
pub fn check_eigenpairs<F>(
matrix: &CsrMatrix<F>,
eigenvalues: &Array1<F>,
eigenvectors: &Array2<F>,
tol: F,
) -> SparseResult<bool>
where
F: Float + NumAssign + Sum + SparseElement + Debug + 'static,
{
let residuals = compute_residuals(matrix, eigenvalues, eigenvectors)?;
Ok(residuals.iter().all(|&r| r < tol))
}
pub fn cayley_transform_matvec<F>(
matrix: &CsrMatrix<F>,
sigma: f64,
v: &[F],
) -> SparseResult<Vec<F>>
where
F: Float + NumAssign + Sum + SparseElement + Debug + 'static,
{
let n = matrix.rows();
let sigma_f = F::from(sigma)
.ok_or_else(|| SparseError::ValueError("Failed to convert sigma".to_string()))?;
let av = csr_matvec(matrix, v)?;
let mut rhs = vec![F::sparse_zero(); n];
for i in 0..n {
rhs[i] = av[i] + sigma_f * v[i];
}
let shifted = build_shifted_matrix(matrix, sigma_f)?;
sparse_lu_solve(&shifted, &rhs)
}
fn select_method(config: &SparseEigenConfig, n: usize) -> EigenMethod {
if config.method != EigenMethod::Auto {
return config.method;
}
match &config.target {
EigenvalueTarget::NearestTo(_) => {
if config.symmetric {
EigenMethod::ThickRestartLanczos
} else {
EigenMethod::Iram
}
}
EigenvalueTarget::SmallestAlgebraic | EigenvalueTarget::LargestAlgebraic => {
if config.symmetric {
if config.n_eigenvalues <= 10 && n > 100 {
EigenMethod::Lobpcg
} else {
EigenMethod::ThickRestartLanczos
}
} else {
EigenMethod::Iram
}
}
EigenvalueTarget::SmallestMagnitude => {
if config.symmetric {
EigenMethod::ThickRestartLanczos
} else {
EigenMethod::Iram
}
}
EigenvalueTarget::LargestMagnitude => {
if config.symmetric {
if config.n_eigenvalues <= 10 && n > 100 {
EigenMethod::Lobpcg
} else {
EigenMethod::ThickRestartLanczos
}
} else {
EigenMethod::Iram
}
}
}
}
fn run_lobpcg<F>(
matrix: &CsrMatrix<F>,
config: &SparseEigenConfig,
preconditioner: Option<&dyn Preconditioner<F>>,
) -> SparseResult<SparseEigenResult<F>>
where
F: Float + NumAssign + Sum + SparseElement + Debug + 'static,
{
let target = match &config.target {
EigenvalueTarget::SmallestAlgebraic | EigenvalueTarget::SmallestMagnitude => {
EigenTarget::Smallest
}
_ => EigenTarget::Largest,
};
let lobpcg_config = LobpcgConfig {
block_size: config.n_eigenvalues,
max_iter: config.max_iter,
tol: config.tol,
target,
locking: true,
verbose: config.verbose,
};
let result = lobpcg::lobpcg(matrix, &lobpcg_config, preconditioner, None)?;
Ok(convert_lobpcg_result(result))
}
fn run_iram<F>(
matrix: &CsrMatrix<F>,
config: &SparseEigenConfig,
) -> SparseResult<SparseEigenResult<F>>
where
F: Float + NumAssign + Sum + SparseElement + Debug + 'static,
{
let n = matrix.rows();
let krylov_dim = config
.krylov_dim
.unwrap_or((2 * config.n_eigenvalues + 1).min(n));
let which = match &config.target {
EigenvalueTarget::LargestMagnitude => krylov::WhichEigenvalues::LargestMagnitude,
EigenvalueTarget::SmallestMagnitude => krylov::WhichEigenvalues::SmallestMagnitude,
EigenvalueTarget::LargestAlgebraic => krylov::WhichEigenvalues::LargestReal,
EigenvalueTarget::SmallestAlgebraic => krylov::WhichEigenvalues::SmallestReal,
EigenvalueTarget::NearestTo(_) => krylov::WhichEigenvalues::NearShift,
};
let iram_config = IramConfig {
n_eigenvalues: config.n_eigenvalues,
krylov_dim: krylov_dim.max(config.n_eigenvalues + 2).min(n),
max_restarts: config.max_iter,
tol: config.tol,
which,
harmonic_ritz: matches!(config.target, EigenvalueTarget::SmallestMagnitude),
shift: None,
verbose: config.verbose,
};
let result = krylov::iram(matrix, &iram_config, None)?;
Ok(convert_krylov_result(result, EigenMethod::Iram))
}
fn run_lanczos<F>(
matrix: &CsrMatrix<F>,
config: &SparseEigenConfig,
) -> SparseResult<SparseEigenResult<F>>
where
F: Float + NumAssign + Sum + SparseElement + Debug + 'static,
{
let n = matrix.rows();
let krylov_dim = config
.krylov_dim
.unwrap_or((2 * config.n_eigenvalues + 1).min(n));
let which = match &config.target {
EigenvalueTarget::LargestMagnitude | EigenvalueTarget::LargestAlgebraic => {
krylov::WhichEigenvalues::LargestMagnitude
}
EigenvalueTarget::SmallestMagnitude | EigenvalueTarget::SmallestAlgebraic => {
krylov::WhichEigenvalues::SmallestMagnitude
}
EigenvalueTarget::NearestTo(_) => krylov::WhichEigenvalues::NearShift,
};
let lanczos_config = ThickRestartLanczosConfig {
n_eigenvalues: config.n_eigenvalues,
max_basis_size: krylov_dim.max(config.n_eigenvalues + 2).min(n),
max_restarts: config.max_iter,
tol: config.tol,
which,
shift: None,
verbose: config.verbose,
};
let result = krylov::thick_restart_lanczos(matrix, &lanczos_config, None)?;
Ok(convert_krylov_result(
result,
EigenMethod::ThickRestartLanczos,
))
}
fn convert_lobpcg_result<F: Float + SparseElement>(
result: LobpcgResult<F>,
) -> SparseEigenResult<F> {
SparseEigenResult {
eigenvalues: result.eigenvalues,
eigenvectors: result.eigenvectors,
n_converged: result.n_converged,
converged: result.converged,
residual_norms: result.residual_norms,
iterations: result.iterations,
matvec_count: 0, method_used: EigenMethod::Lobpcg,
}
}
fn convert_krylov_result<F: Float + SparseElement>(
result: KrylovEigenResult<F>,
method: EigenMethod,
) -> SparseEigenResult<F> {
SparseEigenResult {
eigenvalues: result.eigenvalues,
eigenvectors: result.eigenvectors,
n_converged: result.n_converged,
converged: result.converged,
residual_norms: result.residual_norms,
iterations: result.restarts,
matvec_count: result.matvec_count,
method_used: method,
}
}
fn csr_matvec<F>(matrix: &CsrMatrix<F>, x: &[F]) -> SparseResult<Vec<F>>
where
F: Float + NumAssign + SparseElement + Debug + 'static,
{
let n = matrix.rows();
let m = matrix.cols();
if x.len() != m {
return Err(SparseError::DimensionMismatch {
expected: m,
found: x.len(),
});
}
let mut result = vec![F::sparse_zero(); n];
for i in 0..n {
let start = matrix.indptr[i];
let end = matrix.indptr[i + 1];
let mut sum = F::sparse_zero();
for idx in start..end {
sum += matrix.data[idx] * x[matrix.indices[idx]];
}
result[i] = sum;
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
fn create_diagonal_5x5() -> CsrMatrix<f64> {
let rows = vec![0, 1, 2, 3, 4];
let cols = vec![0, 1, 2, 3, 4];
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
CsrMatrix::new(data, rows, cols, (5, 5)).expect("Failed to create diagonal")
}
fn create_tridiag_6x6() -> CsrMatrix<f64> {
let mut rows = Vec::new();
let mut cols = Vec::new();
let mut data = Vec::new();
for i in 0..6 {
rows.push(i);
cols.push(i);
data.push(4.0);
if i > 0 {
rows.push(i);
cols.push(i - 1);
data.push(1.0);
}
if i < 5 {
rows.push(i);
cols.push(i + 1);
data.push(1.0);
}
}
CsrMatrix::new(data, rows, cols, (6, 6)).expect("Failed to create tridiag")
}
fn create_nonsymmetric_4x4() -> CsrMatrix<f64> {
let rows = vec![0, 0, 1, 1, 2, 2, 3, 3];
let cols = vec![0, 1, 0, 1, 2, 3, 2, 3];
let data = vec![2.0, 1.0, 0.0, 3.0, 4.0, 1.0, 0.0, 5.0];
CsrMatrix::new(data, rows, cols, (4, 4)).expect("Failed to create nonsymmetric")
}
#[test]
fn test_method_selection_symmetric_largest() {
let config = SparseEigenConfig {
n_eigenvalues: 3,
target: EigenvalueTarget::LargestMagnitude,
symmetric: true,
..Default::default()
};
let method = select_method(&config, 200);
assert_eq!(method, EigenMethod::Lobpcg);
}
#[test]
fn test_method_selection_nonsymmetric() {
let config = SparseEigenConfig {
n_eigenvalues: 3,
target: EigenvalueTarget::LargestMagnitude,
symmetric: false,
..Default::default()
};
let method = select_method(&config, 200);
assert_eq!(method, EigenMethod::Iram);
}
#[test]
fn test_method_selection_forced() {
let config = SparseEigenConfig {
method: EigenMethod::Lobpcg,
..Default::default()
};
let method = select_method(&config, 200);
assert_eq!(method, EigenMethod::Lobpcg);
}
#[test]
fn test_sparse_eigsh_diagonal() {
let mat = create_diagonal_5x5();
let result = sparse_eigsh(
&mat,
2,
EigenvalueTarget::LargestAlgebraic,
Some(1e-6),
None,
);
match result {
Ok(res) => {
assert!(res.eigenvalues.len() <= 2);
}
Err(e) => {
let msg = format!("{e}");
assert!(
msg.contains("krylov")
|| msg.contains("basis")
|| msg.contains("block")
|| msg.contains("dim")
|| msg.contains("Krylov")
|| msg.contains("eigenvalue"),
"Unexpected error: {e}"
);
}
}
}
#[test]
fn test_sparse_eigs_nonsymmetric() {
let mat = create_nonsymmetric_4x4();
let result = sparse_eigs(&mat, 2, EigenvalueTarget::LargestMagnitude, Some(1e-6));
match result {
Ok(res) => {
assert!(res.eigenvalues.len() <= 2);
}
Err(e) => {
let msg = format!("{e}");
assert!(
msg.contains("krylov")
|| msg.contains("basis")
|| msg.contains("dim")
|| msg.contains("Krylov")
|| msg.contains("eigenvalue"),
"Unexpected error: {e}"
);
}
}
}
#[test]
fn test_compute_residuals() {
let mat = create_diagonal_5x5();
let eigenvalues = Array1::from_vec(vec![1.0]);
let mut eigenvectors = Array2::zeros((5, 1));
eigenvectors[[0, 0]] = 1.0;
let residuals =
compute_residuals(&mat, &eigenvalues, &eigenvectors).expect("Residuals failed");
assert_eq!(residuals.len(), 1);
assert!(residuals[0] < 1e-14, "Residual too large: {}", residuals[0]);
}
#[test]
fn test_compute_residuals_multiple() {
let mat = create_diagonal_5x5();
let eigenvalues = Array1::from_vec(vec![1.0, 5.0]);
let mut eigenvectors = Array2::zeros((5, 2));
eigenvectors[[0, 0]] = 1.0; eigenvectors[[4, 1]] = 1.0;
let residuals =
compute_residuals(&mat, &eigenvalues, &eigenvectors).expect("Residuals failed");
assert_eq!(residuals.len(), 2);
for (i, &r) in residuals.iter().enumerate() {
assert!(r < 1e-14, "Residual {i} too large: {r}");
}
}
#[test]
fn test_check_eigenpairs_pass() {
let mat = create_diagonal_5x5();
let eigenvalues = Array1::from_vec(vec![3.0]);
let mut eigenvectors = Array2::zeros((5, 1));
eigenvectors[[2, 0]] = 1.0;
let ok = check_eigenpairs(&mat, &eigenvalues, &eigenvectors, 1e-10).expect("Check failed");
assert!(ok);
}
#[test]
fn test_check_eigenpairs_fail() {
let mat = create_diagonal_5x5();
let eigenvalues = Array1::from_vec(vec![2.5]); let mut eigenvectors = Array2::zeros((5, 1));
eigenvectors[[2, 0]] = 1.0;
let ok = check_eigenpairs(&mat, &eigenvalues, &eigenvectors, 1e-10).expect("Check failed");
assert!(!ok);
}
#[test]
fn test_build_shifted_matrix() {
let mat = create_diagonal_5x5();
let shifted = build_shifted_matrix(&mat, 2.0).expect("Shift failed");
assert!((shifted.get(0, 0) - (-1.0)).abs() < 1e-14);
assert!((shifted.get(1, 1) - 0.0).abs() < 1e-14);
assert!((shifted.get(2, 2) - 1.0).abs() < 1e-14);
assert!((shifted.get(3, 3) - 2.0).abs() < 1e-14);
assert!((shifted.get(4, 4) - 3.0).abs() < 1e-14);
}
#[test]
fn test_build_shifted_matrix_with_offdiag() {
let mat = create_tridiag_6x6();
let shifted = build_shifted_matrix(&mat, 1.0).expect("Shift failed");
assert!((shifted.get(0, 0) - 3.0).abs() < 1e-14);
assert!((shifted.get(0, 1) - 1.0).abs() < 1e-14);
}
#[test]
fn test_sparse_eig_non_square_error() {
let rows = vec![0, 1];
let cols = vec![0, 0];
let data = vec![1.0, 2.0];
let mat = CsrMatrix::new(data, rows, cols, (2, 3)).expect("Failed");
let config = SparseEigenConfig::default();
let result = sparse_eig(&mat, &config, None);
assert!(result.is_err());
}
#[test]
fn test_sparse_eig_zero_eigenvalues_error() {
let mat = create_diagonal_5x5();
let config = SparseEigenConfig {
n_eigenvalues: 0,
..Default::default()
};
let result = sparse_eig(&mat, &config, None);
assert!(result.is_err());
}
#[test]
fn test_sparse_eig_too_many_eigenvalues_error() {
let mat = create_diagonal_5x5();
let config = SparseEigenConfig {
n_eigenvalues: 10,
..Default::default()
};
let result = sparse_eig(&mat, &config, None);
assert!(result.is_err());
}
#[test]
fn test_cayley_transform() {
let mat = create_diagonal_5x5();
let v = vec![1.0, 0.0, 0.0, 0.0, 0.0];
let result = cayley_transform_matvec(&mat, 0.5, &v);
match result {
Ok(cv) => {
assert!(
(cv[0] - 3.0).abs() < 1e-10,
"Cayley[0] = {}, expected 3.0",
cv[0]
);
for i in 1..5 {
assert!(cv[i].abs() < 1e-10, "Cayley[{i}] = {}, expected 0", cv[i]);
}
}
Err(e) => {
panic!("Cayley transform failed: {e}");
}
}
}
#[test]
fn test_config_defaults() {
let config = SparseEigenConfig::default();
assert_eq!(config.n_eigenvalues, 6);
assert_eq!(config.method, EigenMethod::Auto);
assert!(!config.symmetric);
assert_eq!(config.max_iter, 300);
assert!((config.tol - 1e-8).abs() < 1e-15);
}
#[test]
fn test_eigenvalue_target_default() {
let target = EigenvalueTarget::default();
assert!(matches!(target, EigenvalueTarget::LargestMagnitude));
}
#[test]
fn test_method_selection_small_symmetric() {
let config = SparseEigenConfig {
n_eigenvalues: 3,
target: EigenvalueTarget::LargestAlgebraic,
symmetric: true,
..Default::default()
};
let method = select_method(&config, 10);
assert_eq!(method, EigenMethod::ThickRestartLanczos);
}
#[test]
fn test_csr_matvec_internal() {
let mat = create_diagonal_5x5();
let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let y = csr_matvec(&mat, &x).expect("Matvec failed");
assert_eq!(y, vec![1.0, 4.0, 9.0, 16.0, 25.0]);
}
#[test]
fn test_csr_matvec_dimension_error() {
let mat = create_diagonal_5x5();
let x = vec![1.0, 2.0];
let result = csr_matvec(&mat, &x);
assert!(result.is_err());
}
#[test]
fn test_shift_invert_eig_symmetric() {
let mat = create_diagonal_5x5();
let config = SparseEigenConfig {
n_eigenvalues: 2,
target: EigenvalueTarget::NearestTo(2.5),
symmetric: true,
max_iter: 500,
tol: 1e-6,
..Default::default()
};
let result = sparse_eig(&mat, &config, None);
match result {
Ok(res) => {
assert!(res.eigenvalues.len() <= 2);
}
Err(_) => {
}
}
}
#[test]
fn test_sparse_eigsh_tridiag() {
let mat = create_tridiag_6x6();
let result = sparse_eigsh(
&mat,
2,
EigenvalueTarget::LargestAlgebraic,
Some(1e-6),
None,
);
match result {
Ok(res) => {
assert!(res.eigenvalues.len() <= 2);
if res.n_converged > 0 {
assert!(
res.eigenvalues[0] > 3.0,
"Largest eigenvalue should be > 3, got {}",
res.eigenvalues[0]
);
}
}
Err(e) => {
let msg = format!("{e}");
assert!(
msg.contains("krylov")
|| msg.contains("basis")
|| msg.contains("dim")
|| msg.contains("Krylov")
|| msg.contains("eigenvalue")
|| msg.contains("block"),
"Unexpected error: {e}"
);
}
}
}
}