use super::{Quantity, QuantityError, Unit};
use ang::{Angle, Degrees, Radians};
use ndarray::*;
use serde::{Deserialize, Serialize};
use std::cmp::PartialEq;
use std::hash::Hash;
use std::ops::Rem;
use std::ops::{Div, DivAssign, Mul, MulAssign};
#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy, Serialize, Deserialize)]
pub struct SIUnit(pub(crate) [i8; 7]);
pub type SINumber = Quantity<f64, SIUnit>;
pub type SIArray<D> = Quantity<Array<f64, D>, SIUnit>;
pub type SIArray0 = SIArray<Ix0>;
pub type SIArray1 = SIArray<Ix1>;
pub type SIArray2 = SIArray<Ix2>;
pub type SIArray3 = SIArray<Ix3>;
pub type SIArray4 = SIArray<Ix4>;
pub type SIArray5 = SIArray<Ix5>;
pub type SIArray6 = SIArray<Ix6>;
impl Unit for SIUnit {
const DIMENSIONLESS: Self = SIUnit([0; 7]);
fn powi(&self, i: i32) -> Self {
let i8 = i as i8;
Self([
self.0[0] * i8,
self.0[1] * i8,
self.0[2] * i8,
self.0[3] * i8,
self.0[4] * i8,
self.0[5] * i8,
self.0[6] * i8,
])
}
fn sqrt(&self) -> Result<Self, QuantityError> {
self.root(2)
}
fn cbrt(&self) -> Result<Self, QuantityError> {
self.root(3)
}
fn root(&self, i: i32) -> Result<Self, QuantityError> {
let i8 = i as i8;
if self.0.iter().all(|u| u.rem(i8) == 0) {
Ok(Self([
self.0[0] / i8,
self.0[1] / i8,
self.0[2] / i8,
self.0[3] / i8,
self.0[4] / i8,
self.0[5] / i8,
self.0[6] / i8,
]))
} else {
Err(QuantityError::SINumberError {
op: String::from("root"),
cause: String::from("Unit exponents are not multiples of index"),
})
}
}
}
impl Mul for SIUnit {
type Output = Self;
#[allow(clippy::suspicious_arithmetic_impl)]
fn mul(self, other: Self) -> Self {
Self([
self.0[0] + other.0[0],
self.0[1] + other.0[1],
self.0[2] + other.0[2],
self.0[3] + other.0[3],
self.0[4] + other.0[4],
self.0[5] + other.0[5],
self.0[6] + other.0[6],
])
}
}
impl Div for SIUnit {
type Output = Self;
#[allow(clippy::suspicious_arithmetic_impl)]
fn div(self, other: Self) -> Self {
Self([
self.0[0] - other.0[0],
self.0[1] - other.0[1],
self.0[2] - other.0[2],
self.0[3] - other.0[3],
self.0[4] - other.0[4],
self.0[5] - other.0[5],
self.0[6] - other.0[6],
])
}
}
impl MulAssign for SIUnit {
#[allow(clippy::suspicious_op_assign_impl)]
fn mul_assign(&mut self, rhs: Self) {
self.0[0] += rhs.0[0];
self.0[1] += rhs.0[1];
self.0[2] += rhs.0[2];
self.0[3] += rhs.0[3];
self.0[4] += rhs.0[4];
self.0[5] += rhs.0[5];
self.0[6] += rhs.0[6];
}
}
impl DivAssign for SIUnit {
#[allow(clippy::suspicious_op_assign_impl)]
fn div_assign(&mut self, rhs: Self) {
self.0[0] -= rhs.0[0];
self.0[1] -= rhs.0[1];
self.0[2] -= rhs.0[2];
self.0[3] -= rhs.0[3];
self.0[4] -= rhs.0[4];
self.0[5] -= rhs.0[5];
self.0[6] -= rhs.0[6];
}
}
const _METER: SIUnit = SIUnit([1, 0, 0, 0, 0, 0, 0]);
const _KILOGRAM: SIUnit = SIUnit([0, 1, 0, 0, 0, 0, 0]);
const _SECOND: SIUnit = SIUnit([0, 0, 1, 0, 0, 0, 0]);
const _AMPERE: SIUnit = SIUnit([0, 0, 0, 1, 0, 0, 0]);
const _MOL: SIUnit = SIUnit([0, 0, 0, 0, 1, 0, 0]);
const _KELVIN: SIUnit = SIUnit([0, 0, 0, 0, 0, 1, 0]);
const _CANDELA: SIUnit = SIUnit([0, 0, 0, 0, 0, 0, 1]);
const _HERTZ: SIUnit = SIUnit([0, 0, -1, 0, 0, 0, 0]);
const _NEWTON: SIUnit = SIUnit([1, 1, -2, 0, 0, 0, 0]);
const _JOULE: SIUnit = SIUnit([2, 1, -2, 0, 0, 0, 0]);
const _PASCAL: SIUnit = SIUnit([-1, 1, -2, 0, 0, 0, 0]);
const _WATT: SIUnit = SIUnit([2, 1, -3, 0, 0, 0, 0]);
const _CUBIC_METER: SIUnit = SIUnit([3, 0, 0, 0, 0, 0, 0]);
const _JOULE_PER_KELVIN: SIUnit = SIUnit([2, 1, -2, 0, 0, -1, 0]);
const _PER_MOL: SIUnit = SIUnit([0, 0, 0, 0, -1, 0, 0]);
const _JOULE_SECOND: SIUnit = SIUnit([2, 1, -1, 0, 0, 0, 0]);
const _JOULE_PER_MOL_AND_KELVIN: SIUnit = SIUnit([2, 1, -2, 0, -1, -1, 0]);
const _AMPERE_SECOND: SIUnit = SIUnit([0, 0, 1, 1, 0, 0, 0]);
const _VOLT: SIUnit = SIUnit([2, 1, -3, -1, 0, 0, 0]);
const _FARAD: SIUnit = SIUnit([-2, -1, 4, 2, 0, 0, 0]);
const _OHM: SIUnit = SIUnit([2, 1, -3, -2, 0, 0, 0]);
const _SIEMENS: SIUnit = SIUnit([-2, -1, 3, 2, 0, 0, 0]);
const _WEBER: SIUnit = SIUnit([2, 1, -2, -1, 0, 0, 0]);
const _TESLA: SIUnit = SIUnit([0, 1, -2, -1, 0, 0, 0]);
const _HENRY: SIUnit = SIUnit([2, 1, -2, -2, 0, 0, 0]);
const _METER_PER_SECOND: SIUnit = SIUnit([1, 0, -1, 0, 0, 0, 0]);
const _LUMEN_PER_WATT: SIUnit = SIUnit([-2, -1, 3, 0, 0, 0, 1]);
pub const METER: SINumber = SINumber {
unit: _METER,
value: 1.0,
};
pub const KILOGRAM: SINumber = SINumber {
unit: _KILOGRAM,
value: 1.0,
};
pub const SECOND: SINumber = SINumber {
unit: _SECOND,
value: 1.0,
};
pub const AMPERE: SINumber = SINumber {
unit: _AMPERE,
value: 1.0,
};
pub const MOL: SINumber = SINumber {
unit: _MOL,
value: 1.0,
};
pub const KELVIN: SINumber = SINumber {
unit: _KELVIN,
value: 1.0,
};
pub const CANDELA: SINumber = SINumber {
unit: _CANDELA,
value: 1.0,
};
pub const ANGSTROM: SINumber = SINumber {
unit: _METER,
value: 1e-10,
};
pub const AU: SINumber = SINumber {
unit: _METER,
value: 149597870700.0,
};
pub const GRAM: SINumber = SINumber {
unit: _KILOGRAM,
value: 1e-3,
};
pub const AMU: SINumber = SINumber {
unit: _KILOGRAM,
value: 1.6605390671738466e-27,
};
pub const HERTZ: SINumber = SINumber {
unit: _HERTZ,
value: 1.0,
};
pub const NEWTON: SINumber = SINumber {
unit: _NEWTON,
value: 1.0,
};
pub const JOULE: SINumber = SINumber {
unit: _JOULE,
value: 1.0,
};
pub const PASCAL: SINumber = SINumber {
unit: _PASCAL,
value: 1.0,
};
pub const WATT: SINumber = SINumber {
unit: _WATT,
value: 1.0,
};
pub const BAR: SINumber = SINumber {
unit: _PASCAL,
value: 1e5,
};
pub const CALORIE: SINumber = SINumber {
unit: _JOULE,
value: 4.184,
};
pub const LITER: SINumber = SINumber {
unit: _CUBIC_METER,
value: 1e-3,
};
pub const MINUTE: SINumber = SINumber {
unit: _SECOND,
value: 60.0,
};
pub const HOUR: SINumber = SINumber {
unit: _SECOND,
value: 3600.0,
};
pub const DAY: SINumber = SINumber {
unit: _SECOND,
value: 86400.0,
};
pub const COULOMB: SINumber = SINumber {
unit: _AMPERE_SECOND,
value: 1.0,
};
pub const VOLT: SINumber = SINumber {
unit: _VOLT,
value: 1.0,
};
pub const FARAD: SINumber = SINumber {
unit: _FARAD,
value: 1.0,
};
pub const OHM: SINumber = SINumber {
unit: _OHM,
value: 1.0,
};
pub const SIEMENS: SINumber = SINumber {
unit: _SIEMENS,
value: 1.0,
};
pub const WEBER: SINumber = SINumber {
unit: _WEBER,
value: 1.0,
};
pub const TESLA: SINumber = SINumber {
unit: _TESLA,
value: 1.0,
};
pub const HENRY: SINumber = SINumber {
unit: _HENRY,
value: 1.0,
};
pub const DEBYE: Debye = Debye(1.0);
pub const KB: SINumber = SINumber {
unit: _JOULE_PER_KELVIN,
value: 1.380649e-23,
};
pub const NAV: SINumber = SINumber {
unit: _PER_MOL,
value: 6.02214076e23,
};
pub const PLANCK: SINumber = SINumber {
unit: _JOULE_SECOND,
value: 6.62607015e-34,
};
pub const RGAS: SINumber = SINumber {
unit: _JOULE_PER_MOL_AND_KELVIN,
value: 1.380649e-23 * 6.02214076e23,
};
pub const DVCS: SINumber = SINumber {
unit: _HERTZ,
value: 9192631770.0,
};
pub const QE: SINumber = SINumber {
unit: _AMPERE_SECOND,
value: 1.602176634e-19,
};
pub const CLIGHT: SINumber = SINumber {
unit: _METER_PER_SECOND,
value: 299792458.0,
};
pub const KCD: SINumber = SINumber {
unit: _LUMEN_PER_WATT,
value: 683.0,
};
pub const G: SINumber = SINumber {
unit: SIUnit([3, -1, -2, 0, 0, 0, 0]),
value: 6.6743e-11,
};
pub const RADIANS: Angle = Radians(1.0);
pub const DEGREES: Angle = Degrees(1.0);
pub const QUECTO: f64 = 1e-30;
pub const RONTO: f64 = 1e-27;
pub const YOCTO: f64 = 1e-24;
pub const ZEPTO: f64 = 1e-21;
pub const ATTO: f64 = 1e-18;
pub const FEMTO: f64 = 1e-15;
pub const PICO: f64 = 1e-12;
pub const NANO: f64 = 1e-9;
pub const MICRO: f64 = 1e-6;
pub const MILLI: f64 = 1e-3;
pub const CENTI: f64 = 1e-2;
pub const DECI: f64 = 1e-1;
pub const DECA: f64 = 1e1;
pub const HECTO: f64 = 1e2;
pub const KILO: f64 = 1e3;
pub const MEGA: f64 = 1e6;
pub const GIGA: f64 = 1e9;
pub const TERA: f64 = 1e12;
pub const PETA: f64 = 1e15;
pub const EXA: f64 = 1e18;
pub const ZETTA: f64 = 1e21;
pub const YOTTA: f64 = 1e24;
pub const RONNA: f64 = 1e27;
pub const QUETTA: f64 = 1e30;
pub struct CELSIUS;
impl Mul<CELSIUS> for f64 {
type Output = SINumber;
#[allow(clippy::suspicious_arithmetic_impl)]
fn mul(self, _: CELSIUS) -> SINumber {
SINumber {
value: self + 273.15,
unit: _KELVIN,
}
}
}
impl<S: Data<Elem = f64>, D: Dimension> Mul<CELSIUS> for ArrayBase<S, D> {
type Output = SIArray<D>;
#[allow(clippy::suspicious_arithmetic_impl)]
fn mul(self, _: CELSIUS) -> SIArray<D> {
SIArray {
value: &self + 273.15,
unit: _KELVIN,
}
}
}
impl Div<CELSIUS> for SINumber {
type Output = f64;
#[allow(clippy::suspicious_arithmetic_impl)]
fn div(self, _: CELSIUS) -> f64 {
self.to_reduced(KELVIN).unwrap() - 273.15
}
}
impl<D: Dimension> Div<CELSIUS> for SIArray<D> {
type Output = Array<f64, D>;
#[allow(clippy::suspicious_arithmetic_impl)]
fn div(self, _: CELSIUS) -> Array<f64, D> {
self.to_reduced(KELVIN).unwrap() - 273.15
}
}
#[derive(Clone, Copy)]
pub struct Debye(pub(crate) f64);
impl Mul<Debye> for f64 {
type Output = Debye;
fn mul(self, other: Debye) -> Debye {
Debye(self * other.0)
}
}
impl Debye {
pub fn powi(&self, n: i32) -> SINumber {
if n % 2 == 1 {
panic!("Debye can only be raised to even powers!");
}
(self.0.powi(2) * 1e-19 * JOULE * ANGSTROM.powi(3)).powi(n / 2)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fmt;
#[test]
fn test_mul_si_si() {
let mass = 1000.0 * KILOGRAM;
let acc = 9.81 * METER / SECOND.powi(2);
let force = mass * acc;
assert_eq!(force.value, 1000.0 * 9.81);
assert!(force.has_unit(&NEWTON))
}
#[test]
fn test_mul_si_number() {
let mass = 1000.0 * KILOGRAM;
let acc = 9.81 * METER / SECOND.powi(2);
let force = mass * acc / 1000.0;
assert_eq!(force.value, 9.81);
assert!(force.has_unit(&NEWTON))
}
#[test]
fn test_add_si_si() {
let p1 = 5.0 * BAR;
let p2 = 1e5 * PASCAL;
assert!((p1 + p2).has_unit(&BAR));
assert_eq!((p1 + p2).value, 6e5)
}
#[test]
#[should_panic]
fn test_add_si_different_units() {
let p = 5.0 * BAR;
let t = 300.0 * KELVIN;
let _r = p + t;
}
#[test]
fn test_fmt() {
let mass = 1000.0 * KILOGRAM;
let acc = 9.81 * METER / SECOND.powi(2);
let force = mass * acc;
let format = fmt::format(format_args!("{}", force));
let target = fmt::format(format_args!("{} kN", force.value / 1000.0));
assert_eq!(format, target);
}
#[test]
fn test_fmt_electro() {
assert_eq!((2500. * WATT / AMPERE).to_string(), "2.5 kV".to_string());
assert_eq!((2500. * COULOMB / VOLT).to_string(), "2.5 kF".to_string());
assert_eq!((2500. * VOLT / AMPERE).to_string(), "2.5 kΩ".to_string());
assert_eq!((2500. / OHM).to_string(), "2.5 kS".to_string());
assert_eq!((2500. * VOLT * SECOND).to_string(), "2.5 kWb".to_string());
assert_eq!(
(2500. * WEBER / METER.powi(2)).to_string(),
"2.5 kT".to_string()
);
assert_eq!((2500. * WEBER / AMPERE).to_string(), "2.5 kH".to_string());
}
#[test]
fn test_celsius() {
assert_eq!(25.0 * CELSIUS, 298.15 * KELVIN);
assert_eq!(298.15 * KELVIN / CELSIUS, 25.0);
}
#[test]
fn test_debye() {
assert_eq!((4.0 * DEBYE).powi(2), 16e-19 * JOULE * ANGSTROM.powi(3));
}
#[test]
fn test_from_shape_fn() {
let arr = SIArray1::from_shape_fn(3, |i| i as f64 * KELVIN);
assert_eq!(arr.to_reduced(KELVIN).unwrap(), arr1(&[0.0, 1.0, 2.0]));
}
#[test]
#[should_panic(expected = "Inconsistent units Pa and K")]
fn test_from_vec() {
let vec = vec![3.0 * PASCAL, 0.04 * BAR, 7.0 * JOULE / METER.powi(3)];
let arr = SIArray1::from_vec(vec);
assert_eq!(arr.to_reduced(PASCAL).unwrap(), arr1(&[3.0, 4000.0, 7.0]));
SIArray1::from_vec(vec![BAR, KELVIN]);
}
}