kryst 4.3.0

Krylov subspace and preconditioned iterative solvers for dense and sparse linear systems, with shared and distributed memory parallelism.
Documentation
#![cfg(feature = "complex")]

use super::*;

fn poisson_1d_complex(n: usize) -> CsrMatrix<S> {
    let mut row_ptr = Vec::with_capacity(n + 1);
    let mut col_idx = Vec::new();
    let mut vals = Vec::new();
    row_ptr.push(0);
    for i in 0..n {
        if i > 0 {
            col_idx.push(i - 1);
            vals.push(S::from_parts(-1.0, 0.0));
        }
        col_idx.push(i);
        vals.push(S::from_parts(2.0, 0.05 * (i as f64 + 1.0) / n as f64));
        if i + 1 < n {
            col_idx.push(i + 1);
            vals.push(S::from_parts(-1.0, 0.0));
        }
        row_ptr.push(col_idx.len());
    }
    CsrMatrix::from_csr(n, n, row_ptr, col_idx, vals)
}

#[test]
fn complex_amg_cycle_rejects_ilu0_smoother_until_scalar_support() {
    let a = poisson_1d_complex(48);
    let mut amg = AMGBuilder::new()
        .grid_relax_type_all(RelaxType::Ilu0)
        .coarse_solve(CoarseSolve::DirectDense)
        .build(&Mat::<f64>::zeros(0, 0))
        .expect("amg build");

    let err = amg
        .setup(&a)
        .expect_err("complex AMG setup with ILU0 should fail");
    assert!(err.to_string().contains("Ilu0"), "unexpected error: {err}");
}

#[test]
fn complex_amg_cycle_rejects_ras_smoother_until_scalar_support() {
    let a = poisson_1d_complex(48);
    let mut amg = AMGBuilder::new()
        .grid_relax_type_all(RelaxType::Ras)
        .coarse_solve(CoarseSolve::DirectDense)
        .build(&Mat::<f64>::zeros(0, 0))
        .expect("amg build");

    let err = amg
        .setup(&a)
        .expect_err("complex AMG setup with RAS should fail");
    assert!(err.to_string().contains("Ras"), "unexpected error: {err}");
}

#[test]
fn symmetric_non_galerkin_requires_symmetric_smoother() {
    let mut cfg = AMGConfig::default();
    cfg.non_galerkin.enabled = true;
    cfg.non_galerkin.symmetry = NgSymmetry::Symmetric;
    cfg.grid_relax_type[RelaxPhase::Down.ix()] = RelaxType::GaussSeidel;
    cfg.grid_relax_type[RelaxPhase::Up.ix()] = RelaxType::GaussSeidel;

    let err = validate_relax_policy(&cfg, cfg.coarse_solve).unwrap_err();
    match err {
        KError::InvalidInput(msg) => assert!(msg.contains("symmetric smoother")),
        other => panic!("unexpected error: {other:?}"),
    }
}