Skip to main content

qualia_core_db/solvers/units/
dimension.rs

1//! Physical dimension as the 7-vector of SI base-dimension exponents.
2//!
3//! Order: length (m), mass (kg), time (s), electric current (A), thermodynamic
4//! temperature (K), amount of substance (mol), luminous intensity (cd). A `Dimension`
5//! is these seven signed exponents; products add exponents, quotients subtract,
6//! powers scale. All-zero is dimensionless.
7
8/// The seven SI base-dimension exponents.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct Dimension {
11    pub exponents: [i8; 7],
12}
13
14/// Indices into the exponent vector.
15pub const LENGTH: usize = 0;
16pub const MASS: usize = 1;
17pub const TIME: usize = 2;
18pub const CURRENT: usize = 3;
19pub const TEMPERATURE: usize = 4;
20pub const AMOUNT: usize = 5;
21pub const LUMINOSITY: usize = 6;
22
23impl Dimension {
24    pub const fn new(exponents: [i8; 7]) -> Self {
25        Self { exponents }
26    }
27
28    /// A base dimension with exponent 1 at `index`.
29    const fn base(index: usize) -> Self {
30        let mut e = [0i8; 7];
31        e[index] = 1;
32        Self { exponents: e }
33    }
34
35    pub const DIMENSIONLESS: Dimension = Dimension::new([0; 7]);
36    pub const LENGTH: Dimension = Dimension::base(LENGTH);
37    pub const MASS: Dimension = Dimension::base(MASS);
38    pub const TIME: Dimension = Dimension::base(TIME);
39    pub const CURRENT: Dimension = Dimension::base(CURRENT);
40    pub const TEMPERATURE: Dimension = Dimension::base(TEMPERATURE);
41    pub const AMOUNT: Dimension = Dimension::base(AMOUNT);
42    pub const LUMINOSITY: Dimension = Dimension::base(LUMINOSITY);
43
44    /// Area = L², Volume = L³.
45    pub const AREA: Dimension = Dimension::new([2, 0, 0, 0, 0, 0, 0]);
46    pub const VOLUME: Dimension = Dimension::new([3, 0, 0, 0, 0, 0, 0]);
47    /// Velocity = L·T⁻¹, Acceleration = L·T⁻².
48    pub const VELOCITY: Dimension = Dimension::new([1, 0, -1, 0, 0, 0, 0]);
49    pub const ACCELERATION: Dimension = Dimension::new([1, 0, -2, 0, 0, 0, 0]);
50    /// Force = M·L·T⁻² (newton).
51    pub const FORCE: Dimension = Dimension::new([1, 1, -2, 0, 0, 0, 0]);
52    /// Energy = M·L²·T⁻² (joule); Power = M·L²·T⁻³ (watt).
53    pub const ENERGY: Dimension = Dimension::new([2, 1, -2, 0, 0, 0, 0]);
54    pub const POWER: Dimension = Dimension::new([2, 1, -3, 0, 0, 0, 0]);
55    /// Pressure = M·L⁻¹·T⁻² (pascal).
56    pub const PRESSURE: Dimension = Dimension::new([-1, 1, -2, 0, 0, 0, 0]);
57    /// Electric charge = T·A (coulomb).
58    pub const CHARGE: Dimension = Dimension::new([0, 0, 1, 1, 0, 0, 0]);
59    /// Frequency = T⁻¹ (hertz).
60    pub const FREQUENCY: Dimension = Dimension::new([0, 0, -1, 0, 0, 0, 0]);
61
62    pub fn is_dimensionless(&self) -> bool {
63        self.exponents == [0; 7]
64    }
65
66    /// Dimension of a product: exponents add (saturating to keep `i8`).
67    pub fn mul(&self, other: &Dimension) -> Dimension {
68        let mut e = [0i8; 7];
69        for i in 0..7 {
70            e[i] = self.exponents[i].saturating_add(other.exponents[i]);
71        }
72        Dimension { exponents: e }
73    }
74
75    /// Dimension of a quotient: exponents subtract.
76    pub fn div(&self, other: &Dimension) -> Dimension {
77        let mut e = [0i8; 7];
78        for i in 0..7 {
79            e[i] = self.exponents[i].saturating_sub(other.exponents[i]);
80        }
81        Dimension { exponents: e }
82    }
83
84    /// Dimension raised to an integer power: exponents scale.
85    pub fn powi(&self, n: i32) -> Dimension {
86        let mut e = [0i8; 7];
87        for i in 0..7 {
88            e[i] = (self.exponents[i] as i32 * n).clamp(i8::MIN as i32, i8::MAX as i32) as i8;
89        }
90        Dimension { exponents: e }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn products_and_quotients_compose() {
100        // velocity = length / time
101        assert_eq!(Dimension::LENGTH.div(&Dimension::TIME), Dimension::VELOCITY);
102        // force × length = energy (work)
103        assert_eq!(Dimension::FORCE.mul(&Dimension::LENGTH), Dimension::ENERGY);
104        // energy / time = power
105        assert_eq!(Dimension::ENERGY.div(&Dimension::TIME), Dimension::POWER);
106        // force / area = pressure
107        assert_eq!(Dimension::FORCE.div(&Dimension::AREA), Dimension::PRESSURE);
108    }
109
110    #[test]
111    fn powers_scale_exponents() {
112        assert_eq!(Dimension::LENGTH.powi(2), Dimension::AREA);
113        assert_eq!(Dimension::LENGTH.powi(3), Dimension::VOLUME);
114        // velocity² has dimension L²T⁻².
115        assert_eq!(
116            Dimension::VELOCITY.powi(2),
117            Dimension::new([2, 0, -2, 0, 0, 0, 0])
118        );
119    }
120
121    #[test]
122    fn dimensionless_detection() {
123        assert!(Dimension::DIMENSIONLESS.is_dimensionless());
124        // velocity / velocity = dimensionless
125        assert!(Dimension::VELOCITY
126            .div(&Dimension::VELOCITY)
127            .is_dimensionless());
128        assert!(!Dimension::FORCE.is_dimensionless());
129    }
130}