feat(core): implement physical types with NewType pattern
Story 1.2: Physical Types (NewType Pattern) - Add Pressure, Temperature, Enthalpy, MassFlow types - Implement SI base units with conversion methods - Add arithmetic operations (Add, Sub, Mul, Div) - Add Display and Debug traits - Comprehensive unit tests (37 tests) - Add PSI and Fahrenheit conversions - Code review fixes applied All tests passing, clippy clean
This commit is contained in:
parent
dd8697b07b
commit
be70a7a6c7
@ -1,7 +1,7 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/components",
|
||||
# "crates/core", # Will be added in future stories
|
||||
"crates/core",
|
||||
# "crates/solver", # Will be added in future stories
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@ -44,7 +44,7 @@ development_status:
|
||||
# Epic 1: Extensible Component Framework
|
||||
epic-1: in-progress
|
||||
1-1-component-trait-definition: done
|
||||
1-2-physical-types-newtype-pattern: backlog
|
||||
1-2-physical-types-newtype-pattern: done
|
||||
1-3-port-and-connection-system: backlog
|
||||
1-4-compressor-component-ahri-540: backlog
|
||||
1-5-generic-heat-exchanger-framework: backlog
|
||||
|
||||
15
crates/core/Cargo.toml
Normal file
15
crates/core/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "entropyk-core"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Core types and primitives for Entropyk thermodynamic simulation library"
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
approx = "0.5"
|
||||
43
crates/core/src/lib.rs
Normal file
43
crates/core/src/lib.rs
Normal file
@ -0,0 +1,43 @@
|
||||
//! # Entropyk Core
|
||||
//!
|
||||
//! Core types and primitives for the Entropyk thermodynamic simulation library.
|
||||
//!
|
||||
//! This crate provides the foundation types used throughout the Entropyk ecosystem,
|
||||
//! including type-safe physical quantities via the NewType pattern.
|
||||
//!
|
||||
//! ## Physical Types
|
||||
//!
|
||||
//! All physical quantities use the NewType pattern to provide compile-time unit safety:
|
||||
//!
|
||||
//! - [`Pressure`] - Pressure in Pascals (Pa)
|
||||
//! - [`Temperature`] - Temperature in Kelvin (K)
|
||||
//! - [`Enthalpy`] - Specific enthalpy in Joules per kilogram (J/kg)
|
||||
//! - [`MassFlow`] - Mass flow rate in kilograms per second (kg/s)
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use entropyk_core::{Pressure, Temperature, Enthalpy, MassFlow};
|
||||
//!
|
||||
//! // Create values using constructors
|
||||
//! let pressure = Pressure::from_bar(1.0);
|
||||
//! let temperature = Temperature::from_celsius(25.0);
|
||||
//!
|
||||
//! // Convert to base units
|
||||
//! assert_eq!(pressure.to_pascals(), 100_000.0);
|
||||
//! assert_eq!(temperature.to_kelvin(), 298.15);
|
||||
//!
|
||||
//! // 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);
|
||||
//! ```
|
||||
|
||||
#![deny(warnings)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod types;
|
||||
|
||||
// Re-export all physical types for convenience
|
||||
pub use types::{Enthalpy, MassFlow, Pressure, Temperature};
|
||||
751
crates/core/src/types.rs
Normal file
751
crates/core/src/types.rs
Normal file
@ -0,0 +1,751 @@
|
||||
//! Physical types using the NewType pattern for compile-time unit safety.
|
||||
//!
|
||||
//! This module provides type-safe wrappers around `f64` for physical quantities,
|
||||
//! preventing accidental mixing of units at compile time.
|
||||
//!
|
||||
//! All types store values in SI base units internally:
|
||||
//! - Pressure: Pascals (Pa)
|
||||
//! - Temperature: Kelvin (K)
|
||||
//! - Enthalpy: Joules per kilogram (J/kg)
|
||||
//! - MassFlow: Kilograms per second (kg/s)
|
||||
|
||||
use std::fmt;
|
||||
use std::ops::{Add, Div, Mul, Sub};
|
||||
|
||||
/// Pressure in Pascals (Pa).
|
||||
///
|
||||
/// Internally stores the value in Pascals (SI base unit).
|
||||
/// Provides conversions to/from common units like bar.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_core::Pressure;
|
||||
///
|
||||
/// let p = Pressure::from_bar(1.0);
|
||||
/// assert_eq!(p.to_pascals(), 100_000.0);
|
||||
/// assert_eq!(p.to_bar(), 1.0);
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct Pressure(pub f64);
|
||||
|
||||
impl Pressure {
|
||||
/// Creates a Pressure from a value in Pascals.
|
||||
pub fn from_pascals(value: f64) -> Self {
|
||||
Pressure(value)
|
||||
}
|
||||
|
||||
/// Creates a Pressure from a value in bar.
|
||||
pub fn from_bar(value: f64) -> Self {
|
||||
Pressure(value * 100_000.0)
|
||||
}
|
||||
|
||||
/// Creates a Pressure from a value in PSI (pounds per square inch).
|
||||
pub fn from_psi(value: f64) -> Self {
|
||||
Pressure(value * 6894.75729)
|
||||
}
|
||||
|
||||
/// Returns the pressure in Pascals.
|
||||
pub fn to_pascals(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the pressure in bar.
|
||||
pub fn to_bar(&self) -> f64 {
|
||||
self.0 / 100_000.0
|
||||
}
|
||||
|
||||
/// Returns the pressure in PSI (pounds per square inch).
|
||||
pub fn to_psi(&self) -> f64 {
|
||||
self.0 / 6894.75729
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Pressure {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} Pa", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Pressure {
|
||||
fn from(value: f64) -> Self {
|
||||
Pressure(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Pressure> for Pressure {
|
||||
type Output = Pressure;
|
||||
|
||||
fn add(self, other: Pressure) -> Pressure {
|
||||
Pressure(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Pressure> for Pressure {
|
||||
type Output = Pressure;
|
||||
|
||||
fn sub(self, other: Pressure) -> Pressure {
|
||||
Pressure(self.0 - other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f64> for Pressure {
|
||||
type Output = Pressure;
|
||||
|
||||
fn mul(self, scalar: f64) -> Pressure {
|
||||
Pressure(self.0 * scalar)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Pressure> for f64 {
|
||||
type Output = Pressure;
|
||||
|
||||
fn mul(self, p: Pressure) -> Pressure {
|
||||
Pressure(self * p.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Pressure {
|
||||
type Output = Pressure;
|
||||
|
||||
fn div(self, scalar: f64) -> Pressure {
|
||||
Pressure(self.0 / scalar)
|
||||
}
|
||||
}
|
||||
|
||||
/// Temperature in Kelvin (K).
|
||||
///
|
||||
/// Internally stores the value in Kelvin (SI base unit).
|
||||
/// Provides conversions to/from Celsius.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_core::Temperature;
|
||||
///
|
||||
/// let t = Temperature::from_celsius(0.0);
|
||||
/// assert_eq!(t.to_kelvin(), 273.15);
|
||||
/// assert_eq!(t.to_celsius(), 0.0);
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct Temperature(pub f64);
|
||||
|
||||
impl Temperature {
|
||||
/// Creates a Temperature from a value in Kelvin.
|
||||
pub fn from_kelvin(value: f64) -> Self {
|
||||
Temperature(value)
|
||||
}
|
||||
|
||||
/// Creates a Temperature from a value in Celsius.
|
||||
pub fn from_celsius(value: f64) -> Self {
|
||||
Temperature(value + 273.15)
|
||||
}
|
||||
|
||||
/// Creates a Temperature from a value in Fahrenheit.
|
||||
pub fn from_fahrenheit(value: f64) -> Self {
|
||||
Temperature((value - 32.0) * 5.0 / 9.0 + 273.15)
|
||||
}
|
||||
|
||||
/// Returns the temperature in Kelvin.
|
||||
pub fn to_kelvin(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the temperature in Celsius.
|
||||
pub fn to_celsius(&self) -> f64 {
|
||||
self.0 - 273.15
|
||||
}
|
||||
|
||||
/// Returns the temperature in Fahrenheit.
|
||||
pub fn to_fahrenheit(&self) -> f64 {
|
||||
(self.0 - 273.15) * 9.0 / 5.0 + 32.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Temperature {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} K", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Temperature {
|
||||
fn from(value: f64) -> Self {
|
||||
Temperature(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Temperature> for Temperature {
|
||||
type Output = Temperature;
|
||||
|
||||
fn add(self, other: Temperature) -> Temperature {
|
||||
Temperature(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Temperature> for Temperature {
|
||||
type Output = Temperature;
|
||||
|
||||
fn sub(self, other: Temperature) -> Temperature {
|
||||
Temperature(self.0 - other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f64> for Temperature {
|
||||
type Output = Temperature;
|
||||
|
||||
fn mul(self, scalar: f64) -> Temperature {
|
||||
Temperature(self.0 * scalar)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Temperature> for f64 {
|
||||
type Output = Temperature;
|
||||
|
||||
fn mul(self, t: Temperature) -> Temperature {
|
||||
Temperature(self * t.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Temperature {
|
||||
type Output = Temperature;
|
||||
|
||||
fn div(self, scalar: f64) -> Temperature {
|
||||
Temperature(self.0 / scalar)
|
||||
}
|
||||
}
|
||||
|
||||
/// Specific enthalpy in Joules per kilogram (J/kg).
|
||||
///
|
||||
/// Internally stores the value in Joules per kilogram (SI base unit).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_core::Enthalpy;
|
||||
///
|
||||
/// let h = Enthalpy::from_joules_per_kg(1000.0);
|
||||
/// assert_eq!(h.to_joules_per_kg(), 1000.0);
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct Enthalpy(pub f64);
|
||||
|
||||
impl Enthalpy {
|
||||
/// Creates an Enthalpy from a value in Joules per kilogram.
|
||||
pub fn from_joules_per_kg(value: f64) -> Self {
|
||||
Enthalpy(value)
|
||||
}
|
||||
|
||||
/// Creates an Enthalpy from a value in kilojoules per kilogram.
|
||||
pub fn from_kilojoules_per_kg(value: f64) -> Self {
|
||||
Enthalpy(value * 1_000.0)
|
||||
}
|
||||
|
||||
/// Returns the enthalpy in Joules per kilogram.
|
||||
pub fn to_joules_per_kg(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the enthalpy in kilojoules per kilogram.
|
||||
pub fn to_kilojoules_per_kg(&self) -> f64 {
|
||||
self.0 / 1_000.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Enthalpy {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} J/kg", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Enthalpy {
|
||||
fn from(value: f64) -> Self {
|
||||
Enthalpy(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Enthalpy> for Enthalpy {
|
||||
type Output = Enthalpy;
|
||||
|
||||
fn add(self, other: Enthalpy) -> Enthalpy {
|
||||
Enthalpy(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Enthalpy> for Enthalpy {
|
||||
type Output = Enthalpy;
|
||||
|
||||
fn sub(self, other: Enthalpy) -> Enthalpy {
|
||||
Enthalpy(self.0 - other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f64> for Enthalpy {
|
||||
type Output = Enthalpy;
|
||||
|
||||
fn mul(self, scalar: f64) -> Enthalpy {
|
||||
Enthalpy(self.0 * scalar)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Enthalpy> for f64 {
|
||||
type Output = Enthalpy;
|
||||
|
||||
fn mul(self, h: Enthalpy) -> Enthalpy {
|
||||
Enthalpy(self * h.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Enthalpy {
|
||||
type Output = Enthalpy;
|
||||
|
||||
fn div(self, scalar: f64) -> Enthalpy {
|
||||
Enthalpy(self.0 / scalar)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mass flow rate in kilograms per second (kg/s).
|
||||
///
|
||||
/// Internally stores the value in kilograms per second (SI base unit).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_core::MassFlow;
|
||||
///
|
||||
/// let m = MassFlow::from_kg_per_s(0.5);
|
||||
/// assert_eq!(m.to_kg_per_s(), 0.5);
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct MassFlow(pub f64);
|
||||
|
||||
impl MassFlow {
|
||||
/// Creates a MassFlow from a value in kilograms per second.
|
||||
pub fn from_kg_per_s(value: f64) -> Self {
|
||||
MassFlow(value)
|
||||
}
|
||||
|
||||
/// Creates a MassFlow from a value in grams per second.
|
||||
pub fn from_grams_per_s(value: f64) -> Self {
|
||||
MassFlow(value / 1_000.0)
|
||||
}
|
||||
|
||||
/// Returns the mass flow rate in kilograms per second.
|
||||
pub fn to_kg_per_s(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the mass flow rate in grams per second.
|
||||
pub fn to_grams_per_s(&self) -> f64 {
|
||||
self.0 * 1_000.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MassFlow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} kg/s", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for MassFlow {
|
||||
fn from(value: f64) -> Self {
|
||||
MassFlow(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<MassFlow> for MassFlow {
|
||||
type Output = MassFlow;
|
||||
|
||||
fn add(self, other: MassFlow) -> MassFlow {
|
||||
MassFlow(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<MassFlow> for MassFlow {
|
||||
type Output = MassFlow;
|
||||
|
||||
fn sub(self, other: MassFlow) -> MassFlow {
|
||||
MassFlow(self.0 - other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f64> for MassFlow {
|
||||
type Output = MassFlow;
|
||||
|
||||
fn mul(self, scalar: f64) -> MassFlow {
|
||||
MassFlow(self.0 * scalar)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<MassFlow> for f64 {
|
||||
type Output = MassFlow;
|
||||
|
||||
fn mul(self, m: MassFlow) -> MassFlow {
|
||||
MassFlow(self * m.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for MassFlow {
|
||||
type Output = MassFlow;
|
||||
|
||||
fn div(self, scalar: f64) -> MassFlow {
|
||||
MassFlow(self.0 / scalar)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
// ==================== PRESSURE TESTS ====================
|
||||
|
||||
#[test]
|
||||
fn test_pressure_from_pascals() {
|
||||
let p = Pressure::from_pascals(101325.0);
|
||||
assert_relative_eq!(p.0, 101325.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(p.to_pascals(), 101325.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pressure_from_bar() {
|
||||
let p = Pressure::from_bar(1.0);
|
||||
assert_relative_eq!(p.to_pascals(), 100_000.0, epsilon = 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pressure_to_bar() {
|
||||
let p = Pressure::from_pascals(100_000.0);
|
||||
assert_relative_eq!(p.to_bar(), 1.0, epsilon = 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pressure_round_trip() {
|
||||
let p1 = Pressure::from_bar(2.5);
|
||||
let bar = p1.to_bar();
|
||||
assert_relative_eq!(bar, 2.5, epsilon = 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pressure_display() {
|
||||
let p = Pressure::from_pascals(101325.0);
|
||||
assert_eq!(format!("{}", p), "101325 Pa");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pressure_from_f64() {
|
||||
let p: Pressure = 101325.0.into();
|
||||
assert_relative_eq!(p.to_pascals(), 101325.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pressure_add() {
|
||||
let p1 = Pressure::from_pascals(100_000.0);
|
||||
let p2 = Pressure::from_pascals(50_000.0);
|
||||
let p3 = p1 + p2;
|
||||
assert_relative_eq!(p3.to_pascals(), 150_000.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pressure_sub() {
|
||||
let p1 = Pressure::from_pascals(100_000.0);
|
||||
let p2 = Pressure::from_pascals(30_000.0);
|
||||
let p3 = p1 - p2;
|
||||
assert_relative_eq!(p3.to_pascals(), 70_000.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pressure_mul() {
|
||||
let p = Pressure::from_pascals(50_000.0);
|
||||
let p2 = p * 2.0;
|
||||
assert_relative_eq!(p2.to_pascals(), 100_000.0, epsilon = 1e-10);
|
||||
|
||||
// Test reverse multiplication
|
||||
let p3 = 2.0 * p;
|
||||
assert_relative_eq!(p3.to_pascals(), 100_000.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pressure_div() {
|
||||
let p = Pressure::from_pascals(100_000.0);
|
||||
let p2 = p / 2.0;
|
||||
assert_relative_eq!(p2.to_pascals(), 50_000.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pressure_psi_conversions() {
|
||||
// 1 atm = 14.696 psi ≈ 101325 Pa
|
||||
let p = Pressure::from_psi(14.696);
|
||||
assert_relative_eq!(p.to_pascals(), 101325.0, epsilon = 1.0);
|
||||
assert_relative_eq!(p.to_psi(), 14.696, epsilon = 1e-3);
|
||||
|
||||
// Round trip test
|
||||
let p2 = Pressure::from_psi(100.0);
|
||||
assert_relative_eq!(p2.to_psi(), 100.0, epsilon = 1e-6);
|
||||
}
|
||||
|
||||
// ==================== TEMPERATURE TESTS ====================
|
||||
|
||||
#[test]
|
||||
fn test_temperature_from_kelvin() {
|
||||
let t = Temperature::from_kelvin(298.15);
|
||||
assert_relative_eq!(t.0, 298.15, epsilon = 1e-10);
|
||||
assert_relative_eq!(t.to_kelvin(), 298.15, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_temperature_from_celsius() {
|
||||
let t = Temperature::from_celsius(0.0);
|
||||
assert_relative_eq!(t.to_kelvin(), 273.15, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_temperature_to_celsius() {
|
||||
let t = Temperature::from_kelvin(273.15);
|
||||
assert_relative_eq!(t.to_celsius(), 0.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_temperature_round_trip() {
|
||||
let t1 = Temperature::from_celsius(25.0);
|
||||
let celsius = t1.to_celsius();
|
||||
assert_relative_eq!(celsius, 25.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_temperature_fahrenheit_conversions() {
|
||||
// 32°F = 0°C = 273.15K
|
||||
let t_freezing = Temperature::from_fahrenheit(32.0);
|
||||
assert_relative_eq!(t_freezing.to_celsius(), 0.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(t_freezing.to_kelvin(), 273.15, epsilon = 1e-10);
|
||||
|
||||
// 212°F = 100°C = 373.15K
|
||||
let t_boiling = Temperature::from_fahrenheit(212.0);
|
||||
assert_relative_eq!(t_boiling.to_celsius(), 100.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(t_boiling.to_fahrenheit(), 212.0, epsilon = 1e-10);
|
||||
|
||||
// Round trip
|
||||
let t1 = Temperature::from_kelvin(300.0);
|
||||
let f = t1.to_fahrenheit();
|
||||
let t2 = Temperature::from_fahrenheit(f);
|
||||
assert_relative_eq!(t1.to_kelvin(), t2.to_kelvin(), epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_temperature_display() {
|
||||
let t = Temperature::from_kelvin(298.15);
|
||||
assert_eq!(format!("{}", t), "298.15 K");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_temperature_add() {
|
||||
let t1 = Temperature::from_kelvin(300.0);
|
||||
let t2 = Temperature::from_kelvin(10.0);
|
||||
let t3 = t1 + t2;
|
||||
assert_relative_eq!(t3.to_kelvin(), 310.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_temperature_sub() {
|
||||
let t1 = Temperature::from_kelvin(300.0);
|
||||
let t2 = Temperature::from_kelvin(50.0);
|
||||
let t3 = t1 - t2;
|
||||
assert_relative_eq!(t3.to_kelvin(), 250.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_temperature_mul() {
|
||||
let t = Temperature::from_kelvin(100.0);
|
||||
let t2 = t * 2.0;
|
||||
assert_relative_eq!(t2.to_kelvin(), 200.0, epsilon = 1e-10);
|
||||
|
||||
// Test reverse multiplication
|
||||
let t3 = 2.0 * t;
|
||||
assert_relative_eq!(t3.to_kelvin(), 200.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_temperature_div() {
|
||||
let t = Temperature::from_kelvin(300.0);
|
||||
let t2 = t / 3.0;
|
||||
assert_relative_eq!(t2.to_kelvin(), 100.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
// ==================== ENTHALPY TESTS ====================
|
||||
|
||||
#[test]
|
||||
fn test_enthalpy_from_joules_per_kg() {
|
||||
let h = Enthalpy::from_joules_per_kg(1000.0);
|
||||
assert_relative_eq!(h.0, 1000.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(h.to_joules_per_kg(), 1000.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enthalpy_display() {
|
||||
let h = Enthalpy::from_joules_per_kg(250000.0);
|
||||
assert_eq!(format!("{}", h), "250000 J/kg");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enthalpy_arithmetic() {
|
||||
let h1 = Enthalpy::from_joules_per_kg(1000.0);
|
||||
let h2 = Enthalpy::from_joules_per_kg(500.0);
|
||||
let h3 = h1 + h2;
|
||||
assert_relative_eq!(h3.to_joules_per_kg(), 1500.0, epsilon = 1e-10);
|
||||
|
||||
let h4 = h1 - h2;
|
||||
assert_relative_eq!(h4.to_joules_per_kg(), 500.0, epsilon = 1e-10);
|
||||
|
||||
let h5 = h1 * 2.0;
|
||||
assert_relative_eq!(h5.to_joules_per_kg(), 2000.0, epsilon = 1e-10);
|
||||
|
||||
let h6 = h1 / 2.0;
|
||||
assert_relative_eq!(h6.to_joules_per_kg(), 500.0, epsilon = 1e-10);
|
||||
|
||||
// Test reverse multiplication
|
||||
let h7 = 2.0 * h1;
|
||||
assert_relative_eq!(h7.to_joules_per_kg(), 2000.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enthalpy_unit_conversions() {
|
||||
let h1 = Enthalpy::from_kilojoules_per_kg(100.0);
|
||||
assert_relative_eq!(h1.to_joules_per_kg(), 100_000.0, epsilon = 1e-6);
|
||||
assert_relative_eq!(h1.to_kilojoules_per_kg(), 100.0, epsilon = 1e-6);
|
||||
}
|
||||
|
||||
// ==================== MASS FLOW TESTS ====================
|
||||
|
||||
#[test]
|
||||
fn test_mass_flow_from_kg_per_s() {
|
||||
let m = MassFlow::from_kg_per_s(0.5);
|
||||
assert_relative_eq!(m.0, 0.5, epsilon = 1e-10);
|
||||
assert_relative_eq!(m.to_kg_per_s(), 0.5, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mass_flow_display() {
|
||||
let m = MassFlow::from_kg_per_s(0.1);
|
||||
assert_eq!(format!("{}", m), "0.1 kg/s");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mass_flow_arithmetic() {
|
||||
let m1 = MassFlow::from_kg_per_s(1.0);
|
||||
let m2 = MassFlow::from_kg_per_s(0.5);
|
||||
let m3 = m1 + m2;
|
||||
assert_relative_eq!(m3.to_kg_per_s(), 1.5, epsilon = 1e-10);
|
||||
|
||||
let m4 = m1 - m2;
|
||||
assert_relative_eq!(m4.to_kg_per_s(), 0.5, epsilon = 1e-10);
|
||||
|
||||
let m5 = m1 * 2.0;
|
||||
assert_relative_eq!(m5.to_kg_per_s(), 2.0, epsilon = 1e-10);
|
||||
|
||||
let m6 = m1 / 2.0;
|
||||
assert_relative_eq!(m6.to_kg_per_s(), 0.5, epsilon = 1e-10);
|
||||
|
||||
// Test reverse multiplication
|
||||
let m7 = 2.0 * m1;
|
||||
assert_relative_eq!(m7.to_kg_per_s(), 2.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mass_flow_unit_conversions() {
|
||||
let m1 = MassFlow::from_grams_per_s(500.0);
|
||||
assert_relative_eq!(m1.to_kg_per_s(), 0.5, epsilon = 1e-6);
|
||||
assert_relative_eq!(m1.to_grams_per_s(), 500.0, epsilon = 1e-6);
|
||||
}
|
||||
|
||||
// ==================== TYPE SAFETY TESTS ====================
|
||||
|
||||
#[test]
|
||||
fn test_pressure_not_equal_to_temperature() {
|
||||
// This test ensures Pressure and Temperature are distinct types
|
||||
let p = Pressure::from_pascals(100_000.0);
|
||||
let t = Temperature::from_kelvin(100.0);
|
||||
|
||||
// They should have different internal representations if units differ
|
||||
// But more importantly, this test would fail at compile time if we tried:
|
||||
// let x: Pressure = t; // Compile error!
|
||||
assert_ne!(p.0, t.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clone_and_copy() {
|
||||
let p1 = Pressure::from_pascals(100_000.0);
|
||||
let p2 = p1; // Copy
|
||||
let p3 = p1.clone(); // Clone
|
||||
|
||||
assert_relative_eq!(p1.to_pascals(), p2.to_pascals(), epsilon = 1e-10);
|
||||
assert_relative_eq!(p1.to_pascals(), p3.to_pascals(), epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_partial_ord() {
|
||||
let p1 = Pressure::from_pascals(100_000.0);
|
||||
let p2 = Pressure::from_pascals(200_000.0);
|
||||
|
||||
assert!(p1 < p2);
|
||||
assert!(p2 > p1);
|
||||
assert!(p1 == Pressure::from_pascals(100_000.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_partial_ord_with_nan() {
|
||||
let p1 = Pressure::from_pascals(100_000.0);
|
||||
let p_nan = Pressure(f64::NAN);
|
||||
|
||||
// NaN comparisons should return false for all ordering operators
|
||||
assert!(!(p_nan < p1));
|
||||
assert!(!(p_nan > p1));
|
||||
assert!(!(p_nan == p1));
|
||||
assert!(!(p1 < p_nan));
|
||||
assert!(!(p1 > p_nan));
|
||||
assert!(!(p1 == p_nan));
|
||||
|
||||
// NaN should not equal itself
|
||||
assert!(!(p_nan == p_nan));
|
||||
}
|
||||
|
||||
// ==================== EDGE CASES ====================
|
||||
|
||||
#[test]
|
||||
fn test_zero_values() {
|
||||
let p = Pressure::from_pascals(0.0);
|
||||
assert_relative_eq!(p.to_pascals(), 0.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(p.to_bar(), 0.0, epsilon = 1e-10);
|
||||
|
||||
let t = Temperature::from_kelvin(0.0);
|
||||
assert_relative_eq!(t.to_kelvin(), 0.0, epsilon = 1e-10);
|
||||
assert_relative_eq!(t.to_celsius(), -273.15, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative_values() {
|
||||
// Negative pressure doesn't make physical sense but is allowed by the type
|
||||
let p = Pressure::from_pascals(-1000.0);
|
||||
assert_relative_eq!(p.to_pascals(), -1000.0, epsilon = 1e-10);
|
||||
|
||||
// Negative temperature (below absolute zero) doesn't make sense
|
||||
let t = Temperature::from_kelvin(-1.0);
|
||||
assert_relative_eq!(t.to_kelvin(), -1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_very_large_values() {
|
||||
let p = Pressure::from_pascals(1e15);
|
||||
assert_relative_eq!(p.to_pascals(), 1e15, epsilon = 1e5);
|
||||
|
||||
let t = Temperature::from_kelvin(1e9);
|
||||
assert_relative_eq!(t.to_kelvin(), 1e9, epsilon = 1e-1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_very_small_values() {
|
||||
let p = Pressure::from_pascals(1e-10);
|
||||
assert_relative_eq!(p.to_pascals(), 1e-10, epsilon = 1e-15);
|
||||
|
||||
let m = MassFlow::from_kg_per_s(1e-12);
|
||||
assert_relative_eq!(m.to_kg_per_s(), 1e-12, epsilon = 1e-17);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user