use crate::DimensionalOperationError;
use num_traits::Zero;
use std::{
collections::{hash_map::Entry, HashMap},
fmt::Debug,
hash::Hash,
ops::{Add, Div, Mul, Neg, Sub},
};
#[derive(Debug, Clone)]
pub struct Dimensional<TDimensions, TMagnitude = f64, TDimensionalMagnitude = i8> {
pub magnitude: TMagnitude,
pub dimensions: HashMap<TDimensions, TDimensionalMagnitude>,
}
impl<TD, TM, TDM> Dimensional<TD, TM, TDM> {
pub fn new(magnitude: TM) -> Self {
Dimensional {
magnitude,
dimensions: HashMap::new(),
}
}
}
impl<TD: Eq + Hash, TM, TDM: Zero + PartialEq> Dimensional<TD, TM, TDM> {
pub fn equal_dimensions(&self, other: &Self) -> bool {
for (dim, mag1) in &self.dimensions {
if mag1.is_zero() {
continue;
}
match other.dimensions.get(dim) {
Some(mag2) if mag1 == mag2 => continue,
_ => return false,
}
}
for (dim, mag1) in &other.dimensions {
if mag1.is_zero() {
continue;
}
match self.dimensions.get(dim) {
Some(mag2) if mag1 == mag2 => continue,
_ => return false,
}
}
true
}
}
impl<TD: Eq + Hash, TM: Add<Output = TM>, TDM: PartialEq + Zero> Add for Dimensional<TD, TM, TDM> {
type Output = Result<Self, DimensionalOperationError>;
fn add(mut self, other: Self) -> Self::Output {
if !self.equal_dimensions(&other) {
return Err(DimensionalOperationError::DifferingDimensions);
}
self.magnitude = self.magnitude + other.magnitude;
Ok(self)
}
}
impl<TD: Eq + Hash, TM: Sub<Output = TM>, TDM: PartialEq + Zero> Sub for Dimensional<TD, TM, TDM> {
type Output = Result<Self, DimensionalOperationError>;
fn sub(mut self, other: Self) -> Self::Output {
if !self.equal_dimensions(&other) {
return Err(DimensionalOperationError::DifferingDimensions);
}
self.magnitude = self.magnitude - other.magnitude;
Ok(self)
}
}
impl<TD: Eq + Hash, TM: Mul<Output = TM>, TDM: Add<Output = TDM>> Mul for Dimensional<TD, TM, TDM> {
type Output = Self;
fn mul(mut self, other: Self) -> Self::Output {
self.magnitude = self.magnitude * other.magnitude;
for (dim, mag1) in other.dimensions {
match self.dimensions.entry(dim) {
Entry::Occupied(entry) => {
let (key, val) = entry.remove_entry();
self.dimensions.insert(key, val + mag1);
}
Entry::Vacant(entry) => {
entry.insert(mag1);
}
}
}
self
}
}
impl<TD: Eq + Hash, TM: Div<Output = TM>, TDM: Sub<Output = TDM> + Neg<Output = TDM>> Div
for Dimensional<TD, TM, TDM>
{
type Output = Self;
fn div(mut self, other: Self) -> Self::Output {
self.magnitude = self.magnitude / other.magnitude;
for (dim, mag1) in other.dimensions {
match self.dimensions.entry(dim) {
Entry::Occupied(entry) => {
let (key, val) = entry.remove_entry();
self.dimensions.insert(key, val - mag1);
}
Entry::Vacant(entry) => {
entry.insert(-mag1);
}
}
}
self
}
}
impl<TD, TM: Neg<Output = TM>, TDM> Neg for Dimensional<TD, TM, TDM> {
type Output = Self;
fn neg(mut self) -> Self::Output {
self.magnitude = -self.magnitude;
self
}
}
impl<TD: Eq + Hash, TM: PartialEq, TDM: Zero + PartialEq> PartialEq for Dimensional<TD, TM, TDM> {
fn eq(&self, other: &Self) -> bool {
self.magnitude == other.magnitude && self.equal_dimensions(other)
}
}
impl<TD: Eq + Hash, TM: Eq, TDM: Zero + Eq> Eq for Dimensional<TD, TM, TDM> {}
impl<TD, TM: Default, TDM> Default for Dimensional<TD, TM, TDM> {
fn default() -> Self {
Dimensional::new(Default::default())
}
}
#[macro_export]
macro_rules! dim {
($mag:expr) => {{
Dimensional::new($mag)
}};
($mag:expr, $($dim:expr => $dim_mag:expr),+) => {{
let mut hash = std::collections::HashMap::new();
$(
hash.insert($dim, $dim_mag);
)+
Dimensional {
magnitude: $mag,
dimensions: hash
}
}}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Hash, PartialEq, Eq)]
enum Dim {
Length,
Time,
}
fn d(magnitude: f64) -> Dimensional<Dim> {
Dimensional::<Dim> {
magnitude,
dimensions: HashMap::new(),
}
}
fn dl(magnitude: f64, length: i8) -> Dimensional<Dim> {
dim!(magnitude, Dim::Length => length)
}
fn dt(magnitude: f64, time: i8) -> Dimensional<Dim> {
dim!(magnitude, Dim::Time => time)
}
fn dlt(magnitude: f64, length: i8, time: i8) -> Dimensional<Dim> {
dim!(magnitude, Dim::Length => length, Dim::Time => time)
}
#[test]
fn dimensional_dim_macro() {
let dim = dim!(2, Dim::Length => 2);
assert_eq!(dim.magnitude, 2);
assert_eq!(dim.dimensions.get(&Dim::Length), Some(&2));
assert_eq!(dim.dimensions.get(&Dim::Time), None);
}
#[test]
fn dimensional_new() {
assert_eq!(Dimensional::new(2.0), d(2.0));
}
#[test]
fn dimensional_equal_dimensions() {
assert!(d(2.0).equal_dimensions(&dl(4.0, 0)));
assert!(dl(2.0, 2).equal_dimensions(&dl(4.0, 2)));
assert!(dl(2.0, 2).equal_dimensions(&dlt(4.0, 2, 0)));
assert!(dlt(2.0, 2, 0).equal_dimensions(&dl(-4.0, 2)));
assert!(dlt(2.0, 2, 0).equal_dimensions(&dlt(-4.0, 2, 0)));
assert!(!dl(2.0, 2).equal_dimensions(&dl(2.0, -1)));
assert!(!dl(2.0, 2).equal_dimensions(&dt(2.0, 2)));
}
#[test]
fn dimensional_add() {
assert_eq!(d(2.0) + d(4.0), Ok(d(6.0)));
assert_eq!(dl(2.0, 1) + dl(-2.0, 1), Ok(dl(0.0, 1)));
assert_eq!(
d(2.0) + dl(2.0, 1),
Err(DimensionalOperationError::DifferingDimensions)
);
}
#[test]
fn dimensional_sub() {
assert_eq!(d(2.0) - d(4.0), Ok(d(-2.0)));
assert_eq!(dl(2.0, 1) - dl(-2.0, 1), Ok(dl(4.0, 1)));
assert_eq!(
d(2.0) - dl(2.0, 1),
Err(DimensionalOperationError::DifferingDimensions)
);
}
#[test]
fn dimensional_mul() {
assert_eq!(d(2.0) * d(2.0), d(4.0));
assert_eq!(d(2.0) * dl(1.0, 1), dl(2.0, 1));
assert_eq!(dl(3.0, 1) * dt(-3.0, 1), dlt(-9.0, 1, 1));
assert_eq!(dl(3.0, 2) * dlt(1.0, -1, 1), dlt(3.0, 1, 1));
}
#[test]
fn dimensional_div() {
assert_eq!(d(2.0) / d(2.0), d(1.0));
assert_eq!(d(2.0) / dl(1.0, 1), dl(2.0, -1));
assert_eq!(dl(3.0, 1) / dt(-3.0, 1), dlt(-1.0, 1, -1));
assert_eq!(dl(3.0, 2) / dlt(1.0, -1, 1), dlt(3.0, 3, -1));
}
#[test]
fn dimensional_neg() {
assert_eq!(-d(2.0), d(-2.0));
assert_eq!(-d(-2.0), d(2.0));
assert_eq!(-dlt(-2.0, 1, -1), dlt(2.0, 1, -1));
}
}