use core::fmt::{self, Debug};
use core::marker::PhantomData;
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
use crate::Real;
use crate::model::dimension::{Dimensioned, TypePow};
use crate::model::measure::Measure;
use crate::model::unit::Unit;
use num_traits::Inv;
use typenum::{Integer, NonZero, ToInt};
pub trait QuantityTag: Debug + Clone + Copy + PartialOrd + PartialEq {
fn name() -> &'static str {
let mut tag_name = core::any::type_name::<Self>();
tag_name = tag_name.rsplit_once("::").map_or(tag_name, |(_, n)| n);
tag_name = tag_name.strip_suffix("Tag").unwrap_or(tag_name);
tag_name
}
}
impl QuantityTag for () {}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Quantity<D: Dimensioned, T: QuantityTag> {
pub(crate) value: Real,
_phantom: PhantomData<(D, T)>,
}
#[doc(hidden)]
pub trait QuantityMarker:
Sized + Debug + Clone + Copy + PartialEq + crate::model::sealed::Sealed
{
type DimensionVector: Dimensioned;
type Tag: QuantityTag;
fn new(value: Real) -> Self;
fn raw_value(&self) -> Real;
}
#[macro_export]
macro_rules! quantity {
($quantity:ident: M $mass:ty, L $length:ty, T $time:ty, I $current:ty, Th $temperature:ty, N $amount:ty, J $luminosity:ty; marked) => {
paste::paste! {
#[cfg(feature = "quantity_tags")]
#[derive(Debug, Clone, Copy, PartialOrd, PartialEq)]
pub struct [<$quantity Tag>] {}
#[cfg(feature = "quantity_tags")]
impl $crate::__model::QuantityTag for [<$quantity Tag>] {}
#[cfg(feature = "quantity_tags")]
quantity!(
$quantity:
$crate::__model::Quantity<
$crate::__model::DimensionVector<$mass, $length, $time, $current, $temperature, $amount, $luminosity>,
[<$quantity Tag>]
>;
);
#[cfg(not(feature = "quantity_tags"))]
quantity!(
$quantity:
$crate::__model::Quantity<
$crate::__model::DimensionVector<$mass, $length, $time, $current, $temperature, $amount, $luminosity>,
()
>;
);
}
};
($quantity:ident: M $mass:ty, L $length:ty, T $time:ty, I $current:ty, Th $temperature:ty, N $amount:ty, J $luminosity:ty) => {
quantity!(
$quantity:
$crate::__model::Quantity<
$crate::__model::DimensionVector<
$mass, $length, $time, $current, $temperature, $amount, $luminosity
>,
()
>;
);
};
($comp_quantity:ident: [$(($quantities:ty, $exps:ty)),+]) => {
quantity!(
$comp_quantity:
$crate::__model::Quantity<$crate::__model::DimensionZero, ()>; $(($quantities, $exps)),+
);
};
($comp_quantity:ident: $quantity_acc:ty; ($quantity:ty, $exp:ty) $(, ($quantities:ty, $exps:ty))*) => {
quantity!(
$comp_quantity:
<$quantity_acc as core::ops::Mul<
<$quantity as $crate::__model::TypePow<$exp>>::Output
>>::Output;
$(($quantities, $exps)),*
);
};
($comp_quantity:ident: $quantity_acc:ty;) => {
pub type $comp_quantity = $quantity_acc;
};
}
impl<D: Dimensioned, T: QuantityTag> Quantity<D, T> {
pub(crate) fn new(value: Real) -> Self {
Self {
value,
_phantom: PhantomData,
}
}
pub fn from<U: Unit<Quantity = Self>>(value: impl Into<Real>) -> Self {
Measure::<U>::new(value.into()).into_q()
}
pub fn as_measure<U: Unit<Quantity = Self>>(&self) -> Measure<U> {
Measure::from_q(*self)
}
pub fn convert<U1, U2>(value: Real) -> Measure<U2>
where
U1: Unit<Quantity = Self>,
U2: Unit<Quantity = Self>,
{
U1::new(value).convert::<U2>()
}
#[cfg(feature = "quantity_tags")]
pub fn specify<Q2>(self) -> Quantity<D, Q2::Tag>
where
Q2: QuantityMarker<DimensionVector = D>,
{
Quantity::new(self.value)
}
}
impl<D: Dimensioned, T: QuantityTag> crate::model::sealed::Sealed for Quantity<D, T> {}
impl<D: Dimensioned, T: QuantityTag> QuantityMarker for Quantity<D, T> {
type DimensionVector = D;
type Tag = T;
fn new(value: Real) -> Self {
Self::new(value)
}
fn raw_value(&self) -> Real {
self.value
}
}
impl<D: Dimensioned, T: QuantityTag> fmt::Display for Quantity<D, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
core::fmt::Display::fmt(&self.value, f)?;
let dim_string = crate::common::format_dims::<Self>();
write!(f, " [{}]", dim_string)?;
let tag_name = T::name();
if tag_name != "()" {
write!(f, " as {}", tag_name)?;
}
Ok(())
}
}
impl<D, T> Add<Self> for Quantity<D, T>
where
D: Dimensioned + Add,
T: QuantityTag,
<D as Add>::Output: Dimensioned,
{
type Output = Quantity<<D as Add>::Output, T>;
fn add(self, rhs: Self) -> Self::Output {
Self::Output::new(self.raw_value() + rhs.raw_value())
}
}
impl<D, T, U> Add<Measure<U>> for Quantity<D, T>
where
D: Dimensioned + Add,
T: QuantityTag,
U: Unit<Quantity = Self>,
<D as Add>::Output: Dimensioned,
{
type Output = Quantity<<D as Add>::Output, T>;
fn add(self, rhs: Measure<U>) -> Self::Output {
Self::Output::new(self.raw_value() + rhs.into_q().raw_value())
}
}
impl<D: Dimensioned, T: QuantityTag> AddAssign<Self> for Quantity<D, T> {
fn add_assign(&mut self, rhs: Self) {
self.value += rhs.raw_value();
}
}
impl<D: Dimensioned, T: QuantityTag, U> AddAssign<Measure<U>> for Quantity<D, T>
where
U: Unit<Quantity = Self>,
{
fn add_assign(&mut self, rhs: Measure<U>) {
*self += rhs.into_q();
}
}
impl<D: Dimensioned, T: QuantityTag> Sub<Self> for Quantity<D, T>
where
D: Sub,
<D as Sub>::Output: Dimensioned,
{
type Output = Quantity<<D as Sub>::Output, T>;
fn sub(self, rhs: Self) -> Self::Output {
Self::Output::new(self.raw_value() - rhs.raw_value())
}
}
impl<D, T, U> Sub<Measure<U>> for Quantity<D, T>
where
D: Dimensioned + Sub,
T: QuantityTag,
U: Unit<Quantity = Self>,
<D as Sub>::Output: Dimensioned,
{
type Output = Quantity<<D as Sub>::Output, T>;
fn sub(self, rhs: Measure<U>) -> Self::Output {
self - rhs.into_q()
}
}
impl<D: Dimensioned, T: QuantityTag> SubAssign<Self> for Quantity<D, T> {
fn sub_assign(&mut self, rhs: Self) {
self.value -= rhs.raw_value();
}
}
impl<D: Dimensioned, T: QuantityTag, U> SubAssign<Measure<U>> for Quantity<D, T>
where
U: Unit<Quantity = Self>,
{
fn sub_assign(&mut self, rhs: Measure<U>) {
*self -= rhs.into_q();
}
}
impl<D1: Dimensioned, D2: Dimensioned, T1: QuantityTag, T2: QuantityTag> Mul<Quantity<D2, T2>>
for Quantity<D1, T1>
where
D1: Mul<D2>,
<D1 as Mul<D2>>::Output: Dimensioned,
{
type Output = Quantity<<D1 as Mul<D2>>::Output, ()>;
fn mul(self, rhs: Quantity<D2, T2>) -> Self::Output {
Self::Output::new(self.raw_value() * rhs.raw_value())
}
}
impl<D1: Dimensioned, D2: Dimensioned, T1: QuantityTag, T2: QuantityTag, U> Mul<Measure<U>>
for Quantity<D1, T1>
where
U: Unit<Quantity = Quantity<D2, T2>>,
D1: Mul<D2>,
<D1 as Mul<D2>>::Output: Dimensioned,
{
type Output = Quantity<<D1 as Mul<D2>>::Output, ()>;
fn mul(self, rhs: Measure<U>) -> Self::Output {
self * rhs.into_q()
}
}
impl<D1: Dimensioned, D2: Dimensioned, T1: QuantityTag, T2: QuantityTag> Div<Quantity<D2, T2>>
for Quantity<D1, T1>
where
D1: Div<D2>,
<D1 as Div<D2>>::Output: Dimensioned,
{
type Output = Quantity<<D1 as Div<D2>>::Output, ()>;
fn div(self, rhs: Quantity<D2, T2>) -> Self::Output {
Self::Output::new(self.raw_value() / rhs.raw_value())
}
}
impl<D1: Dimensioned, D2: Dimensioned, T1: QuantityTag, T2: QuantityTag, U> Div<Measure<U>>
for Quantity<D1, T1>
where
U: Unit<Quantity = Quantity<D2, T2>>,
D1: Div<D2>,
<D1 as Div<D2>>::Output: Dimensioned,
{
type Output = Quantity<<D1 as Div<D2>>::Output, ()>;
fn div(self, rhs: Measure<U>) -> Self::Output {
self / rhs.into_q()
}
}
impl<D: Dimensioned, T: QuantityTag> Mul<Real> for Quantity<D, T> {
type Output = Self;
fn mul(self, scalar: Real) -> Self::Output {
Self::new(self.value * scalar)
}
}
impl<D: Dimensioned, T: QuantityTag> MulAssign<Real> for Quantity<D, T> {
fn mul_assign(&mut self, scalar: Real) {
self.value *= scalar;
}
}
impl<D: Dimensioned, T: QuantityTag> Mul<Quantity<D, T>> for Real {
type Output = Quantity<D, T>;
fn mul(self, quantity: Quantity<D, T>) -> Self::Output {
Self::Output::new(self * quantity.value)
}
}
impl<D: Dimensioned, T: QuantityTag> Div<Real> for Quantity<D, T> {
type Output = Self;
fn div(self, scalar: Real) -> Self::Output {
Self::new(self.value / scalar)
}
}
impl<D: Dimensioned, T: QuantityTag> DivAssign<Real> for Quantity<D, T> {
fn div_assign(&mut self, scalar: Real) {
self.value /= scalar;
}
}
impl<D: Dimensioned, T: QuantityTag> Div<Quantity<D, T>> for Real
where
D: Inv,
<D as Inv>::Output: Dimensioned,
{
type Output = Quantity<<D as Inv>::Output, ()>;
fn div(self, quantity: Quantity<D, T>) -> Self::Output {
Self::Output::new(self / quantity.raw_value())
}
}
impl<D: Dimensioned, T: QuantityTag, Exp> TypePow<Exp> for Quantity<D, T>
where
D: TypePow<Exp>,
<D as TypePow<Exp>>::Output: Dimensioned,
Exp: Integer + NonZero + ToInt<i32>,
{
type Output = Quantity<<D as TypePow<Exp>>::Output, ()>;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::system::*;
#[test]
fn test_quantity_creation_and_raw_value() {
let length = Length::new(100.0);
let mass = Mass::new(5.0);
let time = Time::new(10.0);
assert_eq!(length.raw_value(), 100.0); assert_eq!(mass.raw_value(), 5.0); assert_eq!(time.raw_value(), 10.0); }
#[test]
fn test_from_method() {
let length1 = Length::from::<Metre>(100.0);
let length2 = Length::from::<Centimetre>(10000.0);
let length3 = Length::from::<Kilometre>(0.1);
assert_eq!(length1.raw_value(), 100.0);
assert_eq!(length2.raw_value(), 100.0); assert_eq!(length3.raw_value(), 100.0);
let mass1 = Mass::from::<Kilogram>(2.0);
let mass2 = Mass::from::<Gram>(2000.0);
assert_eq!(mass1.raw_value(), 2.0);
assert_eq!(mass2.raw_value(), 2.0); }
#[test]
fn test_as_measure() {
let length = Metre::new(1000.0).into_q();
let metres = length.as_measure::<Metre>();
let kilometres = length.as_measure::<Kilometre>();
let centimetres = length.as_measure::<Centimetre>();
assert_eq!(metres.value(), 1000.0);
assert_eq!(kilometres.value(), 1.0); assert_eq!(centimetres.value(), 100000.0);
let mass = Kilogram::new(5.0).into_q();
let kg_measure = mass.as_measure::<Kilogram>();
let g_measure = mass.as_measure::<Gram>();
assert_eq!(kg_measure.value(), 5.0);
assert_eq!(g_measure.value(), 5000.0); }
#[test]
fn test_convert_static_method() {
let feet = Length::convert::<Metre, Foot>(100.0);
assert!((feet.value() - 328.084).abs() < 0.001);
let pounds = Mass::convert::<Kilogram, Pound>(1.0);
assert!((pounds.value() - 2.205).abs() < 0.01);
let minutes = Time::convert::<Second, Minute>(3600.0);
assert_eq!(minutes.value(), 60.0); }
}