kryst 4.0.3

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

use crate::error::KError;
use crate::utils::convergence::{
    ConvergedReason, FailureReasonKind, FailureStage, NestedPcFailure, ReasonEmitter,
    map_kerror_to_reason,
};

#[test]
fn reason_emitter_non_finite_maps_nan_and_inf() {
    assert_eq!(
        ReasonEmitter::non_finite(f64::NAN),
        Some(ConvergedReason::DivergedNan)
    );
    assert_eq!(
        ReasonEmitter::non_finite(f64::INFINITY),
        Some(ConvergedReason::DivergedInf)
    );
    assert_eq!(ReasonEmitter::non_finite(1.0), None);
}

#[test]
fn reason_emitter_breakdown_and_indefinite_helpers_are_stable() {
    assert_eq!(
        ReasonEmitter::breakdown(),
        ConvergedReason::DivergedBreakdown
    );
    assert_eq!(
        ReasonEmitter::breakdown_bicg(),
        ConvergedReason::DivergedBreakdownBiCG
    );
    assert_eq!(
        ReasonEmitter::indefinite_matrix(),
        ConvergedReason::DivergedIndefiniteMatrix
    );
    assert_eq!(
        ReasonEmitter::indefinite_pc(),
        ConvergedReason::DivergedIndefinitePC
    );
}

#[test]
fn kerror_stage_mapping_is_consistent_across_pc_setup_apply_and_indefiniteness() {
    assert_eq!(
        map_kerror_to_reason(&KError::PcFailed("setup".into()), FailureStage::Setup),
        Some(ConvergedReason::DivergedPcSetupFailed)
    );
    assert_eq!(
        map_kerror_to_reason(&KError::PcFailed("apply".into()), FailureStage::Solve),
        Some(ConvergedReason::DivergedPcFailed)
    );
    assert_eq!(
        map_kerror_to_reason(&KError::FactorError("ilu fail".into()), FailureStage::Setup),
        Some(ConvergedReason::DivergedPcSetupFailed)
    );
    assert_eq!(
        map_kerror_to_reason(&KError::ZeroPivot(3), FailureStage::Setup),
        Some(ConvergedReason::DivergedPcSetupFailed)
    );
    assert_eq!(
        map_kerror_to_reason(&KError::IndefinitePreconditioner, FailureStage::Setup),
        Some(ConvergedReason::DivergedIndefinitePC)
    );
    assert_eq!(
        map_kerror_to_reason(&KError::DivergedIndefinitePC, FailureStage::Setup),
        Some(ConvergedReason::DivergedIndefinitePC)
    );
    assert_eq!(
        map_kerror_to_reason(&KError::IndefiniteMatrix, FailureStage::Solve),
        Some(ConvergedReason::DivergedIndefiniteMatrix)
    );
    assert_eq!(
        map_kerror_to_reason(&KError::DivergedIndefinitePC, FailureStage::Solve),
        Some(ConvergedReason::DivergedIndefinitePC)
    );
    assert_eq!(
        map_kerror_to_reason(&KError::BreakdownOrIndefinite, FailureStage::Solve),
        Some(ConvergedReason::DivergedBreakdown)
    );
}

#[test]
fn reason_emitter_nested_metadata_preserves_nested_failure_context() {
    let nested = NestedPcFailure {
        component: "inner-ksp",
        reason: ConvergedReason::DivergedBreakdownBiCG,
        iterations: 7,
        final_norm: Some("||M^-1 r||=1e-3".into()),
        residual_history_summary: Some("7 entries".into()),
        detail: "inner preconditioner broke down".into(),
    };
    let cloned = ReasonEmitter::nested_pc_failure(
        &KError::NestedPcFailed(nested.clone()),
        FailureStage::Solve,
    )
    .expect("nested metadata should be present");

    assert_eq!(cloned, nested);
}

#[test]
fn reason_emitter_nested_metadata_builds_stage_specific_pc_failure() {
    let setup_failure = ReasonEmitter::nested_pc_failure(
        &KError::PcFailed("zero pivot".into()),
        FailureStage::Setup,
    )
    .expect("setup metadata");
    assert_eq!(setup_failure.reason, ConvergedReason::DivergedPcSetupFailed);
    assert!(setup_failure.detail.contains("stage=Setup"));

    let apply_failure = ReasonEmitter::nested_pc_failure(
        &KError::PcFailed("apply failed".into()),
        FailureStage::Solve,
    )
    .expect("apply metadata");
    assert_eq!(apply_failure.reason, ConvergedReason::DivergedPcFailed);
    assert!(apply_failure.detail.contains("stage=Solve"));
}

#[test]
fn failure_reason_kind_indefinite_variants_are_canonical() {
    assert_eq!(
        ConvergedReason::from_failure_kind(FailureReasonKind::IndefiniteMatrix),
        ConvergedReason::DivergedIndefiniteMatrix
    );
    assert_eq!(
        ConvergedReason::from_failure_kind(FailureReasonKind::IndefinitePc),
        ConvergedReason::DivergedIndefinitePC
    );
}

#[test]
fn nested_failure_reason_is_preserved_when_mapping_error() {
    let nested = KError::NestedPcFailed(NestedPcFailure {
        component: "pc_ksp",
        reason: ConvergedReason::DivergedMaxIts,
        iterations: 11,
        final_norm: Some("true_residual_l2=1.000000e+00".into()),
        residual_history_summary: Some("history_len=11".into()),
        detail: "inner max iters".into(),
    });
    assert_eq!(
        map_kerror_to_reason(&nested, FailureStage::Solve),
        Some(ConvergedReason::DivergedMaxIts)
    );
    assert_eq!(
        map_kerror_to_reason(&nested, FailureStage::Setup),
        Some(ConvergedReason::DivergedMaxIts)
    );
}

#[test]
fn reason_emitter_nested_metadata_captures_factorization_failures() {
    let setup_factor = ReasonEmitter::nested_pc_failure(
        &KError::FactorError("symbolic factorization failed".into()),
        FailureStage::Setup,
    )
    .expect("setup factor metadata");
    assert_eq!(setup_factor.component, "factorization");
    assert_eq!(setup_factor.reason, ConvergedReason::DivergedPcSetupFailed);

    let apply_factor = ReasonEmitter::nested_pc_failure(&KError::ZeroPivot(9), FailureStage::Solve)
        .expect("apply factor metadata");
    assert_eq!(apply_factor.component, "factorization");
    assert_eq!(apply_factor.reason, ConvergedReason::DivergedPcFailed);
    assert!(apply_factor.detail.contains("zero_pivot_row=9"));
}