KiThe 0.3.6

A numerical suite for chemical kinetics and thermodynamics, combustion, heat and mass transfer,chemical engeneering. Work in progress. Advices and contributions will be appreciated
#[cfg(test)]
mod tests {
    use super::super::SimpleReactorIVP::*;
    use super::super::solver_backend::{
        ReactorIvpExecutionBackend, ReactorIvpMatrixBackend, ReactorIvpMethod,
        ReactorIvpSolverConfig, ReactorIvpSymbolicBackend,
    };
    use crate::Kinetics::mechfinder_api::ReactionData;
    use RustedSciThe::numerical::LSODE2::{
        Lsode2AotProfile, Lsode2AotToolchain, Lsode2ControllerConfig, Lsode2LinearSolverPolicy,
        Lsode2LinearSystemStructure, Lsode2Method, Lsode2NativeExecutionConfig,
        Lsode2ResidualJacobianSource, Lsode2StopComparator, Lsode2SymbolicAssemblyBackend,
        Lsode2SymbolicExecutionMode,
    };
    use std::collections::HashMap;

    fn build_condensed_task() -> SimpleReactorTask {
        let mut task = SimpleReactorTask::new();
        task.kindata
            .set_reaction_data_directly(
                vec![ReactionData::new_elementary(
                    "A=>B".to_string(),
                    vec![1.0e3, 0.0, 1.0e4],
                    None,
                )],
                None,
            )
            .expect("reaction setup should succeed");
        task.kindata.stecheodata.vec_of_molmasses = Some(vec![10.0, 20.0]);
        task.kindata.substances = vec!["A".to_string(), "B".to_string()];
        task.thermal_effects = vec![-2.0e5];
        task.ro = 1200.0;
        task.Cp = 1000.0;
        task.Lambda = 0.25;
        task.m = 0.015;
        task.scaling =
            crate::ReactorsBVP::reactor_BVP_utils::ScalingConfig::new(100.0, 0.05, 100.0);
        task.initial_conditions = HashMap::from([
            ("T".to_string(), 450.0),
            ("q".to_string(), 0.15),
            ("A".to_string(), 0.7),
            ("B".to_string(), 0.3),
        ]);
        task
    }

    #[test]
    fn test_default_solver_config_builds_lambdify_atomview_sparse_problem_config() {
        let mut task = build_condensed_task();
        task.setup_condensed_ivp()
            .expect("condensed setup should succeed");

        let config = task
            .build_lsode2_problem_config()
            .expect("problem config should build");

        assert_eq!(config.method, Lsode2Method::Bdf);
        assert_eq!(
            config.controller,
            Lsode2ControllerConfig::automatic_adams_bdf()
        );
        assert_eq!(
            config.residual_jacobian_source,
            Lsode2ResidualJacobianSource::Symbolic {
                assembly: Lsode2SymbolicAssemblyBackend::AtomView,
                execution: Lsode2SymbolicExecutionMode::LambdifyExpr,
            }
        );
        assert_eq!(
            config.linear_system_structure,
            Lsode2LinearSystemStructure::Sparse
        );
        assert_eq!(config.linear_solver_policy, Lsode2LinearSolverPolicy::Auto);
        assert_eq!(
            config.native_execution,
            Lsode2NativeExecutionConfig::faithful_bdf_solve(200_000, 200_000)
        );
        assert_eq!(config.values, vec!["Teta", "q", "C0", "C1"]);
        assert_eq!(config.arg, "x");
        assert_eq!(config.y0.len(), 4);
        assert_eq!(
            task.solver_backend_config.execution_backend,
            ReactorIvpExecutionBackend::Lambdify
        );
    }

    #[test]
    fn test_method_selection_maps_to_controller_modes() {
        assert_eq!(
            ReactorIvpMethod::Auto.to_controller(),
            Lsode2ControllerConfig::automatic_adams_bdf()
        );
        assert_eq!(
            ReactorIvpMethod::Bdf.to_controller(),
            Lsode2ControllerConfig::bdf_only()
        );
        assert_eq!(
            ReactorIvpMethod::Adams.to_controller(),
            Lsode2ControllerConfig::adams_only()
        );
        assert_eq!(ReactorIvpMethod::Auto.to_lsode2_method(), Lsode2Method::Bdf);
        assert_eq!(ReactorIvpMethod::Bdf.to_lsode2_method(), Lsode2Method::Bdf);
        assert_eq!(
            ReactorIvpMethod::Adams.to_lsode2_method(),
            Lsode2Method::Bdf
        );
    }

