danwi 0.4.2

Zero-cost dimensional analysis library with SI units, compile-time checking, and no_std support
Documentation
//! Type-level dimensions with compile-time arithmetics.
//!
//! Dimension arithmetic happens inside `*` and `/` on quantities; the
//! annotations below only compile because the computed dimensions match.
//!
//! ```
//! use danwi::prelude::*;
//!
//! let v: MeterPerSecond = 10.0.m() / 2.0.s();
//! assert_eq!(v, 5.0.mps());
//!
//! let f: Hertz = 1.0 / 2.0.ms();
//! assert_eq!(f, 500.0.Hz());
//! ```

use core::ops::{Add, Neg, Sub};
use typenum::{Diff, Integer, Negate, Prod, Sum};

/// Type-level dimension representation.
///
/// Type parameters represent exponents for each SI base unit:
/// - T: Time (second, s)
/// - L: Length (metre, m)
/// - M: Mass (kilogram, kg)
/// - I: Electric Current (ampere, A)
/// - K: Thermodynamic Temperature (kelvin, K)
/// - N: Amount of Substance (mole, mol)
/// - J: Luminous Intensity (candela, cd)
///
/// Custom dimensions are aliases with concrete exponents:
///
/// ```
/// use danwi::dimension::{Dimension, N3, P1, Z0};
/// use danwi::prelude::*;
///
/// // jerk (m/s³) = L·T⁻³
/// type Jerk = Dimension<N3, P1, Z0, Z0, Z0, Z0, Z0>;
///
/// let jerk: Quantity<f64, Jerk> = 4.0.mps2() / 2.0.s();
/// assert_eq!(jerk.value(), 2.0);
/// ```
pub type Dimension<T, L, M, I, K, N, J> = (T, L, M, I, K, N, J);

/// Trait to extract type parameters from a Dimension.
pub trait Dimensions {
    type T: Integer;
    type L: Integer;
    type M: Integer;
    type I: Integer;
    type K: Integer;
    type N: Integer;
    type J: Integer;
}

impl<T: Integer, L: Integer, M: Integer, I: Integer, K: Integer, N: Integer, J: Integer> Dimensions
    for Dimension<T, L, M, I, K, N, J>
{
    type T = T;
    type L = L;
    type M = M;
    type I = I;
    type K = K;
    type N = N;
    type J = J;
}

/// Add two dimensions.
pub type DimensionAdd<D1, D2> = Dimension<
    Sum<<D1 as Dimensions>::T, <D2 as Dimensions>::T>,
    Sum<<D1 as Dimensions>::L, <D2 as Dimensions>::L>,
    Sum<<D1 as Dimensions>::M, <D2 as Dimensions>::M>,
    Sum<<D1 as Dimensions>::I, <D2 as Dimensions>::I>,
    Sum<<D1 as Dimensions>::K, <D2 as Dimensions>::K>,
    Sum<<D1 as Dimensions>::N, <D2 as Dimensions>::N>,
    Sum<<D1 as Dimensions>::J, <D2 as Dimensions>::J>,
>;

/// Subtract two dimensions.
pub type DimensionSub<D1, D2> = Dimension<
    Diff<<D1 as Dimensions>::T, <D2 as Dimensions>::T>,
    Diff<<D1 as Dimensions>::L, <D2 as Dimensions>::L>,
    Diff<<D1 as Dimensions>::M, <D2 as Dimensions>::M>,
    Diff<<D1 as Dimensions>::I, <D2 as Dimensions>::I>,
    Diff<<D1 as Dimensions>::K, <D2 as Dimensions>::K>,
    Diff<<D1 as Dimensions>::N, <D2 as Dimensions>::N>,
    Diff<<D1 as Dimensions>::J, <D2 as Dimensions>::J>,
>;

/// Multiply two dimensions (add exponents).
pub type DimensionMul<D1, D2> = DimensionAdd<D1, D2>;

/// Divide two dimensions (subtract exponents).
pub type DimensionDiv<D1, D2> = DimensionSub<D1, D2>;

