feat(python): implement python bindings for all components and solvers
This commit is contained in:
@@ -59,8 +59,16 @@ fn pass(n: usize) -> Box<dyn Component> {
|
||||
fn make_port(fluid: &str, p: f64, h: f64) -> ConnectedPort {
|
||||
use entropyk_components::port::{FluidId, Port};
|
||||
use entropyk_core::{Enthalpy, Pressure};
|
||||
let p1 = Port::new(FluidId::new(fluid), Pressure::from_pascals(p), Enthalpy::from_joules_per_kg(h));
|
||||
let p2 = Port::new(FluidId::new(fluid), Pressure::from_pascals(p), Enthalpy::from_joules_per_kg(h));
|
||||
let p1 = Port::new(
|
||||
FluidId::new(fluid),
|
||||
Pressure::from_pascals(p),
|
||||
Enthalpy::from_joules_per_kg(h),
|
||||
);
|
||||
let p2 = Port::new(
|
||||
FluidId::new(fluid),
|
||||
Pressure::from_pascals(p),
|
||||
Enthalpy::from_joules_per_kg(h),
|
||||
);
|
||||
p1.connect(p2).unwrap().0
|
||||
}
|
||||
|
||||
@@ -89,8 +97,11 @@ fn test_4_component_cycle_macro_creation() {
|
||||
let mc = MacroComponent::new(internal);
|
||||
|
||||
// 4 components × 2 eqs = 8 internal equations, 0 exposed ports
|
||||
assert_eq!(mc.n_equations(), 8,
|
||||
"should have 8 internal equations with no exposed ports");
|
||||
assert_eq!(
|
||||
mc.n_equations(),
|
||||
8,
|
||||
"should have 8 internal equations with no exposed ports"
|
||||
);
|
||||
// 4 edges × 2 vars = 8 internal state vars
|
||||
assert_eq!(mc.internal_state_len(), 8);
|
||||
assert!(mc.get_ports().is_empty());
|
||||
@@ -106,8 +117,11 @@ fn test_4_component_cycle_expose_two_ports() {
|
||||
mc.expose_port(2, "refrig_out", make_port("R134a", 5e5, 4.5e5));
|
||||
|
||||
// 8 internal + 4 coupling (2 per port) = 12 equations
|
||||
assert_eq!(mc.n_equations(), 12,
|
||||
"should have 12 equations with 2 exposed ports");
|
||||
assert_eq!(
|
||||
mc.n_equations(),
|
||||
12,
|
||||
"should have 12 equations with 2 exposed ports"
|
||||
);
|
||||
assert_eq!(mc.get_ports().len(), 2);
|
||||
assert_eq!(mc.port_mappings()[0].name, "refrig_in");
|
||||
assert_eq!(mc.port_mappings()[1].name, "refrig_out");
|
||||
@@ -130,14 +144,18 @@ fn test_4_component_cycle_in_parent_system() {
|
||||
// Actually the validation requires an edge:
|
||||
parent.add_edge(_mc_node, other).unwrap();
|
||||
let result = parent.finalize();
|
||||
assert!(result.is_ok(), "parent finalize should succeed: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"parent finalize should succeed: {:?}",
|
||||
result.err()
|
||||
);
|
||||
|
||||
// Parent has 2 nodes, 1 edge
|
||||
assert_eq!(parent.node_count(), 2);
|
||||
assert_eq!(parent.edge_count(), 1);
|
||||
|
||||
// Parent state vector: 1 edge × 2 = 2 state vars
|
||||
assert_eq!(parent.state_vector_len(), 2);
|
||||
// Parent state vector: 1 edge × 2 = 2 state vars + 8 internal vars = 10 vars
|
||||
assert_eq!(parent.state_vector_len(), 10);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -230,13 +248,16 @@ fn test_jacobian_coupling_entries_correct() {
|
||||
|
||||
let entries = jac.entries();
|
||||
let find = |row: usize, col: usize| -> Option<f64> {
|
||||
entries.iter().find(|&&(r, c, _)| r == row && c == col).map(|&(_, _, v)| v)
|
||||
entries
|
||||
.iter()
|
||||
.find(|&&(r, c, _)| r == row && c == col)
|
||||
.map(|&(_, _, v)| v)
|
||||
};
|
||||
|
||||
// Coupling rows 8 (P) and 9 (h)
|
||||
assert_eq!(find(8, 0), Some(1.0), "∂r_P/∂p_ext should be +1");
|
||||
assert_eq!(find(8, 0), Some(1.0), "∂r_P/∂p_ext should be +1");
|
||||
assert_eq!(find(8, 2), Some(-1.0), "∂r_P/∂int_p should be -1");
|
||||
assert_eq!(find(9, 1), Some(1.0), "∂r_h/∂h_ext should be +1");
|
||||
assert_eq!(find(9, 1), Some(1.0), "∂r_h/∂h_ext should be +1");
|
||||
assert_eq!(find(9, 3), Some(-1.0), "∂r_h/∂int_h should be -1");
|
||||
}
|
||||
|
||||
@@ -248,7 +269,7 @@ fn test_jacobian_coupling_entries_correct() {
|
||||
fn test_macro_component_snapshot_serialization() {
|
||||
let internal = build_4_component_cycle();
|
||||
let mut mc = MacroComponent::new(internal);
|
||||
mc.expose_port(0, "refrig_in", make_port("R134a", 1e5, 4e5));
|
||||
mc.expose_port(0, "refrig_in", make_port("R134a", 1e5, 4e5));
|
||||
mc.expose_port(2, "refrig_out", make_port("R134a", 5e5, 4.5e5));
|
||||
mc.set_global_state_offset(0);
|
||||
|
||||
@@ -265,8 +286,7 @@ fn test_macro_component_snapshot_serialization() {
|
||||
|
||||
// JSON round-trip
|
||||
let json = serde_json::to_string_pretty(&snap).expect("must serialize");
|
||||
let restored: MacroComponentSnapshot =
|
||||
serde_json::from_str(&json).expect("must deserialize");
|
||||
let restored: MacroComponentSnapshot = serde_json::from_str(&json).expect("must deserialize");
|
||||
|
||||
assert_eq!(restored.label, snap.label);
|
||||
assert_eq!(restored.internal_edge_states, snap.internal_edge_states);
|
||||
@@ -295,14 +315,14 @@ fn test_two_macro_chillers_in_parallel_topology() {
|
||||
let chiller_a = {
|
||||
let internal = build_4_component_cycle();
|
||||
let mut mc = MacroComponent::new(internal);
|
||||
mc.expose_port(0, "in_a", make_port("R134a", 1e5, 4e5));
|
||||
mc.expose_port(0, "in_a", make_port("R134a", 1e5, 4e5));
|
||||
mc.expose_port(2, "out_a", make_port("R134a", 5e5, 4.5e5));
|
||||
mc
|
||||
};
|
||||
let chiller_b = {
|
||||
let internal = build_4_component_cycle();
|
||||
let mut mc = MacroComponent::new(internal);
|
||||
mc.expose_port(0, "in_b", make_port("R134a", 1e5, 4e5));
|
||||
mc.expose_port(0, "in_b", make_port("R134a", 1e5, 4e5));
|
||||
mc.expose_port(2, "out_b", make_port("R134a", 5e5, 4.5e5));
|
||||
mc
|
||||
};
|
||||
@@ -313,7 +333,7 @@ fn test_two_macro_chillers_in_parallel_topology() {
|
||||
let cb = parent.add_component(Box::new(chiller_b));
|
||||
// Simple pass-through splitter & merger
|
||||
let splitter = parent.add_component(pass(1));
|
||||
let merger = parent.add_component(pass(1));
|
||||
let merger = parent.add_component(pass(1));
|
||||
|
||||
// Topology: splitter → chiller_a → merger
|
||||
// → chiller_b → merger
|
||||
@@ -323,7 +343,11 @@ fn test_two_macro_chillers_in_parallel_topology() {
|
||||
parent.add_edge(cb, merger).unwrap();
|
||||
|
||||
let result = parent.finalize();
|
||||
assert!(result.is_ok(), "parallel chiller topology should finalize cleanly: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"parallel chiller topology should finalize cleanly: {:?}",
|
||||
result.err()
|
||||
);
|
||||
|
||||
// 4 parent edges × 2 = 8 state variables in the parent
|
||||
// 2 chillers × 8 internal variables = 16 internal variables
|
||||
@@ -344,7 +368,11 @@ fn test_two_macro_chillers_in_parallel_topology() {
|
||||
.traverse_for_jacobian()
|
||||
.map(|(_, c, _)| c.n_equations())
|
||||
.sum();
|
||||
assert_eq!(total_eqs, 26, "total equation count mismatch: {}", total_eqs);
|
||||
assert_eq!(
|
||||
total_eqs, 26,
|
||||
"total equation count mismatch: {}",
|
||||
total_eqs
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -352,14 +380,14 @@ fn test_two_macro_chillers_residuals_are_computable() {
|
||||
let chiller_a = {
|
||||
let internal = build_4_component_cycle();
|
||||
let mut mc = MacroComponent::new(internal);
|
||||
mc.expose_port(0, "in_a", make_port("R134a", 1e5, 4e5));
|
||||
mc.expose_port(0, "in_a", make_port("R134a", 1e5, 4e5));
|
||||
mc.expose_port(2, "out_a", make_port("R134a", 5e5, 4.5e5));
|
||||
mc
|
||||
};
|
||||
let chiller_b = {
|
||||
let internal = build_4_component_cycle();
|
||||
let mut mc = MacroComponent::new(internal);
|
||||
mc.expose_port(0, "in_b", make_port("R134a", 1e5, 4e5));
|
||||
mc.expose_port(0, "in_b", make_port("R134a", 1e5, 4e5));
|
||||
mc.expose_port(2, "out_b", make_port("R134a", 5e5, 4.5e5));
|
||||
mc
|
||||
};
|
||||
@@ -371,7 +399,7 @@ fn test_two_macro_chillers_residuals_are_computable() {
|
||||
let ca = parent.add_component(Box::new(chiller_a));
|
||||
let cb = parent.add_component(Box::new(chiller_b));
|
||||
let splitter = parent.add_component(pass(1));
|
||||
let merger = parent.add_component(pass(1));
|
||||
let merger = parent.add_component(pass(1));
|
||||
parent.add_edge(splitter, ca).unwrap();
|
||||
parent.add_edge(splitter, cb).unwrap();
|
||||
parent.add_edge(ca, merger).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user