Snapshot WIP: Probe calibration path, faer LU backend, and BPHX phase-change duty.
Some checks failed
CI / check (push) Has been cancelled
Some checks failed
CI / check (push) Has been cancelled
Checkpoint incomplete calibration work (cond SDT green, evap SST failing) plus related solver/UI changes so the next pass can fix and extend safely. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
83
crates/solver/tests/faer_backend_parity.rs
Normal file
83
crates/solver/tests/faer_backend_parity.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
//! 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<Vec<f64>> {
|
||||
[
|
||||
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(_)
|
||||
));
|
||||
}
|
||||
Reference in New Issue
Block a user