/// Raise dimension to a power (multiply all exponents).
pub type DimensionPow<D, E> = Dimension<
    Prod<<D as Dimensions>::T, E>,
    Prod<<D as Dimensions>::L, E>,
    Prod<<D as Dimensions>::M, E>,
    Prod<<D as Dimensions>::I, E>,
    Prod<<D as Dimensions>::K, E>,
    Prod<<D as Dimensions>::N, E>,
    Prod<<D as Dimensions>::J, E>,
>;

/// Reciprocal of a dimension (negate all exponents).
pub type DimensionRecip<D> = Dimension<
    Negate<<D as Dimensions>::T>,
    Negate<<D as Dimensions>::L>,
    Negate<<D as Dimensions>::M>,
    Negate<<D as Dimensions>::I>,
    Negate<<D as Dimensions>::K>,
    Negate<<D as Dimensions>::N>,
    Negate<<D as Dimensions>::J>,
>;

/// Helper trait for dimension multiplication operations.
/// Encapsulates all the trait bounds needed for multiplying two dimensions.
///
/// Bound for code that is generic over dimensions:
///
/// ```
/// use danwi::{Quantity, Scalar, dimension::CanMultiplyWith};
///
/// fn square<S, D>(q: Quantity<S, D>) -> Quantity<S, D::Output>
/// where
///     S: Scalar,
///     D: CanMultiplyWith<D>,
/// {
///     q * q
/// }
///
/// use danwi::prelude::*;
///
/// let area = square(3.0.m());
/// assert_eq!(area, 3.0.m() * 3.0.m());
/// ```
pub trait CanMultiplyWith<Rhs: Dimensions>: Dimensions {
    type Output: Dimensions;
}

// Blanket implementation for all valid dimension combinations
impl<Lhs, Rhs> CanMultiplyWith<Rhs> for Lhs
where
    Lhs: Dimensions,
    Rhs: Dimensions,
    <Lhs as Dimensions>::T: Add<<Rhs as Dimensions>::T>,
    <Lhs as Dimensions>::L: Add<<Rhs as Dimensions>::L>,
    <Lhs as Dimensions>::M: Add<<Rhs as Dimensions>::M>,
    <Lhs as Dimensions>::I: Add<<Rhs as Dimensions>::I>,
    <Lhs as Dimensions>::K: Add<<Rhs as Dimensions>::K>,
    <Lhs as Dimensions>::N: Add<<Rhs as Dimensions>::N>,
    <Lhs as Dimensions>::J: Add<<Rhs as Dimensions>::J>,
    Sum<<Lhs as Dimensions>::T, <Rhs as Dimensions>::T>: Integer,
    Sum<<Lhs as Dimensions>::L, <Rhs as Dimensions>::L>: Integer,
    Sum<<Lhs as Dimensions>::M, <Rhs as Dimensions>::M>: Integer,
    Sum<<Lhs as Dimensions>::I, <Rhs as Dimensions>::I>: Integer,
    Sum<<Lhs as Dimensions>::K, <Rhs as Dimensions>::K>: Integer,
    Sum<<Lhs as Dimensions>::N, <Rhs as Dimensions>::N>: Integer,
    Sum<<Lhs as Dimensions>::J, <Rhs as Dimensions>::J>: Integer,
{
    type Output = DimensionMul<Lhs, Rhs>;
}

/// Helper trait for dimension division operations.
/// Encapsulates all the trait bounds needed for dividing two dimensions.
pub trait CanDivideBy<Rhs: Dimensions>: Dimensions {
    type Output: Dimensions;
}

