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}