272 lines
8.8 KiB
Rust
272 lines
8.8 KiB
Rust
//! Integration test for mass balance validation with multiple components.
|
|
//!
|
|
//! This test verifies that the mass balance validation works correctly
|
|
//! across a multi-component system simulating a refrigeration cycle.
|
|
|
|
use entropyk_components::port::{FluidId, Port};
|
|
use entropyk_components::{
|
|
Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector, StateSlice,
|
|
};
|
|
use entropyk_core::{Enthalpy, MassFlow, Pressure};
|
|
use entropyk_solver::system::System;
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Mock components for testing
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/// A mock component that simulates balanced mass flow (like a pipe or heat exchanger).
|
|
struct BalancedComponent {
|
|
ports: Vec<ConnectedPort>,
|
|
mass_flow_in: f64,
|
|
}
|
|
|
|
impl BalancedComponent {
|
|
fn new(ports: Vec<ConnectedPort>, mass_flow: f64) -> Self {
|
|
Self {
|
|
ports,
|
|
mass_flow_in: mass_flow,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Component for BalancedComponent {
|
|
fn compute_residuals(
|
|
&self,
|
|
_state: &StateSlice,
|
|
residuals: &mut ResidualVector,
|
|
) -> Result<(), ComponentError> {
|
|
for r in residuals.iter_mut() {
|
|
*r = 0.0;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn jacobian_entries(
|
|
&self,
|
|
_state: &StateSlice,
|
|
jacobian: &mut JacobianBuilder,
|
|
) -> Result<(), ComponentError> {
|
|
for i in 0..self.n_equations() {
|
|
jacobian.add_entry(i, i, 1.0);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn n_equations(&self) -> usize {
|
|
2
|
|
}
|
|
|
|
fn get_ports(&self) -> &[ConnectedPort] {
|
|
&self.ports
|
|
}
|
|
|
|
fn port_mass_flows(&self, _state: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
|
|
// Balanced: inlet positive, outlet negative
|
|
Ok(vec![
|
|
MassFlow::from_kg_per_s(self.mass_flow_in),
|
|
MassFlow::from_kg_per_s(-self.mass_flow_in),
|
|
])
|
|
}
|
|
}
|
|
|
|
/// A mock component with imbalanced mass flow (for testing violation detection).
|
|
struct ImbalancedComponent {
|
|
ports: Vec<ConnectedPort>,
|
|
}
|
|
|
|
impl ImbalancedComponent {
|
|
fn new(ports: Vec<ConnectedPort>) -> Self {
|
|
Self { ports }
|
|
}
|
|
}
|
|
|
|
impl Component for ImbalancedComponent {
|
|
fn compute_residuals(
|
|
&self,
|
|
_state: &StateSlice,
|
|
residuals: &mut ResidualVector,
|
|
) -> Result<(), ComponentError> {
|
|
for r in residuals.iter_mut() {
|
|
*r = 0.0;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn jacobian_entries(
|
|
&self,
|
|
_state: &StateSlice,
|
|
jacobian: &mut JacobianBuilder,
|
|
) -> Result<(), ComponentError> {
|
|
for i in 0..self.n_equations() {
|
|
jacobian.add_entry(i, i, 1.0);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn n_equations(&self) -> usize {
|
|
2
|
|
}
|
|
|
|
fn get_ports(&self) -> &[ConnectedPort] {
|
|
&self.ports
|
|
}
|
|
|
|
fn port_mass_flows(&self, _state: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
|
|
// Imbalanced: inlet 1.0 kg/s, outlet -0.5 kg/s (sum = 0.5 kg/s violation)
|
|
Ok(vec![
|
|
MassFlow::from_kg_per_s(1.0),
|
|
MassFlow::from_kg_per_s(-0.5),
|
|
])
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Test helpers
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
fn make_connected_port_pair(
|
|
fluid: &str,
|
|
p_bar: f64,
|
|
h_j_kg: f64,
|
|
) -> (ConnectedPort, ConnectedPort) {
|
|
let p1 = Port::new(
|
|
FluidId::new(fluid),
|
|
Pressure::from_bar(p_bar),
|
|
Enthalpy::from_joules_per_kg(h_j_kg),
|
|
);
|
|
let p2 = Port::new(
|
|
FluidId::new(fluid),
|
|
Pressure::from_bar(p_bar),
|
|
Enthalpy::from_joules_per_kg(h_j_kg),
|
|
);
|
|
let (c1, c2) = p1.connect(p2).unwrap();
|
|
(c1, c2)
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Tests
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn test_mass_balance_4_component_cycle() {
|
|
// Simulate a 4-component refrigeration cycle: Compressor → Condenser → Valve → Evaporator
|
|
let mut system = System::new();
|
|
|
|
// Create 4 pairs of connected ports for 4 components
|
|
let (p1a, p1b) = make_connected_port_pair("R134a", 5.0, 400_000.0);
|
|
let (p2a, p2b) = make_connected_port_pair("R134a", 5.0, 400_000.0);
|
|
let (p3a, p3b) = make_connected_port_pair("R134a", 5.0, 400_000.0);
|
|
let (p4a, p4b) = make_connected_port_pair("R134a", 5.0, 400_000.0);
|
|
|
|
// Create 4 balanced components (simulating compressor, condenser, valve, evaporator)
|
|
let mass_flow = 0.1; // kg/s
|
|
let comp1 = Box::new(BalancedComponent::new(vec![p1a, p1b], mass_flow));
|
|
let comp2 = Box::new(BalancedComponent::new(vec![p2a, p2b], mass_flow));
|
|
let comp3 = Box::new(BalancedComponent::new(vec![p3a, p3b], mass_flow));
|
|
let comp4 = Box::new(BalancedComponent::new(vec![p4a, p4b], mass_flow));
|
|
|
|
// Add components to system
|
|
let n1 = system.add_component(comp1);
|
|
let n2 = system.add_component(comp2);
|
|
let n3 = system.add_component(comp3);
|
|
let n4 = system.add_component(comp4);
|
|
|
|
// Connect in a cycle
|
|
system.add_edge(n1, n2).unwrap();
|
|
system.add_edge(n2, n3).unwrap();
|
|
system.add_edge(n3, n4).unwrap();
|
|
system.add_edge(n4, n1).unwrap();
|
|
|
|
system.finalize().unwrap();
|
|
|
|
// Test with zero state vector
|
|
let state = vec![0.0; system.full_state_vector_len()];
|
|
let result = system.check_mass_balance(&state);
|
|
|
|
assert!(
|
|
result.is_ok(),
|
|
"Mass balance should pass for balanced 4-component cycle"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mass_balance_detects_imbalance_in_cycle() {
|
|
// Create a cycle with one imbalanced component
|
|
let mut system = System::new();
|
|
|
|
let (p1a, p1b) = make_connected_port_pair("R134a", 5.0, 400_000.0);
|
|
let (p2a, p2b) = make_connected_port_pair("R134a", 5.0, 400_000.0);
|
|
let (p3a, p3b) = make_connected_port_pair("R134a", 5.0, 400_000.0);
|
|
|
|
// Two balanced components
|
|
let comp1 = Box::new(BalancedComponent::new(vec![p1a, p1b], 0.1));
|
|
let comp3 = Box::new(BalancedComponent::new(vec![p3a, p3b], 0.1));
|
|
|
|
// One imbalanced component
|
|
let comp2 = Box::new(ImbalancedComponent::new(vec![p2a, p2b]));
|
|
|
|
let n1 = system.add_component(comp1);
|
|
let n2 = system.add_component(comp2);
|
|
let n3 = system.add_component(comp3);
|
|
|
|
system.add_edge(n1, n2).unwrap();
|
|
system.add_edge(n2, n3).unwrap();
|
|
system.add_edge(n3, n1).unwrap();
|
|
|
|
system.finalize().unwrap();
|
|
|
|
let state = vec![0.0; system.full_state_vector_len()];
|
|
let result = system.check_mass_balance(&state);
|
|
|
|
assert!(
|
|
result.is_err(),
|
|
"Mass balance should fail when one component is imbalanced"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mass_balance_multiple_components_same_flow() {
|
|
// Test that multiple components with the same mass flow pass validation
|
|
let mut system = System::new();
|
|
|
|
// Create 6 components in a chain
|
|
let mut ports = Vec::new();
|
|
for _ in 0..6 {
|
|
let (pa, pb) = make_connected_port_pair("R134a", 5.0, 400_000.0);
|
|
ports.push((pa, pb));
|
|
}
|
|
|
|
let mass_flow = 0.5; // kg/s
|
|
let components: Vec<_> = ports
|
|
.into_iter()
|
|
.map(|(pa, pb)| Box::new(BalancedComponent::new(vec![pa, pb], mass_flow)))
|
|
.collect();
|
|
|
|
let nodes: Vec<_> = components
|
|
.into_iter()
|
|
.map(|c| system.add_component(c))
|
|
.collect();
|
|
|
|
// Connect in a cycle
|
|
for i in 0..nodes.len() {
|
|
let next = (i + 1) % nodes.len();
|
|
system.add_edge(nodes[i], nodes[next]).unwrap();
|
|
}
|
|
|
|
system.finalize().unwrap();
|
|
|
|
let state = vec![0.0; system.full_state_vector_len()];
|
|
let result = system.check_mass_balance(&state);
|
|
|
|
assert!(
|
|
result.is_ok(),
|
|
"Mass balance should pass for multiple balanced components"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mass_balance_tolerance_constant_accessible() {
|
|
// Verify the tolerance constant is accessible
|
|
assert_eq!(System::MASS_BALANCE_TOLERANCE_KG_S, 1e-9);
|
|
}
|