qualia_core_db/solvers/units/
conversion.rs1use super::dimension::Dimension;
7use super::quantity::Quantity;
8use super::UnitsError;
9
10#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct Unit {
14 pub name: &'static str,
15 pub dimension: Dimension,
16 pub to_si_factor: f64,
17 pub to_si_offset: f64,
18}
19
20impl Unit {
21 pub const fn linear(name: &'static str, dimension: Dimension, factor: f64) -> Self {
22 Self {
23 name,
24 dimension,
25 to_si_factor: factor,
26 to_si_offset: 0.0,
27 }
28 }
29 pub const fn affine(
30 name: &'static str,
31 dimension: Dimension,
32 factor: f64,
33 offset: f64,
34 ) -> Self {
35 Self {
36 name,
37 dimension,
38 to_si_factor: factor,
39 to_si_offset: offset,
40 }
41 }
42
43 pub fn to_si(&self, value: f64) -> f64 {
45 value * self.to_si_factor + self.to_si_offset
46 }
47 pub fn from_si(&self, si: f64) -> f64 {
49 (si - self.to_si_offset) / self.to_si_factor
50 }
51
52 pub fn quantity(&self, value: f64) -> Quantity {
54 Quantity::new(self.to_si(value), self.dimension)
55 }
56
57 pub const METRE: Unit = Unit::linear("m", Dimension::LENGTH, 1.0);
59 pub const KILOMETRE: Unit = Unit::linear("km", Dimension::LENGTH, 1000.0);
60 pub const CENTIMETRE: Unit = Unit::linear("cm", Dimension::LENGTH, 0.01);
61 pub const MILLIMETRE: Unit = Unit::linear("mm", Dimension::LENGTH, 0.001);
62 pub const INCH: Unit = Unit::linear("in", Dimension::LENGTH, 0.0254);
63 pub const FOOT: Unit = Unit::linear("ft", Dimension::LENGTH, 0.3048);
64 pub const MILE: Unit = Unit::linear("mi", Dimension::LENGTH, 1609.344);
65 pub const KILOGRAM: Unit = Unit::linear("kg", Dimension::MASS, 1.0);
67 pub const GRAM: Unit = Unit::linear("g", Dimension::MASS, 0.001);
68 pub const POUND: Unit = Unit::linear("lb", Dimension::MASS, 0.45359237);
69 pub const SECOND: Unit = Unit::linear("s", Dimension::TIME, 1.0);
71 pub const MINUTE: Unit = Unit::linear("min", Dimension::TIME, 60.0);
72 pub const HOUR: Unit = Unit::linear("h", Dimension::TIME, 3600.0);
73 pub const NEWTON: Unit = Unit::linear("N", Dimension::FORCE, 1.0);
75 pub const JOULE: Unit = Unit::linear("J", Dimension::ENERGY, 1.0);
76 pub const KILOWATT_HOUR: Unit = Unit::linear("kWh", Dimension::ENERGY, 3.6e6);
77 pub const PASCAL: Unit = Unit::linear("Pa", Dimension::PRESSURE, 1.0);
78 pub const BAR: Unit = Unit::linear("bar", Dimension::PRESSURE, 1.0e5);
79 pub const KELVIN: Unit = Unit::linear("K", Dimension::TEMPERATURE, 1.0);
81 pub const CELSIUS: Unit = Unit::affine("°C", Dimension::TEMPERATURE, 1.0, 273.15);
82 pub const FAHRENHEIT: Unit = Unit::affine(
83 "°F",
84 Dimension::TEMPERATURE,
85 5.0 / 9.0,
86 255.372_222_222_222_2,
87 );
88}
89
90pub fn convert(value: f64, from: &Unit, to: &Unit) -> Result<f64, UnitsError> {
93 if from.dimension != to.dimension {
94 return Err(UnitsError::IncompatibleDimensions);
95 }
96 Ok(to.from_si(from.to_si(value)))
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 const EPS: f64 = 1e-6;
103
104 #[test]
105 fn length_conversions() {
106 assert!((convert(1.0, &Unit::INCH, &Unit::CENTIMETRE).unwrap() - 2.54).abs() < EPS);
107 assert!((convert(1.0, &Unit::MILE, &Unit::KILOMETRE).unwrap() - 1.609344).abs() < EPS);
108 assert!((convert(3.0, &Unit::FOOT, &Unit::METRE).unwrap() - 0.9144).abs() < EPS);
109 }
110
111 #[test]
112 fn temperature_is_affine() {
113 assert!((convert(0.0, &Unit::CELSIUS, &Unit::KELVIN).unwrap() - 273.15).abs() < EPS);
115 assert!((convert(100.0, &Unit::CELSIUS, &Unit::FAHRENHEIT).unwrap() - 212.0).abs() < 1e-3);
117 assert!((convert(32.0, &Unit::FAHRENHEIT, &Unit::CELSIUS).unwrap()).abs() < 1e-3);
119 assert!((convert(-40.0, &Unit::CELSIUS, &Unit::FAHRENHEIT).unwrap() + 40.0).abs() < 1e-3);
121 }
122
123 #[test]
124 fn energy_conversion() {
125 assert!((convert(1.0, &Unit::KILOWATT_HOUR, &Unit::JOULE).unwrap() - 3.6e6).abs() < 1.0);
127 }
128
129 #[test]
130 fn cross_dimension_conversion_fails_closed() {
131 assert_eq!(
132 convert(1.0, &Unit::METRE, &Unit::SECOND).unwrap_err(),
133 UnitsError::IncompatibleDimensions
134 );
135 }
136}