kryst 3.2.1

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

use crate::algebra::prelude::*;
use crate::error::KError;
use crate::matrix::sparse::CsrMatrix;
use crate::preconditioner::amg::{AMGBuilder, CoarseSolve, RelaxType};
use crate::preconditioner::{PcSide, Preconditioner};
use faer::Mat;

fn poisson_1d(n: usize) -> CsrMatrix<R> {
    let mut row_ptr = Vec::with_capacity(n + 1);
    let mut col_idx = Vec::new();
    let mut values = Vec::new();
    row_ptr.push(0);
    for i in 0..n {
        if i > 0 {
            col_idx.push(i - 1);
            values.push(R::from(-1.0));
        }
        col_idx.push(i);
        values.push(R::from(2.0));
        if i + 1 < n {
            col_idx.push(i + 1);
            values.push(R::from(-1.0));
        }
        row_ptr.push(col_idx.len());
    }
    CsrMatrix::from_csr(n, n, row_ptr, col_idx, values)
}

#[test]
#[cfg(not(feature = "complex"))]
fn spd_preconditioned_operator_positive() {
    let a = poisson_1d(16);
    let mut amg = AMGBuilder::new()
        .relaxation_type(RelaxType::Jacobi)
        .grid_relax_type_all(RelaxType::Jacobi)
        .require_spd(true)
        .build(&Mat::<R>::zeros(0, 0))
        .unwrap();
    amg.setup(&a).unwrap();

    let caps = amg.capabilities();
    assert!(caps.is_spd);
    assert_eq!(caps.side_restriction, Some(PcSide::Left));

    let n = a.nrows();
    let mut x = vec![R::default(); n];
    let mut ax = vec![R::default(); n];
    let mut y = vec![R::default(); n];
    for t in 0..5 {
        for i in 0..n {
            x[i] = R::from(((i + 17 * t) % 11) as f64) - R::from(5.0);
        }
        let alpha = S::from_real(1.0).real();
        let beta = R::default();
        a.spmv_scaled(alpha, &x, beta, &mut ax).unwrap();
        y.fill(R::default());
        amg.apply(PcSide::Left, &ax, &mut y).unwrap();
        let qf = x.iter().zip(&y).map(|(xi, yi)| *xi * *yi).sum::<R>();
        assert!(
            qf > R::default(),
            "quadratic form should be positive, got {qf}"
        );
    }
}

#[test]
#[cfg(not(feature = "complex"))]
fn spd_enforces_r_equals_pt() {
    let a = poisson_1d(12);
    let mut amg = AMGBuilder::new()
        .relaxation_type(RelaxType::Jacobi)
        .grid_relax_type_all(RelaxType::Jacobi)
        .require_spd(true)
        .build(&Mat::<R>::zeros(0, 0))
        .unwrap();
    amg.setup(&a).unwrap();
    assert!(amg.debug_levels_r_equals_pt());
}

#[test]
fn spd_rejects_ilu_coarse_solver() {
    let a = poisson_1d(20);
    let mut amg = AMGBuilder::new()
        .relaxation_type(RelaxType::Jacobi)
        .grid_relax_type_all(RelaxType::Jacobi)
        .coarse_solve(CoarseSolve::ILU)
        .require_spd(true)
        .build(&Mat::<R>::zeros(0, 0))
        .unwrap();
    let err = amg.setup(&a).unwrap_err();
    match err {
        KError::InvalidInput(msg) => {
            assert!(msg.contains("SPD mode requires"))
        }
        other => panic!("expected InvalidInput, got {other:?}"),
    }
}

#[test]
fn spd_requires_symmetric_sweeps() {
    let a = poisson_1d(10);
    let mut amg = AMGBuilder::new()
        .relaxation_type(RelaxType::Jacobi)
        .grid_relax_type_all(RelaxType::Jacobi)
        .smoothing_sweeps(2, 1)
        .require_spd(true)
        .build(&Mat::<R>::zeros(0, 0))
        .unwrap();
    let err = amg.setup(&a).unwrap_err();
    match err {
        KError::InvalidInput(msg) => {
            assert!(msg.to_lowercase().contains("symmetric pre/post"))
        }
        other => panic!("expected InvalidInput, got {other:?}"),
    }
}

#[test]
fn spd_rejects_non_galerkin_when_forbidden() {
    let a = poisson_1d(18);
    let mut amg = AMGBuilder::new()
        .relaxation_type(RelaxType::Jacobi)
        .grid_relax_type_all(RelaxType::Jacobi)
        .non_galerkin(true, 1, R::from(1e-3), R::from(1e-2), 4)
        .require_spd(true)
        .build(&Mat::<R>::zeros(0, 0))
        .unwrap();
    let err = amg.setup(&a).unwrap_err();
    match err {
        KError::InvalidInput(msg) => {
            assert!(msg.to_lowercase().contains("non-galerkin filtering"))
        }
        other => panic!("expected InvalidInput, got {other:?}"),
    }
}

#[test]
fn spd_right_side_errors() {
    let a = poisson_1d(14);
    let mut amg = AMGBuilder::new()
        .relaxation_type(RelaxType::Jacobi)
        .grid_relax_type_all(RelaxType::Jacobi)
        .require_spd(true)
        .build(&Mat::<R>::zeros(0, 0))
        .unwrap();
    amg.setup(&a).unwrap();
    let rhs = vec![R::from(1.0); a.nrows()];
    let mut z = vec![R::default(); a.nrows()];
    let err = amg.apply(PcSide::Right, &rhs, &mut z).unwrap_err();
    match err {
        KError::InvalidInput(msg) => {
            assert!(msg.to_lowercase().contains("left preconditioning"))
        }
        other => panic!("expected InvalidInput, got {other:?}"),
    }
}