chore: remove deprecated flow_boundary and update docs to match new architecture
This commit is contained in:
@@ -13,25 +13,40 @@
|
||||
//! - [`Temperature`] - Temperature in Kelvin (K)
|
||||
//! - [`Enthalpy`] - Specific enthalpy in Joules per kilogram (J/kg)
|
||||
//! - [`MassFlow`] - Mass flow rate in kilograms per second (kg/s)
|
||||
//! - [`Power`] - Power in Watts (W)
|
||||
//! - [`Concentration`] - Glycol/brine mixture fraction [0.0, 1.0]
|
||||
//! - [`VolumeFlow`] - Volumetric flow rate in cubic meters per second (m³/s)
|
||||
//! - [`RelativeHumidity`] - Air moisture level [0.0, 1.0]
|
||||
//! - [`VaporQuality`] - Refrigerant two-phase state [0.0, 1.0]
|
||||
//! - [`Entropy`] - Entropy in Joules per kilogram per Kelvin (J/(kg·K))
|
||||
//! - [`ThermalConductance`] - Thermal conductance in Watts per Kelvin (W/K)
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use entropyk_core::{Pressure, Temperature, Enthalpy, MassFlow};
|
||||
//! use entropyk_core::{Pressure, Temperature, Enthalpy, MassFlow, Concentration, VolumeFlow};
|
||||
//!
|
||||
//! // Create values using constructors
|
||||
//! let pressure = Pressure::from_bar(1.0);
|
||||
//! let temperature = Temperature::from_celsius(25.0);
|
||||
//! let concentration = Concentration::from_percent(30.0);
|
||||
//! let flow = VolumeFlow::from_l_per_s(5.0);
|
||||
//!
|
||||
//! // Convert to base units
|
||||
//! assert_eq!(pressure.to_pascals(), 100_000.0);
|
||||
//! assert_eq!(temperature.to_kelvin(), 298.15);
|
||||
//! assert_eq!(concentration.to_fraction(), 0.3);
|
||||
//! assert_eq!(flow.to_m3_per_s(), 0.005);
|
||||
//!
|
||||
//! // Arithmetic operations
|
||||
//! let p1 = Pressure::from_pascals(100_000.0);
|
||||
//! let p2 = Pressure::from_pascals(50_000.0);
|
||||
//! let p3 = p1 + p2;
|
||||
//! assert_eq!(p3.to_pascals(), 150_000.0);
|
||||
//!
|
||||
//! // Bounded types clamp to valid range
|
||||
//! let c = Concentration::from_percent(150.0); // Clamped to 100%
|
||||
//! assert_eq!(c.to_fraction(), 1.0);
|
||||
//! ```
|
||||
|
||||
#![deny(warnings)]
|
||||
@@ -43,12 +58,12 @@ pub mod types;
|
||||
|
||||
// Re-export all physical types for convenience
|
||||
pub use types::{
|
||||
CircuitId, Enthalpy, Entropy, MassFlow, Power, Pressure, Temperature, ThermalConductance,
|
||||
MIN_MASS_FLOW_REGULARIZATION_KG_S,
|
||||
CircuitId, Concentration, Enthalpy, Entropy, MassFlow, Power, Pressure, RelativeHumidity,
|
||||
Temperature, ThermalConductance, VaporQuality, VolumeFlow, MIN_MASS_FLOW_REGULARIZATION_KG_S,
|
||||
};
|
||||
|
||||
// Re-export calibration types
|
||||
pub use calib::{Calib, CalibIndices, CalibValidationError};
|
||||
|
||||
// Re-export system state
|
||||
pub use state::SystemState;
|
||||
pub use state::{InvalidStateLengthError, SystemState};
|
||||
|
||||
@@ -5,8 +5,28 @@
|
||||
//! has two state variables: pressure and enthalpy.
|
||||
|
||||
use crate::{Enthalpy, Pressure};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::{Deref, DerefMut, Index, IndexMut};
|
||||
|
||||
/// Error returned when constructing `SystemState` with invalid data length.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct InvalidStateLengthError {
|
||||
/// The actual length of the provided vector.
|
||||
pub actual_length: usize,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for InvalidStateLengthError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Data length must be even (P, h pairs), got {}",
|
||||
self.actual_length
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for InvalidStateLengthError {}
|
||||
|
||||
/// Represents the thermodynamic state of the entire system.
|
||||
///
|
||||
/// The internal layout is `[P_edge0, h_edge0, P_edge1, h_edge1, ...]` where:
|
||||
@@ -35,7 +55,7 @@ use std::ops::{Deref, DerefMut, Index, IndexMut};
|
||||
/// assert_eq!(p.to_bar(), 2.0);
|
||||
/// assert_eq!(h.to_kilojoules_per_kg(), 400.0);
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SystemState {
|
||||
data: Vec<f64>,
|
||||
edge_count: usize,
|
||||
@@ -85,7 +105,7 @@ impl SystemState {
|
||||
/// ```
|
||||
pub fn from_vec(data: Vec<f64>) -> Self {
|
||||
assert!(
|
||||
data.len() % 2 == 0,
|
||||
data.len().is_multiple_of(2),
|
||||
"Data length must be even (P, h pairs), got {}",
|
||||
data.len()
|
||||
);
|
||||
@@ -93,6 +113,38 @@ impl SystemState {
|
||||
Self { data, edge_count }
|
||||
}
|
||||
|
||||
/// Creates a `SystemState` from a raw vector, returning an error on invalid length.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `data` - Raw vector with layout `[P0, h0, P1, h1, ...]`
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err(InvalidStateLengthError)` if `data.len()` is not even.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_core::SystemState;
|
||||
///
|
||||
/// let data = vec![100000.0, 400000.0, 200000.0, 250000.0];
|
||||
/// let state = SystemState::try_from_vec(data);
|
||||
/// assert!(state.is_ok());
|
||||
///
|
||||
/// let bad_data = vec![1.0, 2.0, 3.0];
|
||||
/// assert!(SystemState::try_from_vec(bad_data).is_err());
|
||||
/// ```
|
||||
pub fn try_from_vec(data: Vec<f64>) -> Result<Self, InvalidStateLengthError> {
|
||||
if !data.len().is_multiple_of(2) {
|
||||
return Err(InvalidStateLengthError {
|
||||
actual_length: data.len(),
|
||||
});
|
||||
}
|
||||
let edge_count = data.len() / 2;
|
||||
Ok(Self { data, edge_count })
|
||||
}
|
||||
|
||||
/// Returns the number of edges in the system.
|
||||
pub fn edge_count(&self) -> usize {
|
||||
self.edge_count
|
||||
@@ -145,7 +197,10 @@ impl SystemState {
|
||||
|
||||
/// Sets the pressure at the specified edge.
|
||||
///
|
||||
/// Does nothing if `edge_idx` is out of bounds.
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics in debug mode if `edge_idx` is out of bounds. In release mode,
|
||||
/// silently does nothing.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -157,7 +212,14 @@ impl SystemState {
|
||||
///
|
||||
/// assert_eq!(state.pressure(0).unwrap().to_bar(), 1.5);
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn set_pressure(&mut self, edge_idx: usize, p: Pressure) {
|
||||
debug_assert!(
|
||||
edge_idx < self.edge_count,
|
||||
"set_pressure: edge_idx {} out of bounds (edge_count: {})",
|
||||
edge_idx,
|
||||
self.edge_count
|
||||
);
|
||||
if let Some(slot) = self.data.get_mut(edge_idx * 2) {
|
||||
*slot = p.to_pascals();
|
||||
}
|
||||
@@ -165,7 +227,10 @@ impl SystemState {
|
||||
|
||||
/// Sets the enthalpy at the specified edge.
|
||||
///
|
||||
/// Does nothing if `edge_idx` is out of bounds.
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics in debug mode if `edge_idx` is out of bounds. In release mode,
|
||||
/// silently does nothing.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -177,7 +242,14 @@ impl SystemState {
|
||||
///
|
||||
/// assert_eq!(state.enthalpy(0).unwrap().to_kilojoules_per_kg(), 250.0);
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn set_enthalpy(&mut self, edge_idx: usize, h: Enthalpy) {
|
||||
debug_assert!(
|
||||
edge_idx < self.edge_count,
|
||||
"set_enthalpy: edge_idx {} out of bounds (edge_count: {})",
|
||||
edge_idx,
|
||||
self.edge_count
|
||||
);
|
||||
if let Some(slot) = self.data.get_mut(edge_idx * 2 + 1) {
|
||||
*slot = h.to_joules_per_kg();
|
||||
}
|
||||
@@ -404,15 +476,19 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_out_of_bounds_silent() {
|
||||
#[cfg(debug_assertions)]
|
||||
#[should_panic(expected = "edge_idx 10 out of bounds")]
|
||||
fn test_set_pressure_out_of_bounds_panics_in_debug() {
|
||||
let mut state = SystemState::new(2);
|
||||
// These should silently do nothing
|
||||
state.set_pressure(10, Pressure::from_pascals(100000.0));
|
||||
state.set_enthalpy(10, Enthalpy::from_joules_per_kg(300000.0));
|
||||
}
|
||||
|
||||
// Verify nothing was set
|
||||
assert!(state.pressure(10).is_none());
|
||||
assert!(state.enthalpy(10).is_none());
|
||||
#[test]
|
||||
#[cfg(debug_assertions)]
|
||||
#[should_panic(expected = "edge_idx 10 out of bounds")]
|
||||
fn test_set_enthalpy_out_of_bounds_panics_in_debug() {
|
||||
let mut state = SystemState::new(2);
|
||||
state.set_enthalpy(10, Enthalpy::from_joules_per_kg(300000.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -458,6 +534,47 @@ mod tests {
|
||||
assert!(state.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_from_vec_valid() {
|
||||
let data = vec![100000.0, 400000.0, 200000.0, 250000.0];
|
||||
let state = SystemState::try_from_vec(data).unwrap();
|
||||
assert_eq!(state.edge_count(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_from_vec_odd_length() {
|
||||
let data = vec![1.0, 2.0, 3.0];
|
||||
let err = SystemState::try_from_vec(data).unwrap_err();
|
||||
assert_eq!(err.actual_length, 3);
|
||||
assert!(err.to_string().contains("must be even"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_from_vec_empty() {
|
||||
let data: Vec<f64> = vec![];
|
||||
let state = SystemState::try_from_vec(data).unwrap();
|
||||
assert!(state.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_state_length_error_display() {
|
||||
let err = InvalidStateLengthError { actual_length: 5 };
|
||||
let msg = format!("{}", err);
|
||||
assert!(msg.contains("5"));
|
||||
assert!(msg.contains("must be even"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serde_roundtrip() {
|
||||
let mut state = SystemState::new(2);
|
||||
state.set_pressure(0, Pressure::from_pascals(100000.0));
|
||||
state.set_enthalpy(0, Enthalpy::from_joules_per_kg(200000.0));
|
||||
|
||||
let json = serde_json::to_string(&state).unwrap();
|
||||
let deserialized: SystemState = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(state, deserialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iter_edges() {
|
||||
let mut state = SystemState::new(2);
|
||||
|
||||
@@ -8,6 +8,26 @@
|
||||
//! - Temperature: Kelvin (K)
|
||||
//! - Enthalpy: Joules per kilogram (J/kg)
|
||||
//! - MassFlow: Kilograms per second (kg/s)
|
||||
//! - Power: Watts (W)
|
||||
//! - Concentration: Dimensionless fraction [0.0, 1.0]
|
||||
//! - VolumeFlow: Cubic meters per second (m³/s)
|
||||
//! - RelativeHumidity: Dimensionless fraction [0.0, 1.0]
|
||||
//! - VaporQuality: Dimensionless fraction [0.0, 1.0]
|
||||
//! - Entropy: Joules per kilogram per Kelvin (J/(kg·K))
|
||||
//! - ThermalConductance: Watts per Kelvin (W/K)
|
||||
//!
|
||||
//! # Type Safety
|
||||
//!
|
||||
//! These types cannot be mixed accidentally - the following will not compile:
|
||||
//!
|
||||
//! ```compile_fail
|
||||
//! use entropyk_core::{Pressure, Temperature};
|
||||
//!
|
||||
//! let p = Pressure::from_bar(1.0);
|
||||
//! let t = Temperature::from_celsius(25.0);
|
||||
//! // This is a compile error - cannot add Pressure and Temperature!
|
||||
//! let _invalid = p + t; // ERROR: mismatched types
|
||||
//! ```
|
||||
|
||||
use std::fmt;
|
||||
use std::ops::{Add, Div, Mul, Sub};
|
||||
@@ -516,6 +536,418 @@ impl Div<f64> for Power {
|
||||
}
|
||||
}
|
||||
|
||||
/// Concentration (dimensionless fraction 0.0 to 1.0).
|
||||
///
|
||||
/// Represents glycol/brine mixture fraction. Internally stores a dimensionless
|
||||
/// fraction clamped to [0.0, 1.0].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_core::Concentration;
|
||||
///
|
||||
/// let c = Concentration::from_percent(50.0);
|
||||
/// assert_eq!(c.to_fraction(), 0.5);
|
||||
/// assert_eq!(c.to_percent(), 50.0);
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct Concentration(pub f64);
|
||||
|
||||
impl Concentration {
|
||||
/// Creates a Concentration from a fraction, clamped to [0.0, 1.0].
|
||||
pub fn from_fraction(value: f64) -> Self {
|
||||
Concentration(value.clamp(0.0, 1.0))
|
||||
}
|
||||
|
||||
/// Creates a Concentration from a percentage, clamped to [0, 100]%.
|
||||
pub fn from_percent(value: f64) -> Self {
|
||||
Concentration((value / 100.0).clamp(0.0, 1.0))
|
||||
}
|
||||
|
||||
/// Returns the concentration as a fraction [0.0, 1.0].
|
||||
pub fn to_fraction(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the concentration as a percentage [0, 100].
|
||||
pub fn to_percent(&self) -> f64 {
|
||||
self.0 * 100.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Concentration {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}%", self.to_percent())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Concentration {
|
||||
fn from(value: f64) -> Self {
|
||||
Concentration(value.clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Concentration> for Concentration {
|
||||
type Output = Concentration;
|
||||
|
||||
fn add(self, other: Concentration) -> Concentration {
|
||||
Concentration((self.0 + other.0).clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Concentration> for Concentration {
|
||||
type Output = Concentration;
|
||||
|
||||
fn sub(self, other: Concentration) -> Concentration {
|
||||
Concentration((self.0 - other.0).clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f64> for Concentration {
|
||||
type Output = Concentration;
|
||||
|
||||
fn mul(self, scalar: f64) -> Concentration {
|
||||
Concentration((self.0 * scalar).clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Concentration> for f64 {
|
||||
type Output = Concentration;
|
||||
|
||||
fn mul(self, c: Concentration) -> Concentration {
|
||||
Concentration((self * c.0).clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Concentration {
|
||||
type Output = Concentration;
|
||||
|
||||
fn div(self, scalar: f64) -> Concentration {
|
||||
Concentration((self.0 / scalar).clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Volumetric flow rate in cubic meters per second (m³/s).
|
||||
///
|
||||
/// Internally stores the value in m³/s (SI base unit).
|
||||
/// Provides conversions to/from L/s, L/min, and m³/h.
|
||||
///
|
||||
/// Note: Unlike bounded types (Concentration, RelativeHumidity, VaporQuality),
|
||||
/// VolumeFlow accepts negative values to allow representation of reverse flow.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_core::VolumeFlow;
|
||||
///
|
||||
/// let v = VolumeFlow::from_l_per_s(100.0);
|
||||
/// assert_eq!(v.to_m3_per_s(), 0.1);
|
||||
/// assert_eq!(v.to_l_per_min(), 6000.0);
|
||||
///
|
||||
/// // Negative values represent reverse flow
|
||||
/// let reverse = VolumeFlow::from_m3_per_s(-0.5);
|
||||
/// assert_eq!(reverse.to_m3_per_s(), -0.5);
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct VolumeFlow(pub f64);
|
||||
|
||||
impl VolumeFlow {
|
||||
/// Creates a VolumeFlow from a value in m³/s.
|
||||
pub fn from_m3_per_s(value: f64) -> Self {
|
||||
VolumeFlow(value)
|
||||
}
|
||||
|
||||
/// Creates a VolumeFlow from a value in liters per second.
|
||||
pub fn from_l_per_s(value: f64) -> Self {
|
||||
VolumeFlow(value / 1000.0)
|
||||
}
|
||||
|
||||
/// Creates a VolumeFlow from a value in liters per minute.
|
||||
pub fn from_l_per_min(value: f64) -> Self {
|
||||
VolumeFlow(value / 60_000.0)
|
||||
}
|
||||
|
||||
/// Creates a VolumeFlow from a value in m³/h.
|
||||
pub fn from_m3_per_h(value: f64) -> Self {
|
||||
VolumeFlow(value / 3600.0)
|
||||
}
|
||||
|
||||
/// Returns the volumetric flow in m³/s.
|
||||
pub fn to_m3_per_s(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the volumetric flow in liters per second.
|
||||
pub fn to_l_per_s(&self) -> f64 {
|
||||
self.0 * 1000.0
|
||||
}
|
||||
|
||||
/// Returns the volumetric flow in liters per minute.
|
||||
pub fn to_l_per_min(&self) -> f64 {
|
||||
self.0 * 60_000.0
|
||||
}
|
||||
|
||||
/// Returns the volumetric flow in m³/h.
|
||||
pub fn to_m3_per_h(&self) -> f64 {
|
||||
self.0 * 3600.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for VolumeFlow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} m³/s", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for VolumeFlow {
|
||||
fn from(value: f64) -> Self {
|
||||
VolumeFlow(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<VolumeFlow> for VolumeFlow {
|
||||
type Output = VolumeFlow;
|
||||
|
||||
fn add(self, other: VolumeFlow) -> VolumeFlow {
|
||||
VolumeFlow(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<VolumeFlow> for VolumeFlow {
|
||||
type Output = VolumeFlow;
|
||||
|
||||
fn sub(self, other: VolumeFlow) -> VolumeFlow {
|
||||
VolumeFlow(self.0 - other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f64> for VolumeFlow {
|
||||
type Output = VolumeFlow;
|
||||
|
||||
fn mul(self, scalar: f64) -> VolumeFlow {
|
||||
VolumeFlow(self.0 * scalar)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<VolumeFlow> for f64 {
|
||||
type Output = VolumeFlow;
|
||||
|
||||
fn mul(self, v: VolumeFlow) -> VolumeFlow {
|
||||
VolumeFlow(self * v.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for VolumeFlow {
|
||||
type Output = VolumeFlow;
|
||||
|
||||
fn div(self, scalar: f64) -> VolumeFlow {
|
||||
VolumeFlow(self.0 / scalar)
|
||||
}
|
||||
}
|
||||
|
||||
/// Relative humidity (dimensionless fraction 0.0 to 1.0).
|
||||
///
|
||||
/// Represents air moisture level. Internally stores a dimensionless
|
||||
/// fraction clamped to [0.0, 1.0].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_core::RelativeHumidity;
|
||||
///
|
||||
/// let rh = RelativeHumidity::from_percent(60.0);
|
||||
/// assert_eq!(rh.to_fraction(), 0.6);
|
||||
/// assert_eq!(rh.to_percent(), 60.0);
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct RelativeHumidity(pub f64);
|
||||
|
||||
impl RelativeHumidity {
|
||||
/// Creates a RelativeHumidity from a fraction, clamped to [0.0, 1.0].
|
||||
pub fn from_fraction(value: f64) -> Self {
|
||||
RelativeHumidity(value.clamp(0.0, 1.0))
|
||||
}
|
||||
|
||||
/// Creates a RelativeHumidity from a percentage, clamped to [0, 100]%.
|
||||
pub fn from_percent(value: f64) -> Self {
|
||||
RelativeHumidity((value / 100.0).clamp(0.0, 1.0))
|
||||
}
|
||||
|
||||
/// Returns the relative humidity as a fraction [0.0, 1.0].
|
||||
pub fn to_fraction(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the relative humidity as a percentage [0, 100].
|
||||
pub fn to_percent(&self) -> f64 {
|
||||
self.0 * 100.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RelativeHumidity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}% RH", self.to_percent())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for RelativeHumidity {
|
||||
fn from(value: f64) -> Self {
|
||||
RelativeHumidity(value.clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<RelativeHumidity> for RelativeHumidity {
|
||||
type Output = RelativeHumidity;
|
||||
|
||||
fn add(self, other: RelativeHumidity) -> RelativeHumidity {
|
||||
RelativeHumidity((self.0 + other.0).clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<RelativeHumidity> for RelativeHumidity {
|
||||
type Output = RelativeHumidity;
|
||||
|
||||
fn sub(self, other: RelativeHumidity) -> RelativeHumidity {
|
||||
RelativeHumidity((self.0 - other.0).clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f64> for RelativeHumidity {
|
||||
type Output = RelativeHumidity;
|
||||
|
||||
fn mul(self, scalar: f64) -> RelativeHumidity {
|
||||
RelativeHumidity((self.0 * scalar).clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<RelativeHumidity> for f64 {
|
||||
type Output = RelativeHumidity;
|
||||
|
||||
fn mul(self, rh: RelativeHumidity) -> RelativeHumidity {
|
||||
RelativeHumidity((self * rh.0).clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for RelativeHumidity {
|
||||
type Output = RelativeHumidity;
|
||||
|
||||
fn div(self, scalar: f64) -> RelativeHumidity {
|
||||
RelativeHumidity((self.0 / scalar).clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Vapor quality (dimensionless fraction 0.0 to 1.0).
|
||||
///
|
||||
/// Represents refrigerant two-phase state where 0 = saturated liquid
|
||||
/// and 1 = saturated vapor. Internally stores a dimensionless fraction
|
||||
/// clamped to [0.0, 1.0].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_core::VaporQuality;
|
||||
///
|
||||
/// let q = VaporQuality::SATURATED_VAPOR;
|
||||
/// assert!(q.is_saturated_vapor());
|
||||
///
|
||||
/// let q2 = VaporQuality::from_fraction(0.5);
|
||||
/// assert_eq!(q2.to_percent(), 50.0);
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct VaporQuality(pub f64);
|
||||
|
||||
impl VaporQuality {
|
||||
/// Saturated liquid quality (0.0).
|
||||
pub const SATURATED_LIQUID: VaporQuality = VaporQuality(0.0);
|
||||
/// Saturated vapor quality (1.0).
|
||||
pub const SATURATED_VAPOR: VaporQuality = VaporQuality(1.0);
|
||||
|
||||
/// Tolerance for saturated state detection.
|
||||
const SATURATED_TOLERANCE: f64 = 1e-9;
|
||||
|
||||
/// Creates a VaporQuality from a fraction, clamped to [0.0, 1.0].
|
||||
pub fn from_fraction(value: f64) -> Self {
|
||||
VaporQuality(value.clamp(0.0, 1.0))
|
||||
}
|
||||
|
||||
/// Creates a VaporQuality from a percentage, clamped to [0, 100]%.
|
||||
pub fn from_percent(value: f64) -> Self {
|
||||
VaporQuality((value / 100.0).clamp(0.0, 1.0))
|
||||
}
|
||||
|
||||
/// Returns the vapor quality as a fraction [0.0, 1.0].
|
||||
pub fn to_fraction(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the vapor quality as a percentage [0, 100].
|
||||
pub fn to_percent(&self) -> f64 {
|
||||
self.0 * 100.0
|
||||
}
|
||||
|
||||
/// Returns true if this represents saturated liquid (quality ≈ 0).
|
||||
pub fn is_saturated_liquid(&self) -> bool {
|
||||
self.0.abs() < Self::SATURATED_TOLERANCE
|
||||
}
|
||||
|
||||
/// Returns true if this represents saturated vapor (quality ≈ 1).
|
||||
pub fn is_saturated_vapor(&self) -> bool {
|
||||
(1.0 - self.0).abs() < Self::SATURATED_TOLERANCE
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for VaporQuality {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} (quality)", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for VaporQuality {
|
||||
fn from(value: f64) -> Self {
|
||||
VaporQuality(value.clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<VaporQuality> for VaporQuality {
|
||||
type Output = VaporQuality;
|
||||
|
||||
fn add(self, other: VaporQuality) -> VaporQuality {
|
||||
VaporQuality((self.0 + other.0).clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<VaporQuality> for VaporQuality {
|
||||
type Output = VaporQuality;
|
||||
|
||||
fn sub(self, other: VaporQuality) -> VaporQuality {
|
||||
VaporQuality((self.0 - other.0).clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f64> for VaporQuality {
|
||||
type Output = VaporQuality;
|
||||
|
||||
fn mul(self, scalar: f64) -> VaporQuality {
|
||||
VaporQuality((self.0 * scalar).clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<VaporQuality> for f64 {
|
||||
type Output = VaporQuality;
|
||||
|
||||
fn mul(self, q: VaporQuality) -> VaporQuality {
|
||||
VaporQuality((self * q.0).clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for VaporQuality {
|
||||
type Output = VaporQuality;
|
||||
|
||||
fn div(self, scalar: f64) -> VaporQuality {
|
||||
VaporQuality((self.0 / scalar).clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Entropy in J/(kg·K).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct Entropy(pub f64);
|
||||
@@ -703,6 +1135,475 @@ mod tests {
|
||||
use super::*;
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
// ==================== CONCENTRATION TESTS ====================
|
||||
|
||||
#[test]
|
||||
fn test_concentration_from_fraction() {
|
||||
let c = Concentration::from_fraction(0.5);
|
||||
assert_relative_eq!(c.0, 0.5, epsilon = 1e-10);
|
||||
assert_relative_eq!(c.to_fraction(), 0.5, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concentration_from_percent() {
|
||||
let c = Concentration::from_percent(50.0);
|
||||
assert_relative_eq!(c.to_fraction(), 0.5, epsilon = 1e-10);
|
||||
assert_relative_eq!(c.to_percent(), 50.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concentration_clamping_negative() {
|
||||
let c = Concentration::from_fraction(-0.5);
|
||||
assert_relative_eq!(c.to_fraction(), 0.0, epsilon = 1e-10);
|
||||
|
||||
let c2 = Concentration::from_percent(-10.0);
|
||||
assert_relative_eq!(c2.to_fraction(), 0.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concentration_clamping_over_one() {
|
||||
let c = Concentration::from_fraction(1.5);
|
||||
assert_relative_eq!(c.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
|
||||
let c2 = Concentration::from_percent(150.0);
|
||||
assert_relative_eq!(c2.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concentration_display() {
|
||||
let c = Concentration::from_fraction(0.5);
|
||||
assert_eq!(format!("{}", c), "50%");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concentration_from_f64() {
|
||||
let c: Concentration = 0.5.into();
|
||||
assert_relative_eq!(c.to_fraction(), 0.5, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concentration_from_f64_clamping() {
|
||||
let c: Concentration = (-0.5).into();
|
||||
assert_relative_eq!(c.to_fraction(), 0.0, epsilon = 1e-10);
|
||||
|
||||
let c2: Concentration = 1.5.into();
|
||||
assert_relative_eq!(c2.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concentration_add() {
|
||||
let c1 = Concentration::from_fraction(0.3);
|
||||
let c2 = Concentration::from_fraction(0.4);
|
||||
let c3 = c1 + c2;
|
||||
assert_relative_eq!(c3.to_fraction(), 0.7, epsilon = 1e-10);
|
||||
|
||||
// Test clamping on overflow
|
||||
let c4 = Concentration::from_fraction(0.8);
|
||||
let c5 = Concentration::from_fraction(0.5);
|
||||
let c6 = c4 + c5;
|
||||
assert_relative_eq!(c6.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concentration_sub() {
|
||||
let c1 = Concentration::from_fraction(0.7);
|
||||
let c2 = Concentration::from_fraction(0.3);
|
||||
let c3 = c1 - c2;
|
||||
assert_relative_eq!(c3.to_fraction(), 0.4, epsilon = 1e-10);
|
||||
|
||||
// Test clamping on underflow
|
||||
let c4 = Concentration::from_fraction(0.2);
|
||||
let c5 = Concentration::from_fraction(0.5);
|
||||
let c6 = c4 - c5;
|
||||
assert_relative_eq!(c6.to_fraction(), 0.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concentration_mul() {
|
||||
let c = Concentration::from_fraction(0.5);
|
||||
let c2 = c * 2.0;
|
||||
assert_relative_eq!(c2.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
|
||||
// Test reverse multiplication
|
||||
let c3 = 2.0 * c;
|
||||
assert_relative_eq!(c3.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concentration_div() {
|
||||
let c = Concentration::from_fraction(0.8);
|
||||
let c2 = c / 2.0;
|
||||
assert_relative_eq!(c2.to_fraction(), 0.4, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concentration_edge_cases() {
|
||||
let zero = Concentration::from_fraction(0.0);
|
||||
assert_relative_eq!(zero.to_fraction(), 0.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(zero.to_percent(), 0.0, epsilon = 1e-10);
|
||||
|
||||
let one = Concentration::from_fraction(1.0);
|
||||
assert_relative_eq!(one.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(one.to_percent(), 100.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
// ==================== VOLUME FLOW TESTS ====================
|
||||
|
||||
#[test]
|
||||
fn test_volume_flow_from_m3_per_s() {
|
||||
let v = VolumeFlow::from_m3_per_s(1.0);
|
||||
assert_relative_eq!(v.0, 1.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(v.to_m3_per_s(), 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume_flow_from_l_per_s() {
|
||||
let v = VolumeFlow::from_l_per_s(1000.0);
|
||||
assert_relative_eq!(v.to_m3_per_s(), 1.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(v.to_l_per_s(), 1000.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume_flow_from_l_per_min() {
|
||||
let v = VolumeFlow::from_l_per_min(60_000.0);
|
||||
assert_relative_eq!(v.to_m3_per_s(), 1.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(v.to_l_per_min(), 60_000.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume_flow_from_m3_per_h() {
|
||||
let v = VolumeFlow::from_m3_per_h(3600.0);
|
||||
assert_relative_eq!(v.to_m3_per_s(), 1.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(v.to_m3_per_h(), 3600.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume_flow_round_trip() {
|
||||
let v1 = VolumeFlow::from_l_per_s(50.0);
|
||||
assert_relative_eq!(v1.to_l_per_s(), 50.0, epsilon = 1e-10);
|
||||
|
||||
let v2 = VolumeFlow::from_l_per_min(3000.0);
|
||||
assert_relative_eq!(v2.to_l_per_min(), 3000.0, epsilon = 1e-10);
|
||||
|
||||
let v3 = VolumeFlow::from_m3_per_h(100.0);
|
||||
assert_relative_eq!(v3.to_m3_per_h(), 100.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume_flow_cross_conversions() {
|
||||
// 1 m³/s = 1000 L/s = 60000 L/min = 3600 m³/h
|
||||
let v = VolumeFlow::from_m3_per_s(1.0);
|
||||
assert_relative_eq!(v.to_l_per_s(), 1000.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(v.to_l_per_min(), 60_000.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(v.to_m3_per_h(), 3600.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume_flow_display() {
|
||||
let v = VolumeFlow::from_m3_per_s(0.5);
|
||||
assert_eq!(format!("{}", v), "0.5 m³/s");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume_flow_from_f64() {
|
||||
let v: VolumeFlow = 1.0.into();
|
||||
assert_relative_eq!(v.to_m3_per_s(), 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume_flow_add() {
|
||||
let v1 = VolumeFlow::from_m3_per_s(1.0);
|
||||
let v2 = VolumeFlow::from_m3_per_s(0.5);
|
||||
let v3 = v1 + v2;
|
||||
assert_relative_eq!(v3.to_m3_per_s(), 1.5, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume_flow_sub() {
|
||||
let v1 = VolumeFlow::from_m3_per_s(1.0);
|
||||
let v2 = VolumeFlow::from_m3_per_s(0.3);
|
||||
let v3 = v1 - v2;
|
||||
assert_relative_eq!(v3.to_m3_per_s(), 0.7, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume_flow_mul() {
|
||||
let v = VolumeFlow::from_m3_per_s(0.5);
|
||||
let v2 = v * 2.0;
|
||||
assert_relative_eq!(v2.to_m3_per_s(), 1.0, epsilon = 1e-10);
|
||||
|
||||
let v3 = 2.0 * v;
|
||||
assert_relative_eq!(v3.to_m3_per_s(), 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume_flow_div() {
|
||||
let v = VolumeFlow::from_m3_per_s(1.0);
|
||||
let v2 = v / 4.0;
|
||||
assert_relative_eq!(v2.to_m3_per_s(), 0.25, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume_flow_edge_cases() {
|
||||
let zero = VolumeFlow::from_m3_per_s(0.0);
|
||||
assert_relative_eq!(zero.to_m3_per_s(), 0.0, epsilon = 1e-10);
|
||||
|
||||
let negative = VolumeFlow::from_m3_per_s(-1.0);
|
||||
assert_relative_eq!(negative.to_m3_per_s(), -1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
// ==================== RELATIVE HUMIDITY TESTS ====================
|
||||
|
||||
#[test]
|
||||
fn test_relative_humidity_from_fraction() {
|
||||
let rh = RelativeHumidity::from_fraction(0.6);
|
||||
assert_relative_eq!(rh.0, 0.6, epsilon = 1e-10);
|
||||
assert_relative_eq!(rh.to_fraction(), 0.6, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_humidity_from_percent() {
|
||||
let rh = RelativeHumidity::from_percent(60.0);
|
||||
assert_relative_eq!(rh.to_fraction(), 0.6, epsilon = 1e-10);
|
||||
assert_relative_eq!(rh.to_percent(), 60.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_humidity_clamping_negative() {
|
||||
let rh = RelativeHumidity::from_fraction(-0.5);
|
||||
assert_relative_eq!(rh.to_fraction(), 0.0, epsilon = 1e-10);
|
||||
|
||||
let rh2 = RelativeHumidity::from_percent(-10.0);
|
||||
assert_relative_eq!(rh2.to_fraction(), 0.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_humidity_clamping_over_one() {
|
||||
let rh = RelativeHumidity::from_fraction(1.5);
|
||||
assert_relative_eq!(rh.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
|
||||
let rh2 = RelativeHumidity::from_percent(150.0);
|
||||
assert_relative_eq!(rh2.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_humidity_display() {
|
||||
let rh = RelativeHumidity::from_fraction(0.6);
|
||||
assert_eq!(format!("{}", rh), "60% RH");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_humidity_from_f64() {
|
||||
let rh: RelativeHumidity = 0.6.into();
|
||||
assert_relative_eq!(rh.to_fraction(), 0.6, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_humidity_from_f64_clamping() {
|
||||
let rh: RelativeHumidity = (-0.5).into();
|
||||
assert_relative_eq!(rh.to_fraction(), 0.0, epsilon = 1e-10);
|
||||
|
||||
let rh2: RelativeHumidity = 1.5.into();
|
||||
assert_relative_eq!(rh2.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_humidity_add() {
|
||||
let rh1 = RelativeHumidity::from_fraction(0.3);
|
||||
let rh2 = RelativeHumidity::from_fraction(0.4);
|
||||
let rh3 = rh1 + rh2;
|
||||
assert_relative_eq!(rh3.to_fraction(), 0.7, epsilon = 1e-10);
|
||||
|
||||
let rh4 = RelativeHumidity::from_fraction(0.8);
|
||||
let rh5 = RelativeHumidity::from_fraction(0.5);
|
||||
let rh6 = rh4 + rh5;
|
||||
assert_relative_eq!(rh6.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_humidity_sub() {
|
||||
let rh1 = RelativeHumidity::from_fraction(0.7);
|
||||
let rh2 = RelativeHumidity::from_fraction(0.3);
|
||||
let rh3 = rh1 - rh2;
|
||||
assert_relative_eq!(rh3.to_fraction(), 0.4, epsilon = 1e-10);
|
||||
|
||||
let rh4 = RelativeHumidity::from_fraction(0.2);
|
||||
let rh5 = RelativeHumidity::from_fraction(0.5);
|
||||
let rh6 = rh4 - rh5;
|
||||
assert_relative_eq!(rh6.to_fraction(), 0.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_humidity_mul() {
|
||||
let rh = RelativeHumidity::from_fraction(0.5);
|
||||
let rh2 = rh * 2.0;
|
||||
assert_relative_eq!(rh2.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
|
||||
let rh3 = 2.0 * rh;
|
||||
assert_relative_eq!(rh3.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_humidity_div() {
|
||||
let rh = RelativeHumidity::from_fraction(0.8);
|
||||
let rh2 = rh / 2.0;
|
||||
assert_relative_eq!(rh2.to_fraction(), 0.4, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_humidity_edge_cases() {
|
||||
let zero = RelativeHumidity::from_fraction(0.0);
|
||||
assert_relative_eq!(zero.to_fraction(), 0.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(zero.to_percent(), 0.0, epsilon = 1e-10);
|
||||
|
||||
let one = RelativeHumidity::from_fraction(1.0);
|
||||
assert_relative_eq!(one.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(one.to_percent(), 100.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
// ==================== VAPOR QUALITY TESTS ====================
|
||||
|
||||
#[test]
|
||||
fn test_vapor_quality_from_fraction() {
|
||||
let q = VaporQuality::from_fraction(0.5);
|
||||
assert_relative_eq!(q.0, 0.5, epsilon = 1e-10);
|
||||
assert_relative_eq!(q.to_fraction(), 0.5, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vapor_quality_from_percent() {
|
||||
let q = VaporQuality::from_percent(50.0);
|
||||
assert_relative_eq!(q.to_fraction(), 0.5, epsilon = 1e-10);
|
||||
assert_relative_eq!(q.to_percent(), 50.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vapor_quality_constants() {
|
||||
assert_relative_eq!(VaporQuality::SATURATED_LIQUID.0, 0.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(VaporQuality::SATURATED_VAPOR.0, 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vapor_quality_is_saturated_liquid() {
|
||||
let q = VaporQuality::SATURATED_LIQUID;
|
||||
assert!(q.is_saturated_liquid());
|
||||
assert!(!q.is_saturated_vapor());
|
||||
|
||||
let q2 = VaporQuality::from_fraction(1e-10);
|
||||
assert!(q2.is_saturated_liquid());
|
||||
|
||||
let q3 = VaporQuality::from_fraction(0.001);
|
||||
assert!(!q3.is_saturated_liquid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vapor_quality_is_saturated_vapor() {
|
||||
let q = VaporQuality::SATURATED_VAPOR;
|
||||
assert!(q.is_saturated_vapor());
|
||||
assert!(!q.is_saturated_liquid());
|
||||
|
||||
let q2 = VaporQuality::from_fraction(1.0 - 1e-10);
|
||||
assert!(q2.is_saturated_vapor());
|
||||
|
||||
let q3 = VaporQuality::from_fraction(0.999);
|
||||
assert!(!q3.is_saturated_vapor());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vapor_quality_clamping_negative() {
|
||||
let q = VaporQuality::from_fraction(-0.5);
|
||||
assert_relative_eq!(q.to_fraction(), 0.0, epsilon = 1e-10);
|
||||
|
||||
let q2 = VaporQuality::from_percent(-10.0);
|
||||
assert_relative_eq!(q2.to_fraction(), 0.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vapor_quality_clamping_over_one() {
|
||||
let q = VaporQuality::from_fraction(1.5);
|
||||
assert_relative_eq!(q.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
|
||||
let q2 = VaporQuality::from_percent(150.0);
|
||||
assert_relative_eq!(q2.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vapor_quality_display() {
|
||||
let q = VaporQuality::from_fraction(0.5);
|
||||
assert_eq!(format!("{}", q), "0.5 (quality)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vapor_quality_from_f64() {
|
||||
let q: VaporQuality = 0.5.into();
|
||||
assert_relative_eq!(q.to_fraction(), 0.5, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vapor_quality_from_f64_clamping() {
|
||||
let q: VaporQuality = (-0.5).into();
|
||||
assert_relative_eq!(q.to_fraction(), 0.0, epsilon = 1e-10);
|
||||
|
||||
let q2: VaporQuality = 1.5.into();
|
||||
assert_relative_eq!(q2.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vapor_quality_add() {
|
||||
let q1 = VaporQuality::from_fraction(0.3);
|
||||
let q2 = VaporQuality::from_fraction(0.4);
|
||||
let q3 = q1 + q2;
|
||||
assert_relative_eq!(q3.to_fraction(), 0.7, epsilon = 1e-10);
|
||||
|
||||
let q4 = VaporQuality::from_fraction(0.8);
|
||||
let q5 = VaporQuality::from_fraction(0.5);
|
||||
let q6 = q4 + q5;
|
||||
assert_relative_eq!(q6.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vapor_quality_sub() {
|
||||
let q1 = VaporQuality::from_fraction(0.7);
|
||||
let q2 = VaporQuality::from_fraction(0.3);
|
||||
let q3 = q1 - q2;
|
||||
assert_relative_eq!(q3.to_fraction(), 0.4, epsilon = 1e-10);
|
||||
|
||||
let q4 = VaporQuality::from_fraction(0.2);
|
||||
let q5 = VaporQuality::from_fraction(0.5);
|
||||
let q6 = q4 - q5;
|
||||
assert_relative_eq!(q6.to_fraction(), 0.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vapor_quality_mul() {
|
||||
let q = VaporQuality::from_fraction(0.5);
|
||||
let q2 = q * 2.0;
|
||||
assert_relative_eq!(q2.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
|
||||
let q3 = 2.0 * q;
|
||||
assert_relative_eq!(q3.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vapor_quality_div() {
|
||||
let q = VaporQuality::from_fraction(0.8);
|
||||
let q2 = q / 2.0;
|
||||
assert_relative_eq!(q2.to_fraction(), 0.4, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vapor_quality_edge_cases() {
|
||||
let zero = VaporQuality::from_fraction(0.0);
|
||||
assert_relative_eq!(zero.to_fraction(), 0.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(zero.to_percent(), 0.0, epsilon = 1e-10);
|
||||
assert!(zero.is_saturated_liquid());
|
||||
|
||||
let one = VaporQuality::from_fraction(1.0);
|
||||
assert_relative_eq!(one.to_fraction(), 1.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(one.to_percent(), 100.0, epsilon = 1e-10);
|
||||
assert!(one.is_saturated_vapor());
|
||||
}
|
||||
|
||||
// ==================== PRESSURE TESTS ====================
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user