//! Story 1.5 AC#2/#3: the faer dense backend solves the reference cycles with //! results matching the nalgebra baseline within tolerance (not bitwise — //! different factorization engines), via the strangler dispatch. mod common; use entropyk_solver::linear::{make_linear_backend, set_linear_backend_override, DenseLu}; use entropyk_solver::{LinearSolver, Solver}; /// Per-value tolerance matching the golden-snapshot comparison policy. const STATE_RTOL: f64 = 1e-6; const STATE_ATOL: f64 = 1.0; // Pa / J/kg absolute floor fn solve_all_cycles() -> Vec> { [ common::build_reference_cycle_a(), common::build_reference_cycle_b(), common::build_reference_cycle_c(), ] .into_iter() .map(|mut system| { common::solve_reference_system_with_state(&mut system) .state .clone() }) .collect() } #[test] fn faer_path_dispatch_and_reference_cycle_parity() { // 1. Unknown override must fail the solve with the dispatch error (the // dispatch is on the strategy path, not dead code). set_linear_backend_override(Some("definitely-not-a-backend")); let mut system = common::build_reference_cycle_a(); let result = entropyk_solver::FallbackSolver::default_solver().solve(&mut system); assert!(result.is_err(), "unknown backend must fail the solve"); // 2. Baseline (nalgebra) on the three reference cycles. set_linear_backend_override(Some("nalgebra")); let baseline = solve_all_cycles(); // 3. Same cycles on faer; converged states must match within tolerance. set_linear_backend_override(Some("faer")); let faer = solve_all_cycles(); set_linear_backend_override(None); assert_eq!(baseline.len(), faer.len()); for (cycle_idx, (base, alt)) in baseline.iter().zip(faer.iter()).enumerate() { assert_eq!( base.len(), alt.len(), "cycle {cycle_idx}: state length differs" ); for (i, (b, a)) in base.iter().zip(alt.iter()).enumerate() { let scale = b.abs().max(a.abs()).max(STATE_ATOL); assert!( (b - a).abs() <= STATE_RTOL * scale, "cycle {cycle_idx} state[{i}]: nalgebra={b} faer={a} (rel diff {})", (b - a).abs() / scale ); } } } #[test] fn dense_lu_enum_dispatches_both_engines() { for name in ["nalgebra", "faer"] { let mut backend = make_linear_backend(Some(name)).unwrap(); backend.set_problem(2, 2).unwrap(); backend .set_linearisation(&[(0, 0, 2.0), (1, 1, 1.0)]) .unwrap(); let mut rhs = vec![4.0, 3.0]; backend.solve_in_place(&mut rhs).unwrap(); assert!((rhs[0] - 2.0).abs() < 1e-9, "{name}: {}", rhs[0]); assert!((rhs[1] - 3.0).abs() < 1e-9, "{name}: {}", rhs[1]); } assert!(matches!( make_linear_backend(Some("faer")).unwrap(), DenseLu::Faer(_) )); }