use uom::si::f64 as si;
use uom::si::{
acceleration::meter_per_second_squared, angular_momentum::kilogram_square_meter_per_second,
energy::joule, force::newton, length::meter, mass::kilogram, pressure::pascal,
thermodynamic_temperature::kelvin as temperature_kelvin, time::second,
velocity::meter_per_second,
};
pub type Mass = si::Mass;
pub type Length = si::Length;
pub type Time = si::Time;
pub type Velocity = si::Velocity;
pub type Acceleration = si::Acceleration;
pub type Force = si::Force;
pub type Pressure = si::Pressure;
pub type Energy = si::Energy;
pub type ThermodynamicTemperature = si::ThermodynamicTemperature;
pub type AngularMomentum = si::AngularMomentum;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Entropy {
value_jk: f64,
}
impl Entropy {
pub fn from_joules_per_kelvin(jk: f64) -> Self {
Self { value_jk: jk }
}
pub fn get_joules_per_kelvin(self) -> f64 {
self.value_jk
}
pub fn reversible_change(
heat: &Energy,
temperature: &ThermodynamicTemperature,
) -> Option<Self> {
let t = temperature.get::<temperature_kelvin>();
if t <= 0.0 {
return None;
}
Some(Self::from_joules_per_kelvin(heat.get::<joule>() / t))
}
}
impl std::ops::Add for Entropy {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self::from_joules_per_kelvin(self.value_jk + rhs.value_jk)
}
}
impl std::ops::Sub for Entropy {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self::from_joules_per_kelvin(self.value_jk - rhs.value_jk)
}
}
pub fn mass_kg(kg: f64) -> Mass {
si::Mass::new::<kilogram>(kg)
}
pub fn length_m(m: f64) -> Length {
si::Length::new::<meter>(m)
}
pub fn time_s(s: f64) -> Time {
si::Time::new::<second>(s)
}
pub fn velocity_ms(ms: f64) -> Velocity {
si::Velocity::new::<meter_per_second>(ms)
}
pub fn acceleration_ms2(a: f64) -> Acceleration {
si::Acceleration::new::<meter_per_second_squared>(a)
}
pub fn force_n(n: f64) -> Force {
si::Force::new::<newton>(n)
}
pub fn pressure_pa(pa: f64) -> Pressure {
si::Pressure::new::<pascal>(pa)
}
pub fn energy_j(j: f64) -> Energy {
si::Energy::new::<joule>(j)
}
pub fn temperature_k(k: f64) -> ThermodynamicTemperature {
si::ThermodynamicTemperature::new::<temperature_kelvin>(k)
}
pub fn angular_momentum(l: f64) -> AngularMomentum {
si::AngularMomentum::new::<kilogram_square_meter_per_second>(l)
}
pub fn to_kg(m: &Mass) -> f64 {
m.get::<kilogram>()
}
pub fn to_m(l: &Length) -> f64 {
l.get::<meter>()
}
pub fn to_s(t: &Time) -> f64 {
t.get::<second>()
}
pub fn to_ms(v: &Velocity) -> f64 {
v.get::<meter_per_second>()
}
pub fn to_ms2(a: &Acceleration) -> f64 {
a.get::<meter_per_second_squared>()
}
pub fn to_n(f: &Force) -> f64 {
f.get::<newton>()
}
pub fn to_pa(p: &Pressure) -> f64 {
p.get::<pascal>()
}
pub fn to_joules(e: &Energy) -> f64 {
e.get::<joule>()
}
pub fn to_kelvin(t: &ThermodynamicTemperature) -> f64 {
t.get::<temperature_kelvin>()
}
pub fn to_angular_momentum(l: &AngularMomentum) -> f64 {
l.get::<kilogram_square_meter_per_second>()
}
pub fn kinetic_energy(mass: &Mass, velocity: &Velocity) -> Energy {
let ke_j = 0.5 * to_kg(mass) * to_ms(velocity).powi(2);
energy_j(ke_j)
}
pub fn gravitational_pe(mass: &Mass, height: &Length, g: f64) -> Energy {
energy_j(to_kg(mass) * g * to_m(height))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mass_round_trip() {
let m = mass_kg(2.5);
assert!((to_kg(&m) - 2.5).abs() < 1e-12);
}
#[test]
fn test_energy_round_trip() {
let e = energy_j(1000.0);
assert!((to_joules(&e) - 1000.0).abs() < 1e-12);
}
#[test]
fn test_temperature_round_trip() {
let t = temperature_k(300.0);
assert!((to_kelvin(&t) - 300.0).abs() < 1e-12);
}
#[test]
fn test_kinetic_energy_formula() {
let m = mass_kg(2.0);
let v = velocity_ms(3.0);
let ke = kinetic_energy(&m, &v);
assert!((to_joules(&ke) - 9.0).abs() < 1e-10);
}
#[test]
fn test_gravitational_pe_formula() {
let m = mass_kg(1.0);
let h = length_m(10.0);
let pe = gravitational_pe(&m, &h, 9.81);
assert!((to_joules(&pe) - 98.1).abs() < 1e-10);
}
#[test]
fn test_entropy_from_heat_and_temperature() {
let q = energy_j(300.0);
let t = temperature_k(300.0);
let ds = Entropy::reversible_change(&q, &t).unwrap();
assert!((ds.get_joules_per_kelvin() - 1.0).abs() < 1e-10);
}
#[test]
fn test_entropy_zero_temperature_returns_none() {
let q = energy_j(100.0);
let t = temperature_k(0.0);
assert!(Entropy::reversible_change(&q, &t).is_none());
}
#[test]
fn test_entropy_addition() {
let s1 = Entropy::from_joules_per_kelvin(2.0);
let s2 = Entropy::from_joules_per_kelvin(3.0);
let s3 = s1 + s2;
assert!((s3.get_joules_per_kelvin() - 5.0).abs() < 1e-12);
}
#[test]
fn test_angular_momentum_round_trip() {
let l = angular_momentum(4.2);
assert!((to_angular_momentum(&l) - 4.2).abs() < 1e-12);
}
#[test]
fn test_si_unit_mismatch_would_not_compile() {
let _ = (); }
}