feat(python): implement python bindings for all components and solvers
This commit is contained in:
@@ -6,13 +6,15 @@
|
||||
//! - `initial_state` respected by NewtonConfig and PicardConfig
|
||||
//! - `with_initial_state` builder on FallbackSolver delegates to both sub-solvers
|
||||
|
||||
use entropyk_components::{Component, ComponentError, JacobianBuilder, ResidualVector, SystemState};
|
||||
use approx::assert_relative_eq;
|
||||
use entropyk_components::{
|
||||
Component, ComponentError, JacobianBuilder, ResidualVector, SystemState,
|
||||
};
|
||||
use entropyk_core::{Enthalpy, Pressure, Temperature};
|
||||
use entropyk_solver::{
|
||||
solver::{FallbackSolver, NewtonConfig, PicardConfig, Solver},
|
||||
InitializerConfig, SmartInitializer, System,
|
||||
};
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Mock Components for Testing
|
||||
@@ -97,7 +99,10 @@ fn test_newton_with_initial_state_converges_at_target() {
|
||||
assert!(result.is_ok(), "Should converge: {:?}", result.err());
|
||||
let converged = result.unwrap();
|
||||
// Started exactly at solution → 0 iterations needed
|
||||
assert_eq!(converged.iterations, 0, "Should converge at initial state (0 iterations)");
|
||||
assert_eq!(
|
||||
converged.iterations, 0,
|
||||
"Should converge at initial state (0 iterations)"
|
||||
);
|
||||
assert!(converged.final_residual < 1e-6);
|
||||
}
|
||||
|
||||
@@ -112,7 +117,10 @@ fn test_picard_with_initial_state_converges_at_target() {
|
||||
|
||||
assert!(result.is_ok(), "Should converge: {:?}", result.err());
|
||||
let converged = result.unwrap();
|
||||
assert_eq!(converged.iterations, 0, "Should converge at initial state (0 iterations)");
|
||||
assert_eq!(
|
||||
converged.iterations, 0,
|
||||
"Should converge at initial state (0 iterations)"
|
||||
);
|
||||
assert!(converged.final_residual < 1e-6);
|
||||
}
|
||||
|
||||
@@ -147,7 +155,10 @@ fn test_fallback_solver_with_initial_state_at_solution() {
|
||||
|
||||
assert!(result.is_ok(), "Should converge: {:?}", result.err());
|
||||
let converged = result.unwrap();
|
||||
assert_eq!(converged.iterations, 0, "Should converge immediately at initial state");
|
||||
assert_eq!(
|
||||
converged.iterations, 0,
|
||||
"Should converge immediately at initial state"
|
||||
);
|
||||
}
|
||||
|
||||
/// AC #8 — Smart initial state reduces iterations vs. zero initial state.
|
||||
@@ -163,20 +174,30 @@ fn test_smart_initializer_reduces_iterations_vs_zero_start() {
|
||||
// Run 1: from zeros
|
||||
let mut sys_zero = build_system_with_targets(targets.clone());
|
||||
let mut solver_zero = NewtonConfig::default();
|
||||
let result_zero = solver_zero.solve(&mut sys_zero).expect("zero-start should converge");
|
||||
let result_zero = solver_zero
|
||||
.solve(&mut sys_zero)
|
||||
.expect("zero-start should converge");
|
||||
|
||||
// Run 2: from smart initial state (we directly provide the values as an approximation)
|
||||
// Use 95% of target as "smart" initial — simulating a near-correct heuristic
|
||||
let smart_state: Vec<f64> = targets.iter().map(|&t| t * 0.95).collect();
|
||||
let mut sys_smart = build_system_with_targets(targets.clone());
|
||||
let mut solver_smart = NewtonConfig::default().with_initial_state(smart_state);
|
||||
let result_smart = solver_smart.solve(&mut sys_smart).expect("smart-start should converge");
|
||||
let result_smart = solver_smart
|
||||
.solve(&mut sys_smart)
|
||||
.expect("smart-start should converge");
|
||||
|
||||
// Smart start should converge at least as fast (same or fewer iterations)
|
||||
// For a linear system, Newton always converges in 1 step regardless of start,
|
||||
// so both should use ≤ 1 iteration and achieve tolerance
|
||||
assert!(result_zero.final_residual < 1e-6, "Zero start should converge to tolerance");
|
||||
assert!(result_smart.final_residual < 1e-6, "Smart start should converge to tolerance");
|
||||
assert!(
|
||||
result_zero.final_residual < 1e-6,
|
||||
"Zero start should converge to tolerance"
|
||||
);
|
||||
assert!(
|
||||
result_smart.final_residual < 1e-6,
|
||||
"Smart start should converge to tolerance"
|
||||
);
|
||||
assert!(
|
||||
result_smart.iterations <= result_zero.iterations,
|
||||
"Smart start ({} iters) should not need more iterations than zero start ({} iters)",
|
||||
@@ -208,8 +229,14 @@ fn test_cold_start_estimate_then_populate() {
|
||||
|
||||
// Both pressures should be physically reasonable
|
||||
assert!(p_evap.to_bar() > 0.5, "P_evap should be > 0.5 bar");
|
||||
assert!(p_cond.to_bar() > p_evap.to_bar(), "P_cond should exceed P_evap");
|
||||
assert!(p_cond.to_bar() < 50.0, "P_cond should be < 50 bar (not supercritical)");
|
||||
assert!(
|
||||
p_cond.to_bar() > p_evap.to_bar(),
|
||||
"P_cond should exceed P_evap"
|
||||
);
|
||||
assert!(
|
||||
p_cond.to_bar() < 50.0,
|
||||
"P_cond should be < 50 bar (not supercritical)"
|
||||
);
|
||||
|
||||
// Build a 2-edge system and populate state
|
||||
let mut sys = System::new();
|
||||
@@ -256,7 +283,10 @@ fn test_initial_state_length_mismatch_fallback() {
|
||||
let mut solver = NewtonConfig::default().with_initial_state(wrong_state);
|
||||
let result = solver.solve(&mut sys);
|
||||
// Should still converge (fell back to zeros)
|
||||
assert!(result.is_ok(), "Should converge even with mismatched initial_state in release mode");
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Should converge even with mismatched initial_state in release mode"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
|
||||
Reference in New Issue
Block a user