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:
Sepehr 2026-02-14 15:25:30 +01:00
parent dd8697b07b
commit be70a7a6c7
5 changed files with 811 additions and 2 deletions

View File

@ -1,7 +1,7 @@
[workspace] [workspace]
members = [ members = [
"crates/components", "crates/components",
# "crates/core", # Will be added in future stories "crates/core",
# "crates/solver", # Will be added in future stories # "crates/solver", # Will be added in future stories
] ]
resolver = "2" resolver = "2"

View File

@ -44,7 +44,7 @@ development_status:
# Epic 1: Extensible Component Framework # Epic 1: Extensible Component Framework
epic-1: in-progress epic-1: in-progress
1-1-component-trait-definition: done 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-3-port-and-connection-system: backlog
1-4-compressor-component-ahri-540: backlog 1-4-compressor-component-ahri-540: backlog
1-5-generic-heat-exchanger-framework: backlog 1-5-generic-heat-exchanger-framework: backlog

15
crates/core/Cargo.toml Normal file
View 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
View 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
View 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);
}
}