// Blanket implementation for all valid dimension combinations
impl<Lhs, Rhs> CanDivideBy<Rhs> for Lhs
where
    Lhs: Dimensions,
    Rhs: Dimensions,
    <Lhs as Dimensions>::T: Sub<<Rhs as Dimensions>::T>,
    <Lhs as Dimensions>::L: Sub<<Rhs as Dimensions>::L>,
    <Lhs as Dimensions>::M: Sub<<Rhs as Dimensions>::M>,
    <Lhs as Dimensions>::I: Sub<<Rhs as Dimensions>::I>,
    <Lhs as Dimensions>::K: Sub<<Rhs as Dimensions>::K>,
    <Lhs as Dimensions>::N: Sub<<Rhs as Dimensions>::N>,
    <Lhs as Dimensions>::J: Sub<<Rhs as Dimensions>::J>,
    Diff<<Lhs as Dimensions>::T, <Rhs as Dimensions>::T>: Integer,
    Diff<<Lhs as Dimensions>::L, <Rhs as Dimensions>::L>: Integer,
    Diff<<Lhs as Dimensions>::M, <Rhs as Dimensions>::M>: Integer,
    Diff<<Lhs as Dimensions>::I, <Rhs as Dimensions>::I>: Integer,
    Diff<<Lhs as Dimensions>::K, <Rhs as Dimensions>::K>: Integer,
    Diff<<Lhs as Dimensions>::N, <Rhs as Dimensions>::N>: Integer,
    Diff<<Lhs as Dimensions>::J, <Rhs as Dimensions>::J>: Integer,
{
    type Output = DimensionDiv<Lhs, Rhs>;
}

/// Helper trait for dimensions that can be reciprocated (1/D).
pub trait CanReciprocate: Dimensions {
    type Output: Dimensions;
}

impl<D> CanReciprocate for D
where
    D: Dimensions,
    <D as Dimensions>::T: Neg,
    <D as Dimensions>::L: Neg,
    <D as Dimensions>::M: Neg,
    <D as Dimensions>::I: Neg,
    <D as Dimensions>::K: Neg,
    <D as Dimensions>::N: Neg,
    <D as Dimensions>::J: Neg,
    Negate<<D as Dimensions>::T>: Integer,
    Negate<<D as Dimensions>::L>: Integer,
    Negate<<D as Dimensions>::M>: Integer,
    Negate<<D as Dimensions>::I>: Integer,
    Negate<<D as Dimensions>::K>: Integer,
    Negate<<D as Dimensions>::N>: Integer,
    Negate<<D as Dimensions>::J>: Integer,
{
    type Output = DimensionRecip<D>;
}

// Named dimensions, spelled with concrete exponents in (T, L, M, I, K, N, J)
// order. Do not derive these by composition (`DimensionMul<Mass,
// Acceleration>`): type aliases are not memoized, so nested compositions blow
// up exponentially in the type checker. `tests::derivations_match` checks each
// alias against its defining relation instead.

#[doc(no_inline)]
pub use typenum::{N1, N2, N3, N4, P1, P2, P3, P4, Z0};

/// Dimensionless (pure number).
pub type Dimensionless = Dimension<Z0, Z0, Z0, Z0, Z0, Z0, Z0>;

/// Time (second, s) = T
pub type Time = Dimension<P1, Z0, Z0, Z0, Z0, Z0, Z0>;
/// Length (metre, m) = L
pub type Length = Dimension<Z0, P1, Z0, Z0, Z0, Z0, Z0>;
/// Mass (kilogram, kg) = M
pub type Mass = Dimension<Z0, Z0, P1, Z0, Z0, Z0, Z0>;
/// Electric current (ampere, A) = I
pub type ElectricCurrent = Dimension<Z0, Z0, Z0, P1, Z0, Z0, Z0>;
/// Thermodynamic temperature (kelvin, K) = K
pub type ThermodynamicTemperature = Dimension<Z0, Z0, Z0, Z0, P1, Z0, Z0>;
/// Amount of substance (mole, mol) = N
pub type AmountOfSubstance = Dimension<Z0, Z0, Z0, Z0, Z0, P1, Z0>;
/// Luminous intensity (candela, cd) = J
pub type LuminousIntensity = Dimension<Z0, Z0, Z0, Z0, Z0, Z0, P1>;

// Geometric and kinematic

/// Area (m²) = L²
pub type Area = Dimension<Z0, P2, Z0, Z0, Z0, Z0, Z0>;
/// Volume (m³) = L³
pub type Volume = Dimension<Z0, P3, Z0, Z0, Z0, Z0, Z0>;
/// Frequency (hertz, Hz) = T⁻¹
pub type Frequency = Dimension<N1, Z0, Z0, Z0, Z0, Z0, Z0>;
/// Velocity (metre per second, m/s) = L·T⁻¹
pub type Velocity = Dimension<N1, P1, Z0, Z0, Z0, Z0, Z0>;
/// Acceleration (metre per second squared, m/s²) = L·T⁻²
pub type Acceleration = Dimension<N2, P1, Z0, Z0, Z0, Z0, Z0>;

