oxirs_physics/
uom_quantities.rs1use uom::si::f64 as si;
39use uom::si::{
40 acceleration::meter_per_second_squared, angular_momentum::kilogram_square_meter_per_second,
41 energy::joule, force::newton, length::meter, mass::kilogram, pressure::pascal,
42 thermodynamic_temperature::kelvin as temperature_kelvin, time::second,
43 velocity::meter_per_second,
44};
45
46pub type Mass = si::Mass;
52
53pub type Length = si::Length;
55
56pub type Time = si::Time;
58
59pub type Velocity = si::Velocity;
61
62pub type Acceleration = si::Acceleration;
64
65pub type Force = si::Force;
67
68pub type Pressure = si::Pressure;
70
71pub type Energy = si::Energy;
73
74pub type ThermodynamicTemperature = si::ThermodynamicTemperature;
76
77pub type AngularMomentum = si::AngularMomentum;
79
80#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
89pub struct Entropy {
90 value_jk: f64,
92}
93
94impl Entropy {
95 pub fn from_joules_per_kelvin(jk: f64) -> Self {
97 Self { value_jk: jk }
98 }
99
100 pub fn get_joules_per_kelvin(self) -> f64 {
102 self.value_jk
103 }
104
105 pub fn reversible_change(
109 heat: &Energy,
110 temperature: &ThermodynamicTemperature,
111 ) -> Option<Self> {
112 let t = temperature.get::<temperature_kelvin>();
113 if t <= 0.0 {
114 return None;
115 }
116 Some(Self::from_joules_per_kelvin(heat.get::<joule>() / t))
117 }
118}
119
120impl std::ops::Add for Entropy {
121 type Output = Self;
122 fn add(self, rhs: Self) -> Self {
123 Self::from_joules_per_kelvin(self.value_jk + rhs.value_jk)
124 }
125}
126
127impl std::ops::Sub for Entropy {
128 type Output = Self;
129 fn sub(self, rhs: Self) -> Self {
130 Self::from_joules_per_kelvin(self.value_jk - rhs.value_jk)
131 }
132}
133
134pub fn mass_kg(kg: f64) -> Mass {
140 si::Mass::new::<kilogram>(kg)
141}
142
143pub fn length_m(m: f64) -> Length {
145 si::Length::new::<meter>(m)
146}
147
148pub fn time_s(s: f64) -> Time {
150 si::Time::new::<second>(s)
151}
152
153pub fn velocity_ms(ms: f64) -> Velocity {
155 si::Velocity::new::<meter_per_second>(ms)
156}
157
158pub fn acceleration_ms2(a: f64) -> Acceleration {
160 si::Acceleration::new::<meter_per_second_squared>(a)
161}
162
163pub fn force_n(n: f64) -> Force {
165 si::Force::new::<newton>(n)
166}
167
168pub fn pressure_pa(pa: f64) -> Pressure {
170 si::Pressure::new::<pascal>(pa)
171}
172
173pub fn energy_j(j: f64) -> Energy {
175 si::Energy::new::<joule>(j)
176}
177
178pub fn temperature_k(k: f64) -> ThermodynamicTemperature {
180 si::ThermodynamicTemperature::new::<temperature_kelvin>(k)
181}
182
183pub fn angular_momentum(l: f64) -> AngularMomentum {
185 si::AngularMomentum::new::<kilogram_square_meter_per_second>(l)
186}
187
188pub fn to_kg(m: &Mass) -> f64 {
194 m.get::<kilogram>()
195}
196
197pub fn to_m(l: &Length) -> f64 {
199 l.get::<meter>()
200}
201
202pub fn to_s(t: &Time) -> f64 {
204 t.get::<second>()
205}
206
207pub fn to_ms(v: &Velocity) -> f64 {
209 v.get::<meter_per_second>()
210}
211
212pub fn to_ms2(a: &Acceleration) -> f64 {
214 a.get::<meter_per_second_squared>()
215}
216
217pub fn to_n(f: &Force) -> f64 {
219 f.get::<newton>()
220}
221
222pub fn to_pa(p: &Pressure) -> f64 {
224 p.get::<pascal>()
225}
226
227pub fn to_joules(e: &Energy) -> f64 {
229 e.get::<joule>()
230}
231
232pub fn to_kelvin(t: &ThermodynamicTemperature) -> f64 {
234 t.get::<temperature_kelvin>()
235}
236
237pub fn to_angular_momentum(l: &AngularMomentum) -> f64 {
239 l.get::<kilogram_square_meter_per_second>()
240}
241
242pub fn kinetic_energy(mass: &Mass, velocity: &Velocity) -> Energy {
248 let ke_j = 0.5 * to_kg(mass) * to_ms(velocity).powi(2);
249 energy_j(ke_j)
250}
251
252pub fn gravitational_pe(mass: &Mass, height: &Length, g: f64) -> Energy {
254 energy_j(to_kg(mass) * g * to_m(height))
255}
256
257#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn test_mass_round_trip() {
267 let m = mass_kg(2.5);
268 assert!((to_kg(&m) - 2.5).abs() < 1e-12);
269 }
270
271 #[test]
272 fn test_energy_round_trip() {
273 let e = energy_j(1000.0);
274 assert!((to_joules(&e) - 1000.0).abs() < 1e-12);
275 }
276
277 #[test]
278 fn test_temperature_round_trip() {
279 let t = temperature_k(300.0);
280 assert!((to_kelvin(&t) - 300.0).abs() < 1e-12);
281 }
282
283 #[test]
284 fn test_kinetic_energy_formula() {
285 let m = mass_kg(2.0);
287 let v = velocity_ms(3.0);
288 let ke = kinetic_energy(&m, &v);
289 assert!((to_joules(&ke) - 9.0).abs() < 1e-10);
290 }
291
292 #[test]
293 fn test_gravitational_pe_formula() {
294 let m = mass_kg(1.0);
296 let h = length_m(10.0);
297 let pe = gravitational_pe(&m, &h, 9.81);
298 assert!((to_joules(&pe) - 98.1).abs() < 1e-10);
299 }
300
301 #[test]
302 fn test_entropy_from_heat_and_temperature() {
303 let q = energy_j(300.0);
305 let t = temperature_k(300.0);
306 let ds = Entropy::reversible_change(&q, &t).unwrap();
307 assert!((ds.get_joules_per_kelvin() - 1.0).abs() < 1e-10);
308 }
309
310 #[test]
311 fn test_entropy_zero_temperature_returns_none() {
312 let q = energy_j(100.0);
313 let t = temperature_k(0.0);
314 assert!(Entropy::reversible_change(&q, &t).is_none());
315 }
316
317 #[test]
318 fn test_entropy_addition() {
319 let s1 = Entropy::from_joules_per_kelvin(2.0);
320 let s2 = Entropy::from_joules_per_kelvin(3.0);
321 let s3 = s1 + s2;
322 assert!((s3.get_joules_per_kelvin() - 5.0).abs() < 1e-12);
323 }
324
325 #[test]
326 fn test_angular_momentum_round_trip() {
327 let l = angular_momentum(4.2);
328 assert!((to_angular_momentum(&l) - 4.2).abs() < 1e-12);
329 }
330
331 #[test]
332 fn test_si_unit_mismatch_would_not_compile() {
333 let _ = (); }
339}