    #[test]
    fn test_aot_solver_config_preserves_typed_route_selection() {
        let mut task = build_condensed_task();
        task.set_solver_backend_config(
            ReactorIvpSolverConfig::default()
                .with_method(ReactorIvpMethod::Adams)
                .with_symbolic_backend(ReactorIvpSymbolicBackend::ExprLegacy)
                .with_matrix_backend(ReactorIvpMatrixBackend::Banded)
                .with_aot_backend(Lsode2AotToolchain::CTcc, Lsode2AotProfile::Debug)
                .with_integration_domain(0.0, 0.5)
                .with_first_step(Some(1.0e-4))
                .with_max_step(1.0e-2)
                .with_rtol(1.0e-7)
                .with_atol(1.0e-9),
        );
        task.setup_condensed_ivp()
            .expect("condensed setup should succeed");

        let config = task
            .build_lsode2_problem_config()
            .expect("problem config should build");

        assert_eq!(config.controller, Lsode2ControllerConfig::adams_only());
        assert_eq!(
            config.residual_jacobian_source,
            Lsode2ResidualJacobianSource::Symbolic {
                assembly: Lsode2SymbolicAssemblyBackend::ExprLegacy,
                execution: Lsode2SymbolicExecutionMode::Aot {
                    toolchain: Lsode2AotToolchain::CTcc,
                    profile: Lsode2AotProfile::Debug,
                },
            }
        );
        assert_eq!(
            config.linear_system_structure,
            Lsode2LinearSystemStructure::Banded { kl: 0, ku: 0 }
        );
        assert_eq!(
            config.native_execution,
            Lsode2NativeExecutionConfig::faithful_bdf_solve(200_000, 200_000)
        );
        assert_eq!(config.t0, 0.0);
        assert_eq!(config.t_bound, 0.5);
        assert_eq!(config.first_step, Some(1.0e-4));
        assert_eq!(config.max_step, 1.0e-2);
        assert_eq!(config.rtol, 1.0e-7);
        assert_eq!(config.atol, 1.0e-9);
    }

    #[test]
    fn test_lambdify_solver_config_stays_on_symbolic_execution_route() {
        let mut task = build_condensed_task();
        task.set_solver_backend_config(
            ReactorIvpSolverConfig::default()
                .with_method(ReactorIvpMethod::Auto)
                .with_execution_backend(ReactorIvpExecutionBackend::Lambdify),
        );
        task.setup_condensed_ivp()
            .expect("condensed setup should succeed");

        let config = task
            .build_lsode2_problem_config()
            .expect("problem config should build");

        assert_eq!(
            config.residual_jacobian_source,
            Lsode2ResidualJacobianSource::Symbolic {
                assembly: Lsode2SymbolicAssemblyBackend::AtomView,
                execution: Lsode2SymbolicExecutionMode::LambdifyExpr,
            }
        );
        assert!(!matches!(
            config.residual_jacobian_source,
            Lsode2ResidualJacobianSource::Symbolic {
                execution: Lsode2SymbolicExecutionMode::Aot { .. },
                ..
            }
        ));
    }

    #[test]
    fn test_problem_config_rejects_mismatched_dimensions() {
        let task = build_condensed_task();
        let result = task.solver_backend_config.to_rusted_problem_config(
            vec![],
            vec!["x".to_string()],
            "t".to_string(),
            nalgebra::DVector::from_vec(vec![1.0]),
        );
        assert!(matches!(
            result,
            Err(super::super::solver_backend::ReactorIvpBackendError::InvalidConfiguration(_))
        ));
    }

    #[test]
    fn test_banded_backend_accepts_inferred_band_route_for_nonempty_state_count() {
        let result = ReactorIvpMatrixBackend::Banded.validate_for_dimension(2);
        assert!(result.is_ok());
    }

    #[test]
    fn test_banded_backend_maps_to_inferred_lsode2_band_structure() {
        let backend = ReactorIvpMatrixBackend::Banded;

        assert!(backend.validate_for_dimension(2).is_ok());
        assert!(backend.is_banded());
        assert_eq!(
            backend.to_rusted(),
            Lsode2LinearSystemStructure::Banded { kl: 0, ku: 0 }
        );
    }

    #[test]
    fn test_stop_condition_maps_to_species_concentration_le_rule() {
        let mut task = build_condensed_task();
        task.set_solver_backend_config(
            ReactorIvpSolverConfig::default().with_stop_condition_le(0, 1.0e-4),
        );
        task.setup_condensed_ivp()
            .expect("condensed setup should succeed");

        let config = task
            .build_lsode2_problem_config()
            .expect("problem config should build");

        assert_eq!(config.stop_conditions.len(), 1);
        let stop = &config.stop_conditions[0];
        assert_eq!(stop.variable, "C0");
        assert_eq!(stop.target, 1.0e-4);
        assert_eq!(stop.comparator, Lsode2StopComparator::LessEqual);
    }

    #[test]
    fn test_stop_condition_rejects_out_of_range_species_index() {
        let mut task = build_condensed_task();
        task.set_solver_backend_config(
            ReactorIvpSolverConfig::default().with_stop_condition_le(99, 1.0e-4),
        );
        task.setup_condensed_ivp()
            .expect("condensed setup should succeed");

        let result = task.build_lsode2_problem_config();
        assert!(result.is_err());
    }
}