// Mechanical

/// Force (newton, N) = M·L·T⁻²
pub type Force = Dimension<N2, P1, P1, Z0, Z0, Z0, Z0>;
/// Energy, work, heat (joule, J) = M·L²·T⁻²
pub type Energy = Dimension<N2, P2, P1, Z0, Z0, Z0, Z0>;
/// Power (watt, W) = M·L²·T⁻³
pub type Power = Dimension<N3, P2, P1, Z0, Z0, Z0, Z0>;
/// Pressure (pascal, Pa) = M·L⁻¹·T⁻²
pub type Pressure = Dimension<N2, N1, P1, Z0, Z0, Z0, Z0>;
/// Mass flow rate (kg/s) = M·T⁻¹
pub type MassFlowRate = Dimension<N1, Z0, P1, Z0, Z0, Z0, Z0>;

// Electrical and magnetic

/// Electric charge (coulomb, C) = T·I
pub type ElectricCharge = Dimension<P1, Z0, Z0, P1, Z0, Z0, Z0>;
/// Voltage (volt, V) = M·L²·T⁻³·I⁻¹
pub type Voltage = Dimension<N3, P2, P1, N1, Z0, Z0, Z0>;
/// Resistance (ohm, Ω) = M·L²·T⁻³·I⁻²
pub type Resistance = Dimension<N3, P2, P1, N2, Z0, Z0, Z0>;
/// Conductance (siemens, S) = M⁻¹·L⁻²·T³·I²
pub type Conductance = Dimension<P3, N2, N1, P2, Z0, Z0, Z0>;
/// Capacitance (farad, F) = M⁻¹·L⁻²·T⁴·I²
pub type Capacitance = Dimension<P4, N2, N1, P2, Z0, Z0, Z0>;
/// Magnetic flux (weber, Wb) = M·L²·T⁻²·I⁻¹
pub type MagneticFlux = Dimension<N2, P2, P1, N1, Z0, Z0, Z0>;
/// Magnetic flux density (tesla, T) = M·T⁻²·I⁻¹
pub type MagneticFluxDensity = Dimension<N2, Z0, P1, N1, Z0, Z0, Z0>;
/// Inductance (henry, H) = M·L²·T⁻²·I⁻²
pub type Inductance = Dimension<N2, P2, P1, N2, Z0, Z0, Z0>;

#[cfg(test)]
mod tests {
    use super::*;

    trait Same<T> {}
    impl<T> Same<T> for T {}

    fn check<A: Same<B>, B>() {}

    /// Each derived dimension equals its defining relation, one composition
    /// level at a time (deep nesting is what the flat exponents avoid).
    #[test]
    fn derivations_match() {
        check::<Volume, DimensionPow<Length, typenum::P3>>();
        check::<Area, DimensionMul<Length, Length>>();
        check::<Volume, DimensionMul<Area, Length>>();
        check::<Frequency, DimensionRecip<Time>>();
        check::<Velocity, DimensionDiv<Length, Time>>();
        check::<Acceleration, DimensionDiv<Velocity, Time>>();
        check::<Force, DimensionMul<Mass, Acceleration>>();
        check::<Energy, DimensionMul<Force, Length>>();
        check::<Power, DimensionDiv<Energy, Time>>();
        check::<Pressure, DimensionDiv<Force, Area>>();
        check::<MassFlowRate, DimensionDiv<Mass, Time>>();
        check::<ElectricCharge, DimensionMul<ElectricCurrent, Time>>();
        check::<Voltage, DimensionDiv<Power, ElectricCurrent>>();
        check::<Resistance, DimensionDiv<Voltage, ElectricCurrent>>();
        check::<Conductance, DimensionRecip<Resistance>>();
        check::<Capacitance, DimensionDiv<ElectricCharge, Voltage>>();
        check::<MagneticFlux, DimensionMul<Voltage, Time>>();
        check::<MagneticFluxDensity, DimensionDiv<MagneticFlux, Area>>();
        check::<Inductance, DimensionDiv<MagneticFlux, ElectricCurrent>>();
    }
}