Fix code review findings for Story 5-1
- Fixed Critical issue: Wired up _state to the underlying HeatExchanger boundary conditions so the Newton-Raphson solver actually sees numerical gradients. - Fixed Critical issue: Bubble up FluidBackend errors via ComponentError::CalculationFailed instead of silently swallowing backend evaluation failures. - Fixed Medium issue: Connected condenser_with_backend into the eurovent.rs system architecture so the demo solves instead of just printing output. - Fixed Medium issue: Removed heavy FluidId clones inside query loop. - Fixed Low issue: Added physical validations to HxSideConditions.
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder};
|
||||
//! use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder, ConnectedPort};
|
||||
//!
|
||||
//! struct MockComponent {
|
||||
//! n_equations: usize,
|
||||
@@ -42,6 +42,10 @@
|
||||
//! fn n_equations(&self) -> usize {
|
||||
//! self.n_equations
|
||||
//! }
|
||||
//!
|
||||
//! fn get_ports(&self) -> &[ConnectedPort] {
|
||||
//! &[]
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! // Trait object usage
|
||||
@@ -51,6 +55,41 @@
|
||||
#![warn(missing_docs)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
pub mod compressor;
|
||||
pub mod expansion_valve;
|
||||
pub mod external_model;
|
||||
pub mod fan;
|
||||
pub mod heat_exchanger;
|
||||
pub mod pipe;
|
||||
pub mod polynomials;
|
||||
pub mod port;
|
||||
pub mod pump;
|
||||
pub mod state_machine;
|
||||
|
||||
pub use compressor::{Ahri540Coefficients, Compressor, CompressorModel, SstSdtCoefficients};
|
||||
pub use expansion_valve::{ExpansionValve, PhaseRegion};
|
||||
pub use external_model::{
|
||||
ExternalModel, ExternalModelConfig, ExternalModelError, ExternalModelMetadata,
|
||||
ExternalModelType, MockExternalModel, ThreadSafeExternalModel,
|
||||
};
|
||||
pub use fan::{Fan, FanCurves};
|
||||
pub use heat_exchanger::model::FluidState;
|
||||
pub use heat_exchanger::{
|
||||
Condenser, CondenserCoil, Economizer, EpsNtuModel, Evaporator, EvaporatorCoil, ExchangerType,
|
||||
FlowConfiguration, HeatExchanger, HeatExchangerBuilder, HeatTransferModel, HxSideConditions,
|
||||
LmtdModel,
|
||||
};
|
||||
pub use pipe::{friction_factor, roughness, Pipe, PipeGeometry};
|
||||
pub use polynomials::{AffinityLaws, PerformanceCurves, Polynomial1D, Polynomial2D};
|
||||
pub use port::{
|
||||
validate_port_continuity, Connected, ConnectedPort, ConnectionError, Disconnected, FluidId, Port,
|
||||
};
|
||||
pub use pump::{Pump, PumpCurves};
|
||||
pub use state_machine::{
|
||||
CircuitId, OperationalState, StateHistory, StateManageable, StateTransitionError,
|
||||
StateTransitionRecord,
|
||||
};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that can occur during component operations.
|
||||
@@ -95,6 +134,26 @@ pub enum ComponentError {
|
||||
/// disconnected ports, uninitialized parameters).
|
||||
#[error("Invalid component state: {0}")]
|
||||
InvalidState(String),
|
||||
|
||||
/// Invalid state transition.
|
||||
///
|
||||
/// The requested state transition is not allowed for this component.
|
||||
#[error("Invalid state transition from {from:?} to {to:?}: {reason}")]
|
||||
InvalidStateTransition {
|
||||
/// State before attempted transition
|
||||
from: OperationalState,
|
||||
/// Attempted target state
|
||||
to: OperationalState,
|
||||
/// Reason for rejection
|
||||
reason: String,
|
||||
},
|
||||
|
||||
/// Calculation dynamically failed.
|
||||
///
|
||||
/// Occurs when an underlying model or backend fails to evaluate
|
||||
/// properties at the requested state.
|
||||
#[error("Calculation failed: {0}")]
|
||||
CalculationFailed(String),
|
||||
}
|
||||
|
||||
/// Represents the state of the entire thermodynamic system.
|
||||
@@ -246,7 +305,7 @@ impl JacobianBuilder {
|
||||
/// This trait is **object-safe**, meaning it can be used with dynamic dispatch:
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder};
|
||||
/// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder, ConnectedPort};
|
||||
///
|
||||
/// struct SimpleComponent;
|
||||
/// impl Component for SimpleComponent {
|
||||
@@ -257,6 +316,7 @@ impl JacobianBuilder {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// fn n_equations(&self) -> usize { 1 }
|
||||
/// fn get_ports(&self) -> &[ConnectedPort] { &[] }
|
||||
/// }
|
||||
///
|
||||
/// let component: Box<dyn Component> = Box::new(SimpleComponent);
|
||||
@@ -310,7 +370,7 @@ pub trait Component {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder};
|
||||
/// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder, ConnectedPort};
|
||||
///
|
||||
/// struct MassBalanceComponent;
|
||||
/// impl Component for MassBalanceComponent {
|
||||
@@ -328,6 +388,7 @@ pub trait Component {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// fn n_equations(&self) -> usize { 1 }
|
||||
/// fn get_ports(&self) -> &[ConnectedPort] { &[] }
|
||||
/// }
|
||||
/// ```
|
||||
fn compute_residuals(
|
||||
@@ -358,7 +419,7 @@ pub trait Component {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder};
|
||||
/// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder, ConnectedPort};
|
||||
///
|
||||
/// struct LinearComponent;
|
||||
/// impl Component for LinearComponent {
|
||||
@@ -375,6 +436,7 @@ pub trait Component {
|
||||
/// }
|
||||
///
|
||||
/// fn n_equations(&self) -> usize { 1 }
|
||||
/// fn get_ports(&self) -> &[ConnectedPort] { &[] }
|
||||
/// }
|
||||
/// ```
|
||||
fn jacobian_entries(
|
||||
@@ -391,7 +453,7 @@ pub trait Component {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder};
|
||||
/// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder, ConnectedPort};
|
||||
///
|
||||
/// struct ThreeEquationComponent;
|
||||
/// impl Component for ThreeEquationComponent {
|
||||
@@ -402,12 +464,41 @@ pub trait Component {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// fn n_equations(&self) -> usize { 3 }
|
||||
/// fn get_ports(&self) -> &[ConnectedPort] { &[] }
|
||||
/// }
|
||||
///
|
||||
/// let component = ThreeEquationComponent;
|
||||
/// assert_eq!(component.n_equations(), 3);
|
||||
/// ```
|
||||
fn n_equations(&self) -> usize;
|
||||
|
||||
/// Returns the connected ports of this component.
|
||||
///
|
||||
/// This method provides access to the component's ports for topology
|
||||
/// validation and graph construction. Components without ports should
|
||||
/// return an empty slice.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder, ConnectedPort};
|
||||
///
|
||||
/// struct PortlessComponent;
|
||||
/// impl Component for PortlessComponent {
|
||||
/// fn compute_residuals(&self, _state: &SystemState, _residuals: &mut ResidualVector) -> Result<(), ComponentError> {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// fn jacobian_entries(&self, _state: &SystemState, _jacobian: &mut JacobianBuilder) -> Result<(), ComponentError> {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// fn n_equations(&self) -> usize { 0 }
|
||||
/// fn get_ports(&self) -> &[ConnectedPort] { &[] }
|
||||
/// }
|
||||
///
|
||||
/// let component = PortlessComponent;
|
||||
/// assert!(component.get_ports().is_empty());
|
||||
/// ```
|
||||
fn get_ports(&self) -> &[ConnectedPort];
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -454,6 +545,10 @@ mod tests {
|
||||
fn n_equations(&self) -> usize {
|
||||
self.n_equations
|
||||
}
|
||||
|
||||
fn get_ports(&self) -> &[super::ConnectedPort] {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -667,4 +762,64 @@ mod tests {
|
||||
let cloned = err.clone();
|
||||
assert_eq!(err, cloned);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_component_with_ports_integration() {
|
||||
use crate::port::{FluidId, Port};
|
||||
use entropyk_core::{Enthalpy, Pressure};
|
||||
|
||||
struct ComponentWithPorts {
|
||||
ports: Vec<ConnectedPort>,
|
||||
}
|
||||
|
||||
impl ComponentWithPorts {
|
||||
fn new() -> Self {
|
||||
let port1 = Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_bar(1.0),
|
||||
Enthalpy::from_joules_per_kg(400000.0),
|
||||
);
|
||||
let port2 = Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_bar(1.0),
|
||||
Enthalpy::from_joules_per_kg(400000.0),
|
||||
);
|
||||
let (connected1, connected2) =
|
||||
port1.connect(port2).expect("connection should succeed");
|
||||
Self {
|
||||
ports: vec![connected1, connected2],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ComponentWithPorts {
|
||||
fn compute_residuals(
|
||||
&self,
|
||||
_state: &SystemState,
|
||||
_residuals: &mut ResidualVector,
|
||||
) -> Result<(), ComponentError> {
|
||||
Ok(())
|
||||
}
|
||||
fn jacobian_entries(
|
||||
&self,
|
||||
_state: &SystemState,
|
||||
_jacobian: &mut JacobianBuilder,
|
||||
) -> Result<(), ComponentError> {
|
||||
Ok(())
|
||||
}
|
||||
fn n_equations(&self) -> usize {
|
||||
0
|
||||
}
|
||||
fn get_ports(&self) -> &[ConnectedPort] {
|
||||
&self.ports
|
||||
}
|
||||
}
|
||||
|
||||
let component = ComponentWithPorts::new();
|
||||
assert_eq!(component.get_ports().len(), 2);
|
||||
assert_eq!(component.get_ports()[0].fluid_id().as_str(), "R134a");
|
||||
|
||||
let boxed: Box<dyn Component> = Box::new(component);
|
||||
assert_eq!(boxed.get_ports().len(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user