kryst 3.2.1

Krylov subspace and preconditioned iterative solvers for dense and sparse linear systems, with shared and distributed memory parallelism.
use super::*;
use crate::preconditioner::{PcSide, Preconditioner};
use faer::Mat;

#[test]
#[cfg(not(feature = "complex"))]
fn v_cycle_policy_reports_single_visit() -> Result<(), KError> {
    let a = poisson1d(16);
    let mut amg = AMGBuilder::new()
        .relaxation_type(RelaxType::Jacobi)
        .grid_relax_type_all(RelaxType::Jacobi)
        .cycle_v()
        .build(&Mat::<f64>::zeros(0, 0))?;
    amg.setup(&a)?;
    assert_eq!(amg.cycle_policy.gamma_visits(0), 1);
    Ok(())
}

#[test]
#[cfg(not(feature = "complex"))]
fn w_cycle_policy_uses_configured_gamma() -> Result<(), KError> {
    let a = poisson1d(16);
    let mut amg = AMGBuilder::new()
        .relaxation_type(RelaxType::Jacobi)
        .grid_relax_type_all(RelaxType::Jacobi)
        .cycle_w(3)
        .build(&Mat::<f64>::zeros(0, 0))?;
    amg.setup(&a)?;
    assert_eq!(amg.cycle_policy.gamma_visits(0), 3);
    Ok(())
}

#[test]
#[cfg(not(feature = "complex"))]
fn kcycle_postsmoother_reduces_residual() -> Result<(), KError> {
    let a = poisson1d(64);
    let rhs = vec![1.0; a.nrows()];
    let mut res = vec![0.0; a.nrows()];

    let mut amg_v = AMGBuilder::new()
        .relaxation_type(RelaxType::Jacobi)
        .grid_relax_type_all(RelaxType::Jacobi)
        .cycle_v()
        .build(&Mat::<f64>::zeros(0, 0))?;
    amg_v.setup(&a)?;
    let mut z_v = vec![0.0; a.nrows()];
    amg_v.apply(PcSide::Left, &rhs, &mut z_v)?;
    a.spmv_scaled(1.0, &z_v, 0.0, &mut res)?;
    for i in 0..a.nrows() {
        res[i] = rhs[i] - res[i];
    }
    let norm_v = l2_norm(&res);

    let kc = KCycle {
        levels: vec![0],
        iters: 2,
        algo: KrylovAlgo::FCG,
        place_post: true,
        place_pre: false,
    };
    let mut amg_k = AMGBuilder::new()
        .relaxation_type(RelaxType::Jacobi)
        .grid_relax_type_all(RelaxType::Jacobi)
        .cycle_v()
        .kcycle(kc)
        .build(&Mat::<f64>::zeros(0, 0))?;
    amg_k.setup(&a)?;
    let mut z_k = vec![0.0; a.nrows()];
    res.fill(0.0);
    amg_k.apply(PcSide::Left, &rhs, &mut z_k)?;
    a.spmv_scaled(1.0, &z_k, 0.0, &mut res)?;
    for i in 0..a.nrows() {
        res[i] = rhs[i] - res[i];
    }
    let norm_k = l2_norm(&res);

    assert!(
        norm_k <= norm_v * 0.95,
        "K-cycle residual norm {norm_k} should improve over V-cycle {norm_v}"
    );
    Ok(())
}

#[test]
#[cfg(not(feature = "complex"))]
fn kcycle_is_deterministic() -> Result<(), KError> {
    let a = poisson1d(32);
    let kc = KCycle {
        levels: vec![0, 1],
        iters: 2,
        algo: KrylovAlgo::FCG,
        place_post: true,
        place_pre: true,
    };
    let mut amg = AMGBuilder::new()
        .relaxation_type(RelaxType::Jacobi)
        .grid_relax_type_all(RelaxType::Jacobi)
        .cycle_w(2)
        .kcycle(kc)
        .build(&Mat::<f64>::zeros(0, 0))?;
    amg.setup(&a)?;

    let rhs = vec![1.0; a.nrows()];
    let mut z1 = vec![0.0; a.nrows()];
    let mut z2 = vec![0.0; a.nrows()];

    amg.apply(PcSide::Left, &rhs, &mut z1)?;
    amg.apply(PcSide::Left, &rhs, &mut z2)?;

    for i in 0..a.nrows() {
        assert!(
            (z1[i] - z2[i]).abs() < 1e-12,
            "solution mismatch at row {i}"
        );
    }
    Ok(())
}