diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e722b3..b3c36cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.13.0] - 2026-01-06 +### Changed +- Use const generics instead of types from `typenum` to represent units. [#95](https://github.com/itt-ustutt/quantity/pull/95) + ## [0.12.2] - 2025-12-04 ### Fixed - Also updated `num-dual` dependency to 0.13 to fix incorrect dependency resolution for downstream crates. [#95](https://github.com/itt-ustutt/quantity/pull/95) diff --git a/Cargo.toml b/Cargo.toml index 22b1125..f8dd49e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quantity" -version = "0.12.2" +version = "0.13.0" authors = [ "Philipp Rehner ", "Gernot Bauer ", @@ -24,7 +24,6 @@ rustdoc-args = ["--html-in-header", "./src/docs-header.html"] members = ["si-units", "example/extend_quantity"] [dependencies] -typenum = "1.17" num-traits = "0.2" document-features = "0.2" ## Use N-dimensional arrays from the [ndarray] crate as value of a quantity. diff --git a/README.md b/README.md index 7b9d91a..ca9efe9 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Add this to your `Cargo.toml`: ``` [dependencies] -quantity = "0.12" +quantity = "0.13" ``` ## Examples @@ -24,7 +24,7 @@ Calculate pressure of an ideal gas. ```rust let temperature = 25.0 * CELSIUS; -let volume = 1.5 * METER.powi::(); +let volume = 1.5 * METER.powi::<3>(); let moles = 75.0 * MOL; let pressure = moles * RGAS * temperature / volume; println!("{:.5}", pressure); // 123.94785 kPa @@ -36,7 +36,7 @@ Calculate the gravitational pull of the moon on the earth. let mass_earth = 5.9724e24 * KILOGRAM; let mass_moon = 7.346e22 * KILOGRAM; let distance = 383.398 * KILO * METER; -let force = G * mass_earth * mass_moon / distance.powi::(); +let force = G * mass_earth * mass_moon / distance.powi::<2>(); println!("{:.5e}", force); // 1.99208e26 N ``` @@ -44,7 +44,7 @@ Calculate the pressure distribution in the atmosphere using the barometric formu ```rust let z = Quantity::linspace(1.0 * METER, 70.0 * KILO * METER, 10); -let g = 9.81 * METER / SECOND.powi::(); +let g = 9.81 * METER / SECOND.powi::<2>(); let m = 28.949 * GRAM / MOL; let t = 10.0 * CELSIUS; let p0 = BAR; diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..012a684 --- /dev/null +++ b/build.rs @@ -0,0 +1,75 @@ +use std::env; +use std::fmt::Write; +use std::fs; +use std::path::Path; + +fn main() { + // Generate Neg, Add, Mul, Div, Sub impls for Const + // Limiting results of operations to values within [min, max] + + // range of exponents + // use i32 for results that would overflow i8, we will limit to min/max in loop + let min: i32 = -20; + let max: i32 = 20; + + let out_dir = env::var_os("OUT_DIR").unwrap(); + let dest_path = Path::new(&out_dir).join("const_impls.rs"); + let mut out = String::new(); + + // go over all exponent combinations + // for each operation, limit results to min/max + for a in min..=max { + // negation + let neg = -a; + if neg >= min && neg <= max { + writeln!( + &mut out, + "impl Neg for Const<{a}> {{ type Output = Const<{neg}>; fn neg(self) -> Self::Output {{ Const }} }}" + ).unwrap(); + } + + for b in min..=max { + // addition + let sum = a + b; + if sum >= min && sum <= max { + writeln!( + &mut out, + "impl Add> for Const<{a}> {{ type Output = Const<{sum}>; fn add(self, _: Const<{b}>) -> Self::Output {{ Const }} }}" + ).unwrap(); + } + + // subtraction + let diff = a - b; + if diff >= min && diff <= max { + writeln!( + &mut out, + "impl Sub> for Const<{a}> {{ type Output = Const<{diff}>; fn sub(self, _: Const<{b}>) -> Self::Output {{ Const }} }}" + ).unwrap(); + } + + // multiplication + let mul = a * b; + if mul >= min && mul <= max { + writeln!( + &mut out, + "impl Mul> for Const<{a}> {{ type Output = Const<{mul}>; fn mul(self, _: Const<{b}>) -> Self::Output {{ Const }} }}" + ).unwrap(); + } + + // division + // check: don't divide by 0 and only allow for integer results + if b != 0 && a % b == 0 { + let div = a / b; + if div >= min && div <= max { + writeln!( + &mut out, + "impl Div> for Const<{a}> {{ type Output = Const<{div}>; fn div(self, _: Const<{b}>) -> Self::Output {{ Const }} }}" + ).unwrap(); + } + } + } + } + + fs::write(&dest_path, out).unwrap(); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/src/ad.rs b/src/ad.rs index 367bfb2..88bf1c5 100644 --- a/src/ad.rs +++ b/src/ad.rs @@ -1,11 +1,10 @@ -use super::Quantity; +use super::{Diff, Quantity}; use nalgebra::{DefaultAllocator, Dim, OMatrix, OVector, U1, allocator::Allocator}; use num_dual::{ Dual, Dual2, Dual2Vec, Dual3, DualNum, DualStruct, DualVec, Gradients, HyperDual, HyperDualVec, HyperHyperDual, Real, }; use std::ops::Sub; -use typenum::Diff; impl, U> DualStruct for Quantity { type Real = Quantity; @@ -338,32 +337,10 @@ where #[cfg(test)] mod test_num_dual { use super::*; - use crate::{Area, Length, METER, Temperature, Volume}; + use crate::{Area, Length, METER, Volume}; use approx::assert_relative_eq; use nalgebra::{SMatrix, SVector, vector}; use num_dual::{Dual64, ImplicitDerivative, ImplicitFunction}; - use typenum::{P2, P3}; - - struct MyArgs { - temperature: Temperature, - } - - impl + Copy> DualStruct for MyArgs { - type Real = MyArgs; - type Inner = MyArgs; - - fn re(&self) -> Self::Real { - MyArgs { - temperature: self.temperature.re(), - } - } - - fn from_inner(inner: &Self::Inner) -> Self { - MyArgs { - temperature: Temperature::from_inner(&inner.temperature), - } - } - } struct AreaImplicit; impl ImplicitFunction for AreaImplicit { @@ -403,27 +380,27 @@ mod test_num_dual { fn test_derivative() { let (v, dv) = first_derivative(volume, 5.0 * METER); println!("{v}\t{dv:3}"); - assert_eq!(v, 125.0 * METER.powi::()); - assert_eq!(dv, 75.0 * METER.powi::()); + assert_eq!(v, 125.0 * METER.powi::<3>()); + assert_eq!(dv, 75.0 * METER.powi::<2>()); let (v, dv, d2v) = second_derivative(volume, 5.0 * METER); println!("{v}\t{dv:3}\t\t{d2v}"); - assert_eq!(v, 125.0 * METER.powi::(),); - assert_eq!(dv, 75.0 * METER.powi::(),); + assert_eq!(v, 125.0 * METER.powi::<3>(),); + assert_eq!(dv, 75.0 * METER.powi::<2>(),); assert_eq!(d2v, 30.0 * METER); let (v, dv_dx, dv_dh, d2v) = second_partial_derivative(volume2, (5.0 * METER, 20.0 * METER)); println!("{v}\t{dv_dx:3}\t{dv_dh:3}\t{d2v}"); - assert_eq!(v, 500.0 * METER.powi::(),); - assert_eq!(dv_dx, 200.0 * METER.powi::(),); - assert_eq!(dv_dh, 25.0 * METER.powi::(),); + assert_eq!(v, 500.0 * METER.powi::<3>(),); + assert_eq!(dv_dx, 200.0 * METER.powi::<2>(),); + assert_eq!(dv_dh, 25.0 * METER.powi::<2>(),); assert_eq!(d2v, 10.0 * METER); let (v, dv, d2v, d3v) = third_derivative(volume, 5.0 * METER); println!("{v}\t{dv:3}\t\t{d2v}\t{d3v}"); - assert_eq!(v, 125.0 * METER.powi::(),); - assert_eq!(dv, 75.0 * METER.powi::(),); + assert_eq!(v, 125.0 * METER.powi::<3>(),); + assert_eq!(dv, 75.0 * METER.powi::<2>(),); assert_eq!(d2v, 30.0 * METER); assert_eq!(d3v.into_value(), 6.0); } diff --git a/src/fmt.rs b/src/fmt.rs index 6b96098..d40d646 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -4,24 +4,23 @@ use ndarray::{Array, Dimension}; use std::collections::HashMap; use std::fmt; use std::sync::LazyLock; -use typenum::{N1, N2, N3, P2, P3, P4, Quot}; const UNIT_SYMBOLS: [&str; 7] = ["s", "m", "kg", "A", "K", "mol", "cd"]; impl< Inner: fmt::Debug, - T: Integer, - L: Integer, - M: Integer, - I: Integer, - THETA: Integer, - N: Integer, - J: Integer, + const T: i8, + const L: i8, + const M: i8, + const I: i8, + const THETA: i8, + const N: i8, + const J: i8, > fmt::Debug for Quantity> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f)?; - let unit = [T::I8, L::I8, M::I8, I::I8, THETA::I8, N::I8, J::I8] + let unit = [T, L, M, I, THETA, N, J] .iter() .zip(UNIT_SYMBOLS.iter()) .filter_map(|(&u, &s)| match u { @@ -48,8 +47,8 @@ pub(crate) trait PrintUnit { } macro_rules! impl_fmt { - ($t:ident, $l:ident, $m:ident, $i:ident, $theta:ident, $n:ident, $unit:expr, $symbol:expr, $has_prefix:expr) => { - impl fmt::LowerExp for Quantity> + ($t:expr, $l:expr, $m:expr, $i:expr, $theta:expr, $n:expr, $unit:expr, $symbol:expr, $has_prefix:expr) => { + impl fmt::LowerExp for Quantity> where for<'a> &'a T: Div, for<'a> Quot<&'a T, f64>: fmt::LowerExp, @@ -60,7 +59,7 @@ macro_rules! impl_fmt { } } - impl fmt::UpperExp for Quantity> + impl fmt::UpperExp for Quantity> where for<'a> &'a T: Div, for<'a> Quot<&'a T, f64>: fmt::UpperExp, @@ -73,7 +72,7 @@ macro_rules! impl_fmt { #[cfg(feature = "ndarray")] impl fmt::Display - for Quantity, SIUnit<$t, $l, $m, $i, $theta, $n, Z0>> + for Quantity, SIUnit<$t, $l, $m, $i, $theta, $n, 0>> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { (self / $unit).into_value().fmt(f)?; @@ -81,7 +80,7 @@ macro_rules! impl_fmt { } } - impl fmt::Display for Quantity> { + impl fmt::Display for Quantity> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (value, prefix) = get_prefix((self / $unit).into_value(), $has_prefix); if !((1e-2..1e4).contains(&value.abs()) || value == 0.0) { @@ -94,30 +93,30 @@ macro_rules! impl_fmt { } #[cfg(feature = "python")] - impl PrintUnit for Quantity> { + impl PrintUnit for Quantity> { const UNIT: &'static str = $symbol; } }; } -impl_fmt!(P1, Z0, Z0, Z0, Z0, Z0, SECOND, "s", Some(KILO)); -impl_fmt!(Z0, P1, Z0, Z0, Z0, Z0, METER, "m", Some(MEGA)); -impl_fmt!(Z0, Z0, P1, Z0, Z0, Z0, GRAM, "g", Some(MEGA)); -impl_fmt!(Z0, Z0, Z0, Z0, Z0, P1, MOL, "mol", Some(MEGA)); -impl_fmt!(Z0, Z0, Z0, Z0, P1, Z0, KELVIN, "K", None); -impl_fmt!(N1, Z0, Z0, Z0, Z0, Z0, HERTZ, "Hz", Some(PETA)); -impl_fmt!(N2, P1, P1, Z0, Z0, Z0, NEWTON, "N", Some(PETA)); -impl_fmt!(N2, N1, P1, Z0, Z0, Z0, PASCAL, "Pa", Some(PETA)); -impl_fmt!(N2, P2, P1, Z0, Z0, Z0, JOULE, "J", Some(PETA)); -impl_fmt!(N3, P2, P1, Z0, Z0, Z0, WATT, "W", Some(PETA)); -impl_fmt!(P1, Z0, Z0, P1, Z0, Z0, COULOMB, "C", None); -impl_fmt!(N3, P2, P1, N1, Z0, Z0, VOLT, "V", Some(PETA)); -impl_fmt!(P4, N2, N1, P2, Z0, Z0, FARAD, "F", Some(PETA)); -impl_fmt!(N3, P2, P1, N2, Z0, Z0, OHM, "Ω", Some(PETA)); -impl_fmt!(P3, N2, N1, P2, Z0, Z0, SIEMENS, "S", Some(PETA)); -impl_fmt!(N2, P2, P1, N1, Z0, Z0, WEBER, "Wb", Some(PETA)); -impl_fmt!(N2, Z0, P1, N1, Z0, Z0, TESLA, "T", Some(PETA)); -impl_fmt!(N2, P2, P1, N2, Z0, Z0, HENRY, "H", Some(PETA)); +impl_fmt!(1, 0, 0, 0, 0, 0, SECOND, "s", Some(KILO)); +impl_fmt!(0, 1, 0, 0, 0, 0, METER, "m", Some(MEGA)); +impl_fmt!(0, 0, 1, 0, 0, 0, GRAM, "g", Some(MEGA)); +impl_fmt!(0, 0, 0, 0, 0, 1, MOL, "mol", Some(MEGA)); +impl_fmt!(0, 0, 0, 0, 1, 0, KELVIN, "K", None); +impl_fmt!(-1, 0, 0, 0, 0, 0, HERTZ, "Hz", Some(PETA)); +impl_fmt!(-2, 1, 1, 0, 0, 0, NEWTON, "N", Some(PETA)); +impl_fmt!(-2, -1, 1, 0, 0, 0, PASCAL, "Pa", Some(PETA)); +impl_fmt!(-2, 2, 1, 0, 0, 0, JOULE, "J", Some(PETA)); +impl_fmt!(-3, 2, 1, 0, 0, 0, WATT, "W", Some(PETA)); +impl_fmt!(1, 0, 0, 1, 0, 0, COULOMB, "C", None); +impl_fmt!(-3, 2, 1, -1, 0, 0, VOLT, "V", Some(PETA)); +impl_fmt!(4, -2, -1, 2, 0, 0, FARAD, "F", Some(PETA)); +impl_fmt!(-3, 2, 1, -2, 0, 0, OHM, "Ω", Some(PETA)); +impl_fmt!(3, -2, -1, 2, 0, 0, SIEMENS, "S", Some(PETA)); +impl_fmt!(-2, 2, 1, -1, 0, 0, WEBER, "Wb", Some(PETA)); +impl_fmt!(-2, 0, 1, -1, 0, 0, TESLA, "T", Some(PETA)); +impl_fmt!(-2, 2, 1, -2, 0, 0, HENRY, "H", Some(PETA)); const M2: Area = Quantity(1.0, PhantomData); const M3: Volume = Quantity(1.0, PhantomData); @@ -127,32 +126,32 @@ const JKGK: SpecificEntropy = Quantity(1.0, PhantomData); const WMK: ThermalConductivity = Quantity(1.0, PhantomData); const GS: MassFlowRate = Quantity(1e-3, PhantomData); -impl_fmt!(Z0, N3, Z0, Z0, Z0, P1, MOL / M3, "mol/m³", Some(MEGA)); -impl_fmt!(Z0, N2, Z0, Z0, Z0, P1, MOL / M2, "mol/m²", Some(MEGA)); -impl_fmt!(Z0, N1, Z0, Z0, Z0, P1, MOL / METER, "mol/m", Some(MEGA)); -impl_fmt!(Z0, P3, Z0, Z0, Z0, N1, M3 / MOL, "m³/mol", None); -impl_fmt!(Z0, P3, Z0, Z0, N1, N1, M3 / MOL / KELVIN, "m³/mol/K", None); -impl_fmt!(Z0, N3, P1, Z0, Z0, Z0, GRAM / M3, "g/m³", Some(MEGA)); -impl_fmt!(N2, Z0, P1, Z0, Z0, Z0, NEWTON / METER, "N/m", Some(PETA)); -impl_fmt!(N1, P2, P1, Z0, Z0, Z0, JOULE * SECOND, "J*s", Some(PETA)); -impl_fmt!(N2, P2, P1, Z0, Z0, N1, JOULE / MOL, "J/mol", Some(PETA)); -impl_fmt!(N2, P2, P1, Z0, N1, Z0, JOULE / KELVIN, "J/K", Some(PETA)); -impl_fmt!(N2, P2, P1, Z0, N1, N1, JMK, "J/mol/K", Some(PETA)); -impl_fmt!(N2, P2, Z0, Z0, Z0, Z0, JOULE / KG, "J/kg", Some(PETA)); -impl_fmt!(N2, P2, Z0, Z0, N1, Z0, JKGK, "J/kg/K", Some(PETA)); -impl_fmt!(N1, N1, P1, Z0, Z0, Z0, PASCAL * SECOND, "Pa*s", Some(PETA)); -impl_fmt!(N1, P1, Z0, Z0, Z0, Z0, METER / SECOND, "m/s", Some(MEGA)); -impl_fmt!(N1, P2, Z0, Z0, Z0, Z0, M2 / SECOND, "m²/s", None); -impl_fmt!(N3, P1, P1, Z0, N1, Z0, WMK, "W/m/K", Some(PETA)); -impl_fmt!(Z0, Z0, P1, Z0, Z0, N1, GRAM / MOL, "g/mol", Some(MEGA)); -impl_fmt!(Z0, P2, Z0, Z0, Z0, Z0, M2, "m²", None); -impl_fmt!(Z0, P3, Z0, Z0, Z0, Z0, M3, "m³", None); -impl_fmt!(N1, P3, N1, Z0, Z0, Z0, M3 / KG / SECOND, "m³/kg/s²", None); -impl_fmt!(N3, P2, P1, Z0, N1, Z0, WATT / KELVIN, "W/K", None); -impl_fmt!(N3, Z0, P1, Z0, N1, Z0, WMK / METER, "W/m²/K", None); -impl_fmt!(N3, Z0, P1, Z0, Z0, Z0, WATT / M2, "W/m²", None); -impl_fmt!(N1, Z0, P1, Z0, Z0, Z0, GS, "g/s", Some(MEGA)); -impl_fmt!(N1, N2, P1, Z0, Z0, Z0, GS / M2, "g/m²/s", Some(MEGA)); +impl_fmt!(0, -3, 0, 0, 0, 1, MOL / M3, "mol/m³", Some(MEGA)); +impl_fmt!(0, -2, 0, 0, 0, 1, MOL / M2, "mol/m²", Some(MEGA)); +impl_fmt!(0, -1, 0, 0, 0, 1, MOL / METER, "mol/m", Some(MEGA)); +impl_fmt!(0, 3, 0, 0, 0, -1, M3 / MOL, "m³/mol", None); +impl_fmt!(0, 3, 0, 0, -1, -1, M3 / MOL / KELVIN, "m³/mol/K", None); +impl_fmt!(0, -3, 1, 0, 0, 0, GRAM / M3, "g/m³", Some(MEGA)); +impl_fmt!(-2, 0, 1, 0, 0, 0, NEWTON / METER, "N/m", Some(PETA)); +impl_fmt!(-1, 2, 1, 0, 0, 0, JOULE * SECOND, "J*s", Some(PETA)); +impl_fmt!(-2, 2, 1, 0, 0, -1, JOULE / MOL, "J/mol", Some(PETA)); +impl_fmt!(-2, 2, 1, 0, -1, 0, JOULE / KELVIN, "J/K", Some(PETA)); +impl_fmt!(-2, 2, 1, 0, -1, -1, JMK, "J/mol/K", Some(PETA)); +impl_fmt!(-2, 2, 0, 0, 0, 0, JOULE / KG, "J/kg", Some(PETA)); +impl_fmt!(-2, 2, 0, 0, -1, 0, JKGK, "J/kg/K", Some(PETA)); +impl_fmt!(-1, -1, 1, 0, 0, 0, PASCAL * SECOND, "Pa*s", Some(PETA)); +impl_fmt!(-1, 1, 0, 0, 0, 0, METER / SECOND, "m/s", Some(MEGA)); +impl_fmt!(-1, 2, 0, 0, 0, 0, M2 / SECOND, "m²/s", None); +impl_fmt!(-3, 1, 1, 0, -1, 0, WMK, "W/m/K", Some(PETA)); +impl_fmt!(0, 0, 1, 0, 0, -1, GRAM / MOL, "g/mol", Some(MEGA)); +impl_fmt!(0, 2, 0, 0, 0, 0, M2, "m²", None); +impl_fmt!(0, 3, 0, 0, 0, 0, M3, "m³", None); +impl_fmt!(-1, 3, -1, 0, 0, 0, M3 / KG / SECOND, "m³/kg/s²", None); +impl_fmt!(-3, 2, 1, 0, -1, 0, WATT / KELVIN, "W/K", None); +impl_fmt!(-3, 0, 1, 0, -1, 0, WMK / METER, "W/m²/K", None); +impl_fmt!(-3, 0, 1, 0, 0, 0, WATT / M2, "W/m²", None); +impl_fmt!(-1, 0, 1, 0, 0, 0, GS, "g/s", Some(MEGA)); +impl_fmt!(-1, -2, 1, 0, 0, 0, GS / M2, "g/m²/s", Some(MEGA)); fn get_prefix(value: f64, has_prefix: Option) -> (f64, &'static str) { if let Some(p) = has_prefix { diff --git a/src/lib.rs b/src/lib.rs index bb9f58f..78f03ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,9 +91,8 @@ //! Calculate pressure of an ideal gas. //! ``` //! # use quantity::*; -//! # use typenum::P3; //! let temperature = 25.0 * CELSIUS; -//! let volume = 1.5 * METER.powi::(); +//! let volume = 1.5 * METER.powi::<3>(); //! let moles = 75.0 * MOL; //! let pressure = moles * RGAS * temperature / volume; //! println!("{:.5}", pressure); // 123.94785 kPa @@ -102,11 +101,10 @@ //! Calculate the gravitational pull of the moon on the earth. //! ``` //! # use quantity::*; -//! # use typenum::P2; //! let mass_earth = 5.9724e24 * KILOGRAM; //! let mass_moon = 7.346e22 * KILOGRAM; //! let distance = 383.398 * KILO * METER; -//! let force = G * mass_earth * mass_moon / distance.powi::(); +//! let force = G * mass_earth * mass_moon / distance.powi::<2>(); //! println!("{:.5e}", force); // 1.99208e26 N //! ``` //! @@ -116,9 +114,8 @@ //! # #[cfg(feature = "ndarray")] //! # { //! # use quantity::*; -//! # use typenum::P2; //! let z = Length::linspace(1.0 * METER, 70.0 * KILO * METER, 10); -//! let g = 9.81 * METER / SECOND.powi::(); +//! let g = 9.81 * METER / SECOND.powi::<2>(); //! let m = 28.949 * GRAM / MOL; //! let t = 10.0 * CELSIUS; //! let p0 = BAR; @@ -145,8 +142,7 @@ #[cfg(feature = "ndarray")] use ndarray::{Array, ArrayBase, Data, Dimension}; use std::marker::PhantomData; -use std::ops::{Deref, Div, Mul}; -use typenum::{ATerm, Diff, Integer, N1, N2, Negate, P1, P3, Quot, Sum, TArr, Z0}; +use std::ops::{Add, Deref, Div, Mul, Neg, Sub}; #[cfg(feature = "num-dual")] pub mod ad; @@ -159,22 +155,221 @@ mod ops; #[cfg(feature = "python")] mod python; -pub type SIUnit = - TArr>>>>>>; +type Sum = >::Output; +type Diff = >::Output; +type Negate = ::Output; +type Prod = >::Output; +type Quot = >::Output; + +/// Convertion between const generics and the Rust type system. +pub struct Const; + +// implements all operations (+,-,*,/) for integers within a given range. +include!(concat!(env!("OUT_DIR"), "/const_impls.rs")); + +/// A compile-time representation of an SI unit based on the exponents of +/// the seven base units (time, length, mass, current, temperature, amount +/// of substance, luminous intensity) +#[derive(Clone, Copy)] +pub struct SIUnit< + const T: i8, + const L: i8, + const M: i8, + const I: i8, + const THETA: i8, + const N: i8, + const J: i8, +>; + +impl< + const T1: i8, + const L1: i8, + const M1: i8, + const I1: i8, + const THETA1: i8, + const N1: i8, + const J1: i8, + const T2: i8, + const L2: i8, + const M2: i8, + const I2: i8, + const THETA2: i8, + const N2: i8, + const J2: i8, + const T3: i8, + const L3: i8, + const M3: i8, + const I3: i8, + const THETA3: i8, + const N3: i8, + const J3: i8, +> Add> for SIUnit +where + Const: Add, Output = Const>, + Const: Add, Output = Const>, + Const: Add, Output = Const>, + Const: Add, Output = Const>, + Const: Add, Output = Const>, + Const: Add, Output = Const>, + Const: Add, Output = Const>, +{ + type Output = SIUnit; + + fn add(self, _: SIUnit) -> Self::Output { + SIUnit + } +} + +impl< + const T1: i8, + const L1: i8, + const M1: i8, + const I1: i8, + const THETA1: i8, + const N1: i8, + const J1: i8, + const T2: i8, + const L2: i8, + const M2: i8, + const I2: i8, + const THETA2: i8, + const N2: i8, + const J2: i8, +> Neg for SIUnit +where + Const: Neg>, + Const: Neg>, + Const: Neg>, + Const: Neg>, + Const: Neg>, + Const: Neg>, + Const: Neg>, +{ + type Output = SIUnit; + + fn neg(self) -> Self::Output { + SIUnit + } +} + +impl< + const T1: i8, + const L1: i8, + const M1: i8, + const I1: i8, + const THETA1: i8, + const N1: i8, + const J1: i8, + const T2: i8, + const L2: i8, + const M2: i8, + const I2: i8, + const THETA2: i8, + const N2: i8, + const J2: i8, + const T3: i8, + const L3: i8, + const M3: i8, + const I3: i8, + const THETA3: i8, + const N3: i8, + const J3: i8, +> Sub> for SIUnit +where + Const: Sub, Output = Const>, + Const: Sub, Output = Const>, + Const: Sub, Output = Const>, + Const: Sub, Output = Const>, + Const: Sub, Output = Const>, + Const: Sub, Output = Const>, + Const: Sub, Output = Const>, +{ + type Output = SIUnit; + + fn sub(self, _: SIUnit) -> Self::Output { + SIUnit + } +} + +impl< + const T1: i8, + const L1: i8, + const M1: i8, + const I1: i8, + const THETA1: i8, + const N1: i8, + const J1: i8, + const T2: i8, + const L2: i8, + const M2: i8, + const I2: i8, + const THETA2: i8, + const N2: i8, + const J2: i8, + const E: i8, +> Mul> for SIUnit +where + Const: Mul, Output = Const>, + Const: Mul, Output = Const>, + Const: Mul, Output = Const>, + Const: Mul, Output = Const>, + Const: Mul, Output = Const>, + Const: Mul, Output = Const>, + Const: Mul, Output = Const>, +{ + type Output = SIUnit; + + fn mul(self, _: Const) -> Self::Output { + SIUnit + } +} + +impl< + const T1: i8, + const L1: i8, + const M1: i8, + const I1: i8, + const THETA1: i8, + const N1: i8, + const J1: i8, + const T2: i8, + const L2: i8, + const M2: i8, + const I2: i8, + const THETA2: i8, + const N2: i8, + const J2: i8, + const E: i8, +> Div> for SIUnit +where + Const: Div, Output = Const>, + Const: Div, Output = Const>, + Const: Div, Output = Const>, + Const: Div, Output = Const>, + Const: Div, Output = Const>, + Const: Div, Output = Const>, + Const: Div, Output = Const>, +{ + type Output = SIUnit; + + fn div(self, _: Const) -> Self::Output { + SIUnit + } +} /// Physical quantity with compile-time checked unit. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Quantity(T, PhantomData); -pub type _Dimensionless = SIUnit; -pub type _Time = SIUnit; -pub type _Length = SIUnit; -pub type _Mass = SIUnit; -pub type _Current = SIUnit; -pub type _Temperature = SIUnit; -pub type _Moles = SIUnit; -pub type _LuminousIntensity = SIUnit; +pub type _Dimensionless = SIUnit<0, 0, 0, 0, 0, 0, 0>; +pub type _Time = SIUnit<1, 0, 0, 0, 0, 0, 0>; +pub type _Length = SIUnit<0, 1, 0, 0, 0, 0, 0>; +pub type _Mass = SIUnit<0, 0, 1, 0, 0, 0, 0>; +pub type _Current = SIUnit<0, 0, 0, 1, 0, 0, 0>; +pub type _Temperature = SIUnit<0, 0, 0, 0, 1, 0, 0>; +pub type _Moles = SIUnit<0, 0, 0, 0, 0, 1, 0>; +pub type _LuminousIntensity = SIUnit<0, 0, 0, 0, 0, 0, 1>; pub type Dimensionless = Quantity; pub type Time = Quantity; @@ -352,11 +547,9 @@ pub const QE: Charge = Quantity(1.602176634e-19, PhantomData); /// Speed of light $\\left(c=299792458\\,\\frac{\text{m}}{\text{s}}\\right)$ pub const CLIGHT: Velocity = Quantity(299792458.0, PhantomData); /// Luminous efficacy of $540\\,\text{THz}$ radiation $\\left(K_\text{cd}=683\\,\\frac{\text{lm}}{\text{W}}\\right)$ -#[expect(clippy::type_complexity)] -pub const KCD: Quantity> = Quantity(683.0, PhantomData); +pub const KCD: Quantity> = Quantity(683.0, PhantomData); /// Gravitational constant $\\left(G=6.6743\\times 10^{-11}\\,\\frac{\text{m}^3}{\text{kg}\cdot\text{s}^2}\\right)$ -#[expect(clippy::type_complexity)] -pub const G: Quantity> = Quantity(6.6743e-11, PhantomData); +pub const G: Quantity> = Quantity(6.6743e-11, PhantomData); /// Prefix quecto $\\left(\text{q}=10^{-30}\\right)$ pub const QUECTO: f64 = 1e-30; @@ -527,6 +720,72 @@ impl Deref for Dimensionless { mod test { use super::*; + #[test] + fn test_quantity_instantiation() { + let t = 10.0 * SECOND; + assert_eq!(t.0, 10.0); + + let l = 5.0 * METER; + assert_eq!(l.0, 5.0); + } + + #[test] + fn test_quantity_conversion() { + let dist = 1.5 * KILO * METER; + let raw_m = dist.convert_into(METER); + assert!((raw_m - 1500.0).abs() < 1e-10); + + let km = Quantity::new(1000.0); + let val_km = dist.convert_to(km); + assert!((val_km - 1.5).abs() < 1e-10); + } + + #[test] + fn test_celsius_conversion() { + let c = 0.0 * CELSIUS; + assert_eq!(c.0, 273.15); + + let zero = c / CELSIUS; + assert!(zero.abs() < 1e-15); + } + + #[test] + fn test_prefix_scaling() { + let v1 = 1.0 * MILLI * METER; + let v2 = 1.0 * KILO * METER; + + assert_eq!(v1.0, 0.001); + assert_eq!(v2.0, 1000.0); + } + + #[test] + fn test_quantity_arithmetic() { + let d = 10.0 * METER; + let t = 2.0 * SECOND; + + let v = d / t; + assert_eq!(v.0, 5.0); + + let m = 5.0 * KILOGRAM; + let a = 2.0 * METER / (SECOND * SECOND); + let f = m * a; + assert_eq!(f.0, 10.0); + + let l1 = 1.0 * METER; + let l2 = 2.0 * METER; + let l3 = l1 + l2; + assert_eq!(l3.0, 3.0); + } + + #[test] + fn test_angles() { + let ninety_deg = 90.0 * DEGREES; + let half_pi = std::f64::consts::FRAC_PI_2; + + assert!((ninety_deg.0 - half_pi).abs() < 1e-10); + assert!((ninety_deg.sin() - 1.0).abs() < 1e-10); + } + #[test] fn test_deref() { let pressure = 1.0135 * BAR; diff --git a/src/nalgebra.rs b/src/nalgebra.rs index aa6014d..dc9c8fc 100644 --- a/src/nalgebra.rs +++ b/src/nalgebra.rs @@ -1,11 +1,10 @@ -use super::Quantity; +use super::{Quantity, Sum}; use nalgebra::allocator::Allocator; use nalgebra::constraint::{DimEq, ShapeConstraint}; use nalgebra::{ClosedAddAssign, ClosedMulAssign, DMatrix, DefaultAllocator, Dim, OMatrix, Scalar}; use num_traits::Zero; use std::marker::PhantomData; use std::ops::Add; -use typenum::Sum; impl Quantity, U> where diff --git a/src/ops.rs b/src/ops.rs index 8aaa9b1..9f7a85f 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -1,4 +1,4 @@ -use super::Quantity; +use super::{Const, Diff, Negate, Prod, Quantity, Quot, Sum}; #[cfg(feature = "approx")] use approx::{AbsDiffEq, RelativeEq}; #[cfg(feature = "nalgebra")] @@ -12,7 +12,6 @@ use num_dual::DualNum; use num_traits::{Inv, Signed}; use std::marker::PhantomData; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; -use typenum::{Diff, Integer, Negate, P2, P3, Prod, Quot, Sum}; // Multiplication impl Mul> for Quantity @@ -372,15 +371,14 @@ impl Quantity { /// ``` /// # use quantity::METER; /// # use approx::assert_relative_eq; - /// # use typenum::P2; /// let x = 3.0 * METER; - /// assert_relative_eq!(x.powi::(), 9.0 * METER * METER); + /// assert_relative_eq!(x.powi::<2>(), 9.0 * METER * METER); /// ``` - pub fn powi(self) -> Quantity> + pub fn powi(self) -> Quantity>> where - U: Mul, + U: Mul>, { - Quantity(self.0.powi(E::I32), PhantomData) + Quantity(self.0.powi(E as i32), PhantomData) } /// Calculate the square root of self. @@ -392,9 +390,9 @@ impl Quantity { /// let x = 9.0 * METER * METER; /// assert_relative_eq!(x.sqrt(), 3.0 * METER); /// ``` - pub fn sqrt(self) -> Quantity> + pub fn sqrt(self) -> Quantity>> where - U: Div, + U: Div>, { Quantity(self.0.sqrt(), PhantomData) } @@ -408,9 +406,9 @@ impl Quantity { /// let x = 27.0 * METER * METER * METER; /// assert_relative_eq!(x.cbrt(), 3.0 * METER); /// ``` - pub fn cbrt(self) -> Quantity> + pub fn cbrt(self) -> Quantity>> where - U: Div, + U: Div>, { Quantity(self.0.cbrt(), PhantomData) } @@ -421,15 +419,14 @@ impl Quantity { /// ``` /// # use quantity::METER; /// # use approx::assert_relative_eq; - /// # use typenum::P4; /// let x = 81.0 * METER * METER * METER * METER; - /// assert_relative_eq!(x.root::(), 3.0 * METER); + /// assert_relative_eq!(x.root::<4>(), 3.0 * METER); /// ``` - pub fn root(self) -> Quantity> + pub fn root(self) -> Quantity>> where - U: Div, + U: Div>, { - Quantity(self.0.powf(1.0 / R::I32 as f64), PhantomData) + Quantity(self.0.powf(1.0 / R as f64), PhantomData) } } @@ -441,15 +438,14 @@ impl, U> Quantity { /// ``` /// # use quantity::METER; /// # use approx::assert_relative_eq; - /// # use typenum::P2; /// let x = 3.0 * METER; - /// assert_relative_eq!(x.powi::(), 9.0 * METER * METER); + /// assert_relative_eq!(x.powi::<2>(), 9.0 * METER * METER); /// ``` - pub fn powi(self) -> Quantity> + pub fn powi(self) -> Quantity>> where - U: Mul, + U: Mul>, { - Quantity(self.0.powi(E::I32), PhantomData) + Quantity(self.0.powi(E as i32), PhantomData) } /// Calculate the square root of self. @@ -461,9 +457,9 @@ impl, U> Quantity { /// let x = 9.0 * METER * METER; /// assert_relative_eq!(x.sqrt(), 3.0 * METER); /// ``` - pub fn sqrt(self) -> Quantity> + pub fn sqrt(self) -> Quantity>> where - U: Div, + U: Div>, { Quantity(self.0.sqrt(), PhantomData) } @@ -477,9 +473,9 @@ impl, U> Quantity { /// let x = 27.0 * METER * METER * METER; /// assert_relative_eq!(x.cbrt(), 3.0 * METER); /// ``` - pub fn cbrt(self) -> Quantity> + pub fn cbrt(self) -> Quantity>> where - U: Div, + U: Div>, { Quantity(self.0.cbrt(), PhantomData) } @@ -490,15 +486,14 @@ impl, U> Quantity { /// ``` /// # use quantity::METER; /// # use approx::assert_relative_eq; - /// # use typenum::P4; /// let x = 81.0 * METER * METER * METER * METER; - /// assert_relative_eq!(x.root::(), 3.0 * METER); + /// assert_relative_eq!(x.root::<4>(), 3.0 * METER); /// ``` - pub fn root(self) -> Quantity> + pub fn root(self) -> Quantity>> where - U: Div, + U: Div>, { - Quantity(self.0.powf(1.0 / R::I32 as f64), PhantomData) + Quantity(self.0.powf(1.0 / R as f64), PhantomData) } } diff --git a/src/python.rs b/src/python.rs index 45551b9..9fd2d45 100644 --- a/src/python.rs +++ b/src/python.rs @@ -12,7 +12,6 @@ use numpy::PyReadonlyArray; use numpy::{PyReadonlyArray1, PyReadonlyArray2, ToPyArray}; use pyo3::{exceptions::PyValueError, prelude::*}; use std::{marker::PhantomData, sync::LazyLock}; -use typenum::Integer; static SIOBJECT: LazyLock> = LazyLock::new(|| { Python::attach(|py| { @@ -24,15 +23,23 @@ static SIOBJECT: LazyLock> = LazyLock::new(|| { }) }); -impl<'py, T: Integer, L: Integer, M: Integer, I: Integer, THETA: Integer, N: Integer, J: Integer> - IntoPyObject<'py> for Quantity> +impl< + 'py, + const T: i8, + const L: i8, + const M: i8, + const I: i8, + const THETA: i8, + const N: i8, + const J: i8, +> IntoPyObject<'py> for Quantity> { type Target = PyAny; type Output = Bound<'py, PyAny>; type Error = PyErr; fn into_pyobject(self, py: Python<'py>) -> PyResult> { - let unit = [L::I8, M::I8, T::I8, I::I8, N::I8, THETA::I8, J::I8]; + let unit = [L, M, T, I, N, THETA, J]; SIOBJECT.bind(py).call1((self.0, unit)) } } @@ -40,13 +47,13 @@ impl<'py, T: Integer, L: Integer, M: Integer, I: Integer, THETA: Integer, N: Int #[cfg(feature = "ndarray")] impl< 'py, - T: Integer, - L: Integer, - M: Integer, - I: Integer, - THETA: Integer, - N: Integer, - J: Integer, + const T: i8, + const L: i8, + const M: i8, + const I: i8, + const THETA: i8, + const N: i8, + const J: i8, D: Dimension, > IntoPyObject<'py> for Quantity, SIUnit> { @@ -55,44 +62,68 @@ impl< type Error = PyErr; fn into_pyobject(self, py: Python<'py>) -> PyResult> { - let unit = [L::I8, M::I8, T::I8, I::I8, N::I8, THETA::I8, J::I8]; + let unit = [L, M, T, I, N, THETA, J]; let value = self.0.into_pyarray(py).into_any(); SIOBJECT.bind(py).call1((value, unit)) } } #[cfg(feature = "nalgebra")] -impl<'py, T: Integer, L: Integer, M: Integer, I: Integer, THETA: Integer, N: Integer, J: Integer> - IntoPyObject<'py> for Quantity, SIUnit> +impl< + 'py, + const T: i8, + const L: i8, + const M: i8, + const I: i8, + const THETA: i8, + const N: i8, + const J: i8, +> IntoPyObject<'py> for Quantity, SIUnit> { type Target = PyAny; type Output = Bound<'py, PyAny>; type Error = PyErr; fn into_pyobject(self, py: Python<'py>) -> PyResult> { - let unit = [L::I8, M::I8, T::I8, I::I8, N::I8, THETA::I8, J::I8]; + let unit = [L, M, T, I, N, THETA, J]; let value = self.0.to_pyarray(py).into_any(); SIOBJECT.bind(py).call1((value, unit)) } } #[cfg(feature = "nalgebra")] -impl<'py, T: Integer, L: Integer, M: Integer, I: Integer, THETA: Integer, N: Integer, J: Integer> - IntoPyObject<'py> for Quantity, SIUnit> +impl< + 'py, + const T: i8, + const L: i8, + const M: i8, + const I: i8, + const THETA: i8, + const N: i8, + const J: i8, +> IntoPyObject<'py> for Quantity, SIUnit> { type Target = PyAny; type Output = Bound<'py, PyAny>; type Error = PyErr; fn into_pyobject(self, py: Python<'py>) -> PyResult> { - let unit = [L::I8, M::I8, T::I8, I::I8, N::I8, THETA::I8, J::I8]; + let unit = [L, M, T, I, N, THETA, J]; let value = numpy::PyArray1::from_slice(py, self.0.data.as_vec()).into_any(); SIOBJECT.bind(py).call1((value, unit)) } } -impl<'py, T: Integer, L: Integer, M: Integer, I: Integer, THETA: Integer, N: Integer, J: Integer> - FromPyObject<'_, 'py> for Quantity> +impl< + 'py, + const T: i8, + const L: i8, + const M: i8, + const I: i8, + const THETA: i8, + const N: i8, + const J: i8, +> FromPyObject<'_, 'py> for Quantity> where Self: PrintUnit, { @@ -108,7 +139,7 @@ where ob.call_method0("__repr__")? ))); }; - let unit_into = [L::I8, M::I8, T::I8, I::I8, N::I8, THETA::I8, J::I8]; + let unit_into = [L, M, T, I, N, THETA, J]; if unit_into == unit_from { Ok(Quantity(value, PhantomData)) } else { @@ -124,13 +155,13 @@ where #[cfg(feature = "ndarray")] impl< 'py, - T: Integer, - L: Integer, - M: Integer, - I: Integer, - THETA: Integer, - N: Integer, - J: Integer, + const T: i8, + const L: i8, + const M: i8, + const I: i8, + const THETA: i8, + const N: i8, + const J: i8, D: Dimension, > FromPyObject<'_, 'py> for Quantity, SIUnit> where @@ -149,7 +180,7 @@ where ))); }; let value = value.as_array().to_owned(); - let unit_into = [L::I8, M::I8, T::I8, I::I8, N::I8, THETA::I8, J::I8]; + let unit_into = [L, M, T, I, N, THETA, J]; if unit_into == unit_from { Ok(Quantity(value, PhantomData)) } else { @@ -163,8 +194,16 @@ where } #[cfg(feature = "nalgebra")] -impl<'py, T: Integer, L: Integer, M: Integer, I: Integer, THETA: Integer, N: Integer, J: Integer> - FromPyObject<'_, 'py> for Quantity, SIUnit> +impl< + 'py, + const T: i8, + const L: i8, + const M: i8, + const I: i8, + const THETA: i8, + const N: i8, + const J: i8, +> FromPyObject<'_, 'py> for Quantity, SIUnit> where Self: PrintUnit, { @@ -183,7 +222,7 @@ where ob.call_method0("__repr__")? ))); }; - let unit_into = [L::I8, M::I8, T::I8, I::I8, N::I8, THETA::I8, J::I8]; + let unit_into = [L, M, T, I, N, THETA, J]; if unit_into == unit_from { Ok(Quantity(value, PhantomData)) } else { @@ -197,8 +236,16 @@ where } #[cfg(feature = "nalgebra")] -impl<'py, T: Integer, L: Integer, M: Integer, I: Integer, THETA: Integer, N: Integer, J: Integer> - FromPyObject<'_, 'py> for Quantity, SIUnit> +impl< + 'py, + const T: i8, + const L: i8, + const M: i8, + const I: i8, + const THETA: i8, + const N: i8, + const J: i8, +> FromPyObject<'_, 'py> for Quantity, SIUnit> where Self: PrintUnit, { @@ -217,7 +264,7 @@ where ob.call_method0("__repr__")? ))); }; - let unit_into = [L::I8, M::I8, T::I8, I::I8, N::I8, THETA::I8, J::I8]; + let unit_into = [L, M, T, I, N, THETA, J]; if unit_into == unit_from { Ok(Quantity(value, PhantomData)) } else {