feat(python): implement python bindings for all components and solvers
This commit is contained in:
@@ -5,11 +5,11 @@
|
||||
//! - AC #8: `convergence_report` field in `ConvergedState` (Some when criteria set, None by default)
|
||||
//! - Backward compatibility: existing raw-tolerance workflow unchanged
|
||||
|
||||
use entropyk_solver::{
|
||||
CircuitConvergence, ConvergenceCriteria, ConvergenceReport, ConvergedState, ConvergenceStatus,
|
||||
FallbackSolver, FallbackConfig, NewtonConfig, PicardConfig, Solver, System,
|
||||
};
|
||||
use approx::assert_relative_eq;
|
||||
use entropyk_solver::{
|
||||
CircuitConvergence, ConvergedState, ConvergenceCriteria, ConvergenceReport, ConvergenceStatus,
|
||||
FallbackConfig, FallbackSolver, NewtonConfig, PicardConfig, Solver, System,
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// AC #8: ConvergenceReport in ConvergedState
|
||||
@@ -18,13 +18,11 @@ use approx::assert_relative_eq;
|
||||
/// Test that `ConvergedState::new` does NOT attach a report (backward-compat).
|
||||
#[test]
|
||||
fn test_converged_state_new_no_report() {
|
||||
let state = ConvergedState::new(
|
||||
vec![1.0, 2.0],
|
||||
10,
|
||||
1e-8,
|
||||
ConvergenceStatus::Converged,
|
||||
let state = ConvergedState::new(vec![1.0, 2.0], 10, 1e-8, ConvergenceStatus::Converged);
|
||||
assert!(
|
||||
state.convergence_report.is_none(),
|
||||
"ConvergedState::new should not attach a report"
|
||||
);
|
||||
assert!(state.convergence_report.is_none(), "ConvergedState::new should not attach a report");
|
||||
}
|
||||
|
||||
/// Test that `ConvergedState::with_report` attaches a report.
|
||||
@@ -49,7 +47,10 @@ fn test_converged_state_with_report_attaches_report() {
|
||||
report,
|
||||
);
|
||||
|
||||
assert!(state.convergence_report.is_some(), "with_report should attach a report");
|
||||
assert!(
|
||||
state.convergence_report.is_some(),
|
||||
"with_report should attach a report"
|
||||
);
|
||||
assert!(state.convergence_report.unwrap().is_globally_converged());
|
||||
}
|
||||
|
||||
@@ -95,22 +96,34 @@ fn test_fallback_with_convergence_criteria_delegates() {
|
||||
|
||||
let newton_c = solver.newton_config.convergence_criteria.unwrap();
|
||||
let picard_c = solver.picard_config.convergence_criteria.unwrap();
|
||||
assert_relative_eq!(newton_c.pressure_tolerance_pa, criteria.pressure_tolerance_pa);
|
||||
assert_relative_eq!(picard_c.pressure_tolerance_pa, criteria.pressure_tolerance_pa);
|
||||
assert_relative_eq!(
|
||||
newton_c.pressure_tolerance_pa,
|
||||
criteria.pressure_tolerance_pa
|
||||
);
|
||||
assert_relative_eq!(
|
||||
picard_c.pressure_tolerance_pa,
|
||||
criteria.pressure_tolerance_pa
|
||||
);
|
||||
}
|
||||
|
||||
/// Test backward-compat: Newton without criteria → `convergence_criteria` is `None`.
|
||||
#[test]
|
||||
fn test_newton_without_criteria_is_none() {
|
||||
let cfg = NewtonConfig::default();
|
||||
assert!(cfg.convergence_criteria.is_none(), "Default Newton should have no criteria");
|
||||
assert!(
|
||||
cfg.convergence_criteria.is_none(),
|
||||
"Default Newton should have no criteria"
|
||||
);
|
||||
}
|
||||
|
||||
/// Test backward-compat: Picard without criteria → `convergence_criteria` is `None`.
|
||||
#[test]
|
||||
fn test_picard_without_criteria_is_none() {
|
||||
let cfg = PicardConfig::default();
|
||||
assert!(cfg.convergence_criteria.is_none(), "Default Picard should have no criteria");
|
||||
assert!(
|
||||
cfg.convergence_criteria.is_none(),
|
||||
"Default Picard should have no criteria"
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that Newton with empty system returns Err (no panic when criteria set).
|
||||
@@ -119,8 +132,8 @@ fn test_newton_with_criteria_empty_system_no_panic() {
|
||||
let mut sys = System::new();
|
||||
sys.finalize().unwrap();
|
||||
|
||||
let mut solver = NewtonConfig::default()
|
||||
.with_convergence_criteria(ConvergenceCriteria::default());
|
||||
let mut solver =
|
||||
NewtonConfig::default().with_convergence_criteria(ConvergenceCriteria::default());
|
||||
|
||||
// Empty system → wrapped error, no panic
|
||||
let result = solver.solve(&mut sys);
|
||||
@@ -133,8 +146,8 @@ fn test_picard_with_criteria_empty_system_no_panic() {
|
||||
let mut sys = System::new();
|
||||
sys.finalize().unwrap();
|
||||
|
||||
let mut solver = PicardConfig::default()
|
||||
.with_convergence_criteria(ConvergenceCriteria::default());
|
||||
let mut solver =
|
||||
PicardConfig::default().with_convergence_criteria(ConvergenceCriteria::default());
|
||||
|
||||
let result = solver.solve(&mut sys);
|
||||
assert!(result.is_err());
|
||||
@@ -171,9 +184,27 @@ fn test_global_convergence_requires_all_circuits() {
|
||||
// 3 circuits, one fails → not globally converged
|
||||
let report = ConvergenceReport {
|
||||
per_circuit: vec![
|
||||
CircuitConvergence { circuit_id: 0, pressure_ok: true, mass_ok: true, energy_ok: true, converged: true },
|
||||
CircuitConvergence { circuit_id: 1, pressure_ok: true, mass_ok: true, energy_ok: true, converged: true },
|
||||
CircuitConvergence { circuit_id: 2, pressure_ok: false, mass_ok: true, energy_ok: true, converged: false },
|
||||
CircuitConvergence {
|
||||
circuit_id: 0,
|
||||
pressure_ok: true,
|
||||
mass_ok: true,
|
||||
energy_ok: true,
|
||||
converged: true,
|
||||
},
|
||||
CircuitConvergence {
|
||||
circuit_id: 1,
|
||||
pressure_ok: true,
|
||||
mass_ok: true,
|
||||
energy_ok: true,
|
||||
converged: true,
|
||||
},
|
||||
CircuitConvergence {
|
||||
circuit_id: 2,
|
||||
pressure_ok: false,
|
||||
mass_ok: true,
|
||||
energy_ok: true,
|
||||
converged: false,
|
||||
},
|
||||
],
|
||||
globally_converged: false,
|
||||
};
|
||||
@@ -184,9 +215,13 @@ fn test_global_convergence_requires_all_circuits() {
|
||||
#[test]
|
||||
fn test_single_circuit_global_convergence() {
|
||||
let report = ConvergenceReport {
|
||||
per_circuit: vec![
|
||||
CircuitConvergence { circuit_id: 0, pressure_ok: true, mass_ok: true, energy_ok: true, converged: true },
|
||||
],
|
||||
per_circuit: vec![CircuitConvergence {
|
||||
circuit_id: 0,
|
||||
pressure_ok: true,
|
||||
mass_ok: true,
|
||||
energy_ok: true,
|
||||
converged: true,
|
||||
}],
|
||||
globally_converged: true,
|
||||
};
|
||||
assert!(report.is_globally_converged());
|
||||
@@ -196,27 +231,41 @@ fn test_single_circuit_global_convergence() {
|
||||
// AC #7: Integration Validation (Actual Solve)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
use entropyk_components::{Component, ComponentError, JacobianBuilder, ResidualVector, SystemState};
|
||||
use entropyk_components::port::ConnectedPort;
|
||||
use entropyk_components::{
|
||||
Component, ComponentError, JacobianBuilder, ResidualVector, SystemState,
|
||||
};
|
||||
|
||||
struct MockConvergingComponent;
|
||||
|
||||
impl Component for MockConvergingComponent {
|
||||
fn compute_residuals(&self, state: &SystemState, residuals: &mut ResidualVector) -> Result<(), ComponentError> {
|
||||
fn compute_residuals(
|
||||
&self,
|
||||
state: &SystemState,
|
||||
residuals: &mut ResidualVector,
|
||||
) -> Result<(), ComponentError> {
|
||||
// Simple linear system will converge in 1 step
|
||||
residuals[0] = state[0] - 5.0;
|
||||
residuals[1] = state[1] - 10.0;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn jacobian_entries(&self, _state: &SystemState, jacobian: &mut JacobianBuilder) -> Result<(), ComponentError> {
|
||||
fn jacobian_entries(
|
||||
&self,
|
||||
_state: &SystemState,
|
||||
jacobian: &mut JacobianBuilder,
|
||||
) -> Result<(), ComponentError> {
|
||||
jacobian.add_entry(0, 0, 1.0);
|
||||
jacobian.add_entry(1, 1, 1.0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn n_equations(&self) -> usize { 2 }
|
||||
fn get_ports(&self) -> &[ConnectedPort] { &[] }
|
||||
fn n_equations(&self) -> usize {
|
||||
2
|
||||
}
|
||||
fn get_ports(&self) -> &[ConnectedPort] {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -235,7 +284,7 @@ fn test_newton_with_criteria_single_circuit() {
|
||||
|
||||
let mut solver = NewtonConfig::default().with_convergence_criteria(criteria);
|
||||
let result = solver.solve(&mut sys).expect("Solver should converge");
|
||||
|
||||
|
||||
// Check that we got a report back
|
||||
assert!(result.convergence_report.is_some());
|
||||
let report = result.convergence_report.unwrap();
|
||||
@@ -253,7 +302,8 @@ fn test_backward_compat_tolerance_field_survives() {
|
||||
let cfg = NewtonConfig {
|
||||
tolerance: 1e-8,
|
||||
..Default::default()
|
||||
}.with_convergence_criteria(criteria);
|
||||
}
|
||||
.with_convergence_criteria(criteria);
|
||||
|
||||
// tolerance is still 1e-8 (not overwritten by criteria)
|
||||
assert_relative_eq!(cfg.tolerance, 1e-8);
|
||||
|
||||
Reference in New Issue
Block a user