ndarray_unit/
unit.rs

1//! This module provides a struct being a simple representation of the international measure system and an Enum containing the seven base unit of the Internation SI plus other (radian, currency, pop...).
2//! It allows to multiply and divise unit between them and to print them.
3//! Several utility functions are provided to construct your own derived Unit from the base ones
4//!
5//! # Exemples
6//!
7//! ```
8//! use ndarray_unit;
9//! use ndarray_unit::{Unit, BaseUnit};
10//!
11//! let meter_per_sec = Unit::from_vec(vec![(BaseUnit::METER, 1), (BaseUnit::SECOND, -1)]);
12//! println!("meter_per_sec = {}", meter_per_sec);
13//!
14//! let acceleration = &meter_per_sec / &ndarray_unit::get_second();
15//! println!("acceleration = {}", acceleration);
16//! ```
17//! **Output**
18//! ```
19//! // meter_per_sec = m·s⁻¹
20//! // acceleration = m·s⁻²
21//! ```
22
23use std::collections::HashMap;
24use std::fmt::{Display, Formatter, Result};
25use std::ops::{Div, Mul};
26
27/// An enum representing the seven units of the **International System of Units**
28#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
29pub enum BaseUnit {
30    METER,
31    SECOND,
32    KILOGRAM,
33    AMPERE,
34    KELVIN,
35    MOLE,
36    CANDELA,
37
38    // others useful
39    RADIAN,
40    STERADIAN,
41
42    // economics indicator
43    CURRENCY,
44    INHABITANT,
45    BIRTH,
46    DEATH,
47}
48
49#[derive(Debug, Clone)]
50pub struct Unit {
51    /// A HashMap containing the base units of the Unit as well as their power
52    base_units: HashMap<BaseUnit, i32>,
53}
54
55impl Unit {
56    /// Return an Unit with an empty set of `BaseUnit`
57    pub fn new() -> Unit {
58        Unit {
59            base_units: HashMap::new(),
60        }
61    }
62
63    /// Create a Unit from a vector of BaseUnit and their power
64    /// # Examples
65    ///
66    /// ```
67    /// use ndarray_unit::Unit;
68    /// use ndarray_unit::BaseUnit;
69    ///
70    /// let candela_square_divided_by_ampere_cube = Unit::from_vec(vec![(BaseUnit::CANDELA, 2), (BaseUnit::AMPERE, -3)]);
71    /// println!("{}", candela_square_divided_by_ampere_cube)
72    /// ```
73    /// **Output**
74    /// ```
75    /// // cd²·A⁻³
76    /// ```
77
78    pub fn from_vec(list: Vec<(BaseUnit, i32)>) -> Unit {
79        let mut res = Unit::new();
80        for (unit, count) in list {
81            res.add_single_unit(unit, count);
82        }
83        res
84    }
85
86    pub fn get_base_units(&self) -> &HashMap<BaseUnit, i32> {
87        &self.base_units
88    }
89
90    /// Given an `Unit`, return the inverse of this unit.
91    /// It inverse the sign of the power of every BaseUnit of the given input Struct
92    ///
93    /// # Exemples
94    ///
95    /// ```
96    /// use ndarray_unit::Unit;
97    /// use ndarray_unit::BaseUnit;
98    ///
99    /// let unit = Unit::from_vec(vec![(BaseUnit::KELVIN, 2), (BaseUnit::MOLE, -3)]);
100    /// let inverse = Unit::from_vec(vec![(BaseUnit::KELVIN, -2), (BaseUnit::MOLE, 3)]);
101    ///
102    /// assert_eq!(inverse, unit.get_inverse());
103    /// ```
104    pub fn get_inverse(&self) -> Unit {
105        let mut hashmap: HashMap<BaseUnit, i32> = HashMap::new();
106        for (unit, count) in self.base_units.iter() {
107            hashmap.insert(*unit, -count);
108        }
109        Unit {
110            base_units: hashmap,
111        }
112    }
113
114    /// Add a BaseUnit (and its power) to an existing `mut Unit`
115    /// # Examples
116    ///
117    /// ```
118    /// use ndarray_unit::Unit;
119    /// use ndarray_unit::BaseUnit;
120    ///
121    /// let mut meter = Unit::new();
122    /// meter.add_single_unit(BaseUnit::METER, 1);
123    ///
124    /// let mut meter_per_second = meter.clone();
125    /// meter_per_second.add_single_unit(BaseUnit::SECOND, -1);
126    /// println!("{}", meter_per_second);
127    /// ```
128    ///
129    /// **Output**
130    /// ```
131    /// // m·s⁻¹
132    /// ```
133    pub fn add_single_unit(&mut self, unit: BaseUnit, n: i32) {
134        let count = self.base_units.entry(unit).or_insert(0);
135        *count += n;
136    }
137
138    fn add_from_hashmap(&mut self, hashmap: &HashMap<BaseUnit, i32>) {
139        for (unit, count) in hashmap.iter() {
140            self.add_single_unit(*unit, *count);
141        }
142    }
143}
144
145impl PartialEq for Unit {
146    fn eq(&self, other: &Unit) -> bool {
147        let mut equal = true;
148        for (unit, count) in self.base_units.iter() {
149            if *count != 0 {
150                match other.base_units.get(unit) {
151                    Some(i) => {
152                        if i != count {
153                            equal = false;
154                        }
155                    }
156                    _ => equal = false,
157                }
158            }
159        }
160
161        for (unit, count) in other.base_units.iter() {
162            if *count != 0 {
163                match self.base_units.get(unit) {
164                    Some(i) => {
165                        if i != count {
166                            equal = false;
167                        }
168                    }
169                    _ => equal = false,
170                }
171            }
172        }
173        equal
174    }
175}
176
177/// Given two `Unit`s, perform a multiplication between them and return a new `Unit`.
178/// It adds the base_units of the two given Struct
179///
180/// # Exemples
181///
182/// ```
183/// use ndarray_unit::Unit;
184/// use ndarray_unit::BaseUnit;
185///
186/// let meter = Unit::from_vec(vec![(BaseUnit::METER, 1)]);
187/// let square_meter = Unit::from_vec(vec![(BaseUnit::METER, 2)]);
188///
189/// let cube_meter = &meter * &square_meter;
190/// // cube_meter = Unit { base_units: {METER : 3} }
191/// ```
192impl Mul for &Unit {
193    type Output = Unit;
194
195    fn mul(self, other: &Unit) -> Unit {
196        let mut result = Unit::new();
197        result.add_from_hashmap(&self.base_units);
198        result.add_from_hashmap(&other.base_units);
199        result
200    }
201}
202
203/// Given two `Unit`s, perform a division between them and return a new `Unit`.
204/// It substracts the base_units of the two given Struct
205///
206/// # Exemples
207///
208/// ```
209/// use ndarray_unit::Unit;
210/// use ndarray_unit::BaseUnit;
211///
212/// let meter = Unit::from_vec(vec![(BaseUnit::METER, 1)]);
213/// let second_square = Unit::from_vec(vec![(BaseUnit::SECOND, 2)]);
214///
215/// let acceleration = &meter / &second_square;
216/// // acceleration = Unit { base_units: {METER : 1, SECOND: -2} }
217/// ```
218impl Div for &Unit {
219    type Output = Unit;
220
221    fn div(self, other: &Unit) -> Unit {
222        let mut result = Unit::new();
223        let inversed_unit = &other.get_inverse();
224        let inversed_map = inversed_unit.get_base_units();
225        result.add_from_hashmap(&self.base_units);
226        result.add_from_hashmap(&inversed_map);
227        result
228    }
229}
230
231impl Display for Unit {
232    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
233        let mut list_units: Vec<(&BaseUnit, &i32)> = self
234            .base_units
235            .iter()
236            .filter(|(_, val)| **val != 0)
237            .collect();
238        list_units.sort_by(|(_, val1), (_, val2)| val2.partial_cmp(val1).unwrap());
239
240        if list_units.len() == 0 {
241            return write!(f, "∅");
242        }
243
244        let mut res = String::new();
245
246        let mut iterator = list_units.iter().peekable();
247        while let Some((unit, count)) = iterator.next() {
248            res.push_str(match unit {
249                // SI
250                BaseUnit::METER => "m",
251                BaseUnit::SECOND => "s",
252                BaseUnit::KILOGRAM => "kg",
253                BaseUnit::AMPERE => "A",
254                BaseUnit::KELVIN => "K",
255                BaseUnit::MOLE => "mol",
256                BaseUnit::CANDELA => "cd",
257                // others
258                BaseUnit::RADIAN => "rad",
259                BaseUnit::STERADIAN => "sr",
260                // eco
261                BaseUnit::CURRENCY => "currency",
262                BaseUnit::INHABITANT => "inhabitant",
263                BaseUnit::BIRTH => "birth",
264                BaseUnit::DEATH => "death",
265            });
266
267            if **count != 1 {
268                let count_string = count.to_string();
269                for c in count_string.chars() {
270                    match c {
271                        '-' => res.push_str("⁻"),
272                        '0' => res.push_str("⁰"),
273                        '1' => res.push_str("¹"),
274                        '2' => res.push_str("²"),
275                        '3' => res.push_str("³"),
276                        '4' => res.push_str("⁴"),
277                        '5' => res.push_str("⁵"),
278                        '6' => res.push_str("⁶"),
279                        '7' => res.push_str("⁷"),
280                        '8' => res.push_str("⁸"),
281                        '9' => res.push_str("⁹"),
282                        _ => (),
283                    }
284                }
285            }
286
287            match iterator.peek() {
288                Some(_) => res.push_str("·"),
289                None => (),
290            }
291        }
292
293        write!(f, "{}", res)
294    }
295}