feat(python): implement python bindings for all components and solvers

This commit is contained in:
Sepehr
2026-02-21 20:34:56 +01:00
parent 8ef8cd2eba
commit 4440132b0a
310 changed files with 11577 additions and 397 deletions

View File

@@ -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)]