chore: sync project state and current artifacts

This commit is contained in:
Sepehr
2026-02-22 23:27:31 +01:00
parent 1b6415776e
commit dd77089b22
232 changed files with 37056 additions and 4296 deletions

View File

@@ -47,7 +47,7 @@
use crate::port::{Connected, Disconnected, FluidId, Port};
use crate::{
CircuitId, Component, ComponentError, ConnectedPort, JacobianBuilder, OperationalState,
ResidualVector, SystemState,
ResidualVector, StateSlice,
};
use entropyk_core::Calib;
use std::marker::PhantomData;
@@ -284,25 +284,35 @@ impl ExpansionValve<Connected> {
}
/// Computes the full thermodynamic state at the inlet port.
pub fn inlet_state(&self, backend: &impl entropyk_fluids::FluidBackend) -> Result<entropyk_fluids::ThermoState, ComponentError> {
pub fn inlet_state(
&self,
backend: &impl entropyk_fluids::FluidBackend,
) -> Result<entropyk_fluids::ThermoState, ComponentError> {
backend
.full_state(
entropyk_fluids::FluidId::new(self.port_inlet.fluid_id().as_str()),
self.port_inlet.pressure(),
self.port_inlet.enthalpy(),
)
.map_err(|e| ComponentError::CalculationFailed(format!("Failed to compute inlet state: {}", e)))
.map_err(|e| {
ComponentError::CalculationFailed(format!("Failed to compute inlet state: {}", e))
})
}
/// Computes the full thermodynamic state at the outlet port.
pub fn outlet_state(&self, backend: &impl entropyk_fluids::FluidBackend) -> Result<entropyk_fluids::ThermoState, ComponentError> {
pub fn outlet_state(
&self,
backend: &impl entropyk_fluids::FluidBackend,
) -> Result<entropyk_fluids::ThermoState, ComponentError> {
backend
.full_state(
entropyk_fluids::FluidId::new(self.port_outlet.fluid_id().as_str()),
self.port_outlet.pressure(),
self.port_outlet.enthalpy(),
)
.map_err(|e| ComponentError::CalculationFailed(format!("Failed to compute outlet state: {}", e)))
.map_err(|e| {
ComponentError::CalculationFailed(format!("Failed to compute outlet state: {}", e))
})
}
/// Returns the optional opening parameter (0.0 to 1.0).
@@ -534,7 +544,7 @@ impl ExpansionValve<Connected> {
impl Component for ExpansionValve<Connected> {
fn compute_residuals(
&self,
state: &SystemState,
state: &StateSlice,
residuals: &mut ResidualVector,
) -> Result<(), ComponentError> {
if residuals.len() != self.n_equations() {
@@ -585,7 +595,11 @@ impl Component for ExpansionValve<Connected> {
// Mass flow: ṁ_out = f_m × ṁ_in (calibration factor on inlet flow)
let mass_flow_in = state[0];
let mass_flow_out = state[1];
let f_m = self.calib_indices.f_m.map(|idx| state[idx]).unwrap_or(self.calib.f_m);
let f_m = self
.calib_indices
.f_m
.map(|idx| state[idx])
.unwrap_or(self.calib.f_m);
residuals[1] = mass_flow_out - f_m * mass_flow_in;
Ok(())
@@ -593,7 +607,7 @@ impl Component for ExpansionValve<Connected> {
fn jacobian_entries(
&self,
_state: &SystemState,
_state: &StateSlice,
jacobian: &mut JacobianBuilder,
) -> Result<(), ComponentError> {
if self.is_effectively_off() {
@@ -613,7 +627,11 @@ impl Component for ExpansionValve<Connected> {
OperationalState::On | OperationalState::Off => {}
}
let f_m = self.calib_indices.f_m.map(|idx| _state[idx]).unwrap_or(self.calib.f_m);
let f_m = self
.calib_indices
.f_m
.map(|idx| _state[idx])
.unwrap_or(self.calib.f_m);
jacobian.add_entry(0, 0, 0.0);
jacobian.add_entry(0, 1, 0.0);
jacobian.add_entry(1, 0, -f_m);
@@ -633,7 +651,10 @@ impl Component for ExpansionValve<Connected> {
2
}
fn port_mass_flows(&self, state: &SystemState) -> Result<Vec<entropyk_core::MassFlow>, ComponentError> {
fn port_mass_flows(
&self,
state: &StateSlice,
) -> Result<Vec<entropyk_core::MassFlow>, ComponentError> {
if state.len() < MIN_STATE_DIMENSIONS {
return Err(ComponentError::InvalidStateDimensions {
expected: MIN_STATE_DIMENSIONS,
@@ -645,6 +666,45 @@ impl Component for ExpansionValve<Connected> {
Ok(vec![m_in, m_out])
}
/// Returns the enthalpies at the inlet and outlet ports.
///
/// For an expansion valve (isenthalpic device), the inlet and outlet
/// enthalpies should be equal: h_in ≈ h_out.
///
/// # Returns
///
/// A vector containing `[h_inlet, h_outlet]` in order.
fn port_enthalpies(
&self,
_state: &StateSlice,
) -> Result<Vec<entropyk_core::Enthalpy>, ComponentError> {
Ok(vec![
self.port_inlet.enthalpy(),
self.port_outlet.enthalpy(),
])
}
/// Returns the energy transfers for the expansion valve.
///
/// An expansion valve is an isenthalpic throttling device:
/// - **Heat (Q)**: 0 W (adiabatic - no heat exchange with environment)
/// - **Work (W)**: 0 W (no moving parts - no mechanical work)
///
/// # Returns
///
/// `Some((Q=0, W=0))` always, since expansion valves are passive devices.
fn energy_transfers(
&self,
_state: &StateSlice,
) -> Option<(entropyk_core::Power, entropyk_core::Power)> {
match self.operational_state {
OperationalState::Off | OperationalState::Bypass | OperationalState::On => Some((
entropyk_core::Power::from_watts(0.0),
entropyk_core::Power::from_watts(0.0),
)),
}
}
fn get_ports(&self) -> &[ConnectedPort] {
&[]
}
@@ -1019,8 +1079,8 @@ mod tests {
#[test]
fn test_circuit_id() {
let mut valve = create_disconnected_valve();
valve.set_circuit_id(CircuitId::new("primary"));
assert_eq!(valve.circuit_id().as_str(), "primary");
valve.set_circuit_id(CircuitId::from_number(5));
assert_eq!(valve.circuit_id().as_number(), 5);
}
#[test]
@@ -1237,14 +1297,14 @@ mod tests {
#[test]
fn test_state_manageable_circuit_id() {
let valve = create_test_valve();
assert_eq!(valve.circuit_id().as_str(), "default");
assert_eq!(*valve.circuit_id(), CircuitId::ZERO);
}
#[test]
fn test_state_manageable_set_circuit_id() {
let mut valve = create_test_valve();
valve.set_circuit_id(CircuitId::new("secondary"));
assert_eq!(valve.circuit_id().as_str(), "secondary");
valve.set_circuit_id(CircuitId::from_number(2));
assert_eq!(valve.circuit_id().as_number(), 2);
}
#[test]
@@ -1503,4 +1563,141 @@ mod tests {
assert!(PhaseRegion::TwoPhase.is_two_phase() == true);
assert!(PhaseRegion::Superheated.is_two_phase() == false);
}
#[test]
fn test_energy_transfers_zero() {
let valve = create_test_valve();
let state = vec![0.05, 0.05];
let (heat, work) = valve.energy_transfers(&state).unwrap();
assert_relative_eq!(heat.to_watts(), 0.0, epsilon = 1e-10);
assert_relative_eq!(work.to_watts(), 0.0, epsilon = 1e-10);
}
#[test]
fn test_energy_transfers_off_mode() {
let mut valve = create_test_valve();
valve.set_operational_state(OperationalState::Off);
let state = vec![0.05, 0.05];
let (heat, work) = valve.energy_transfers(&state).unwrap();
assert_relative_eq!(heat.to_watts(), 0.0, epsilon = 1e-10);
assert_relative_eq!(work.to_watts(), 0.0, epsilon = 1e-10);
}
#[test]
fn test_energy_transfers_bypass_mode() {
let inlet = Port::new(
FluidId::new("R134a"),
Pressure::from_bar(10.0),
Enthalpy::from_joules_per_kg(250000.0),
);
let outlet = Port::new(
FluidId::new("R134a"),
Pressure::from_bar(10.0),
Enthalpy::from_joules_per_kg(250000.0),
);
let (inlet_conn, outlet_conn) = inlet.connect(outlet).unwrap();
let valve = ExpansionValve {
calib_indices: entropyk_core::CalibIndices::default(),
port_inlet: inlet_conn,
port_outlet: outlet_conn,
calib: Calib::default(),
operational_state: OperationalState::Bypass,
opening: Some(1.0),
fluid_id: FluidId::new("R134a"),
circuit_id: CircuitId::default(),
_state: PhantomData,
};
let state = vec![0.05, 0.05];
let (heat, work) = valve.energy_transfers(&state).unwrap();
assert_relative_eq!(heat.to_watts(), 0.0, epsilon = 1e-10);
assert_relative_eq!(work.to_watts(), 0.0, epsilon = 1e-10);
}
#[test]
fn test_port_enthalpies_returns_two_values() {
let valve = create_test_valve();
let state = vec![0.05, 0.05];
let enthalpies = valve.port_enthalpies(&state).unwrap();
assert_eq!(enthalpies.len(), 2);
}
#[test]
fn test_port_enthalpies_isenthalpic() {
let valve = create_test_valve();
let state = vec![0.05, 0.05];
let enthalpies = valve.port_enthalpies(&state).unwrap();
assert_relative_eq!(
enthalpies[0].to_joules_per_kg(),
enthalpies[1].to_joules_per_kg(),
epsilon = 1e-10
);
}
#[test]
fn test_port_enthalpies_inlet_value() {
let inlet = Port::new(
FluidId::new("R134a"),
Pressure::from_bar(10.0),
Enthalpy::from_joules_per_kg(300000.0),
);
let outlet = Port::new(
FluidId::new("R134a"),
Pressure::from_bar(10.0),
Enthalpy::from_joules_per_kg(300000.0),
);
let (inlet_conn, mut outlet_conn) = inlet.connect(outlet).unwrap();
outlet_conn.set_pressure(Pressure::from_bar(3.5));
let valve = ExpansionValve {
calib_indices: entropyk_core::CalibIndices::default(),
port_inlet: inlet_conn,
port_outlet: outlet_conn,
calib: Calib::default(),
operational_state: OperationalState::On,
opening: Some(1.0),
fluid_id: FluidId::new("R134a"),
circuit_id: CircuitId::default(),
_state: PhantomData,
};
let state = vec![0.05, 0.05];
let enthalpies = valve.port_enthalpies(&state).unwrap();
assert_relative_eq!(enthalpies[0].to_joules_per_kg(), 300000.0, epsilon = 1e-10);
assert_relative_eq!(enthalpies[1].to_joules_per_kg(), 300000.0, epsilon = 1e-10);
}
#[test]
fn test_expansion_valve_energy_balance() {
let valve = create_test_valve();
let state = vec![0.05, 0.05];
let energy = valve.energy_transfers(&state);
let mass_flows = valve.port_mass_flows(&state);
let enthalpies = valve.port_enthalpies(&state);
assert!(energy.is_some());
assert!(mass_flows.is_ok());
assert!(enthalpies.is_ok());
let (heat, work) = energy.unwrap();
let m_flows = mass_flows.unwrap();
let h_flows = enthalpies.unwrap();
assert_eq!(m_flows.len(), h_flows.len());
assert_relative_eq!(heat.to_watts(), 0.0, epsilon = 1e-10);
assert_relative_eq!(work.to_watts(), 0.0, epsilon = 1e-10);
}
}