From cb3a04957594be62659563e064dab3ea79a2ea96 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 14 Jan 2024 22:10:50 +0100 Subject: [PATCH 01/40] add Magnitude type (that is - for now - just a wrapper over f64) and use it instead of f64 --- crates/diman_lib/Cargo.toml | 3 -- crates/diman_lib/src/dimension_exponent.rs | 6 ++- crates/diman_lib/src/lib.rs | 1 + crates/diman_lib/src/magnitude.rs | 42 +++++++++++++++++++ crates/diman_lib/src/ratio.rs | 6 +-- .../src/codegen/debug_trait.rs | 10 +++-- .../src/codegen/dimensions.rs | 4 +- crates/diman_unit_system/src/codegen/units.rs | 23 ++++------ .../diman_unit_system/src/dimension_math.rs | 8 ++-- crates/diman_unit_system/src/parse/mod.rs | 5 ++- crates/diman_unit_system/src/types/mod.rs | 17 ++++---- .../diman_unit_system/src/types/prefixes.rs | 6 ++- 12 files changed, 89 insertions(+), 42 deletions(-) create mode 100644 crates/diman_lib/src/magnitude.rs diff --git a/crates/diman_lib/Cargo.toml b/crates/diman_lib/Cargo.toml index e7a4a8b..2c6681f 100644 --- a/crates/diman_lib/Cargo.toml +++ b/crates/diman_lib/Cargo.toml @@ -10,6 +10,3 @@ license = "MIT OR Apache-2.0" repository = "https://github.com/tehforsch/diman" [dependencies] - -[dev-dependencies] -num-traits = { version = "0.2.17", default-features = false } \ No newline at end of file diff --git a/crates/diman_lib/src/dimension_exponent.rs b/crates/diman_lib/src/dimension_exponent.rs index 78bd6ca..763d0ce 100644 --- a/crates/diman_lib/src/dimension_exponent.rs +++ b/crates/diman_lib/src/dimension_exponent.rs @@ -1,7 +1,9 @@ use std::ops::{AddAssign, Mul, Neg}; +use crate::magnitude::Magnitude; + pub trait DimensionExponent: Clone + PartialEq + Copy + Mul + AddAssign + Neg { - fn float_pow(num: f64, exponent: Self) -> f64; + fn float_pow(mag: Magnitude, exponent: Self) -> Magnitude; fn one() -> Self; fn zero() -> Self; fn from_int(i: i32) -> Self; @@ -16,7 +18,7 @@ impl DimensionExponent for i64 { 0 } - fn float_pow(num: f64, exponent: Self) -> f64 { + fn float_pow(num: Magnitude, exponent: Self) -> Magnitude { num.powi(exponent as i32) } diff --git a/crates/diman_lib/src/lib.rs b/crates/diman_lib/src/lib.rs index 032b56f..3803ee1 100644 --- a/crates/diman_lib/src/lib.rs +++ b/crates/diman_lib/src/lib.rs @@ -2,5 +2,6 @@ #![feature(generic_const_exprs, adt_const_params)] pub mod dimension_exponent; +pub mod magnitude; pub mod ratio; pub mod runtime_unit_storage; diff --git a/crates/diman_lib/src/magnitude.rs b/crates/diman_lib/src/magnitude.rs new file mode 100644 index 0000000..dd00821 --- /dev/null +++ b/crates/diman_lib/src/magnitude.rs @@ -0,0 +1,42 @@ +use std::ops::{Div, Mul}; + +#[derive(Clone, Copy, PartialEq)] +pub struct Magnitude(f64); + +impl Magnitude { + pub fn new(val: f64) -> Self { + Self(val) + } + + pub const fn as_f64(self) -> f64 { + self.0 + } + + pub fn is_one(&self) -> bool { + self.0 == 1.0 + } + + pub(crate) fn powi(&self, exponent: i32) -> Magnitude { + Self(self.0.powi(exponent)) + } + + pub(crate) fn pow_rational(&self, num: i64, denom: i64) -> Self { + Self(self.0.powf(num as f64 / denom as f64)) + } +} + +impl Mul for Magnitude { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + Self(self.0 * rhs.0) + } +} + +impl Div for Magnitude { + type Output = Self; + + fn div(self, rhs: Self) -> Self::Output { + Self(self.0 / rhs.0) + } +} diff --git a/crates/diman_lib/src/ratio.rs b/crates/diman_lib/src/ratio.rs index 9d73f59..7dd57b7 100644 --- a/crates/diman_lib/src/ratio.rs +++ b/crates/diman_lib/src/ratio.rs @@ -1,4 +1,4 @@ -use crate::dimension_exponent::DimensionExponent; +use crate::{dimension_exponent::DimensionExponent, magnitude::Magnitude}; #[derive( ::core::cmp::PartialEq, @@ -93,8 +93,8 @@ impl DimensionExponent for Ratio { Self { num: 0, denom: 1 } } - fn float_pow(num: f64, exponent: Self) -> f64 { - num.powf(exponent.num as f64 / exponent.denom as f64) + fn float_pow(num: Magnitude, exponent: Self) -> Magnitude { + num.pow_rational(exponent.num, exponent.denom) } fn from_int(i: i32) -> Self { diff --git a/crates/diman_unit_system/src/codegen/debug_trait.rs b/crates/diman_unit_system/src/codegen/debug_trait.rs index 0a66091..086aca7 100644 --- a/crates/diman_unit_system/src/codegen/debug_trait.rs +++ b/crates/diman_unit_system/src/codegen/debug_trait.rs @@ -24,7 +24,7 @@ impl Codegen { let units: TokenStream = units .filter_map(|unit| { let dim = self.get_dimension_expr(&unit.dimensions); - let magnitude = unit.magnitude; + let magnitude = unit.magnitude.as_f64(); let symbol = &unit.symbol.as_ref()?.0.to_string(); Some(quote! { #runtime_unit::new( @@ -44,8 +44,12 @@ impl Codegen { pub fn gen_debug_trait_impl(&self) -> TokenStream { let dimension_type = &self.defs.dimension_type; let quantity_type = &self.defs.quantity_type; - let units_storage = - self.runtime_unit_storage(self.defs.units.iter().filter(|unit| unit.magnitude == 1.0)); + let units_storage = self.runtime_unit_storage( + self.defs + .units + .iter() + .filter(|unit| unit.magnitude.is_one()), + ); let get_base_dimension_symbols = self .defs .base_dimensions diff --git a/crates/diman_unit_system/src/codegen/dimensions.rs b/crates/diman_unit_system/src/codegen/dimensions.rs index 0589d08..c19c472 100644 --- a/crates/diman_unit_system/src/codegen/dimensions.rs +++ b/crates/diman_unit_system/src/codegen/dimensions.rs @@ -142,7 +142,7 @@ impl Codegen { let dimension = self.get_dimension_expr(&constant.dimensions); let quantity_type = &self.defs.quantity_type; let constant_name = &constant.name; - let value = constant.magnitude; + let magnitude = constant.magnitude.as_f64(); let float_type = &type_.base_storage().name; let type_ = type_.name(); // TODO(minor): The allow(clippy::approx_constant) @@ -153,7 +153,7 @@ impl Codegen { // macro, but this is an easy fix for now. quote! { #[allow(clippy::approx_constant)] - pub const #constant_name: #quantity_type::<#type_, { #dimension }> = #quantity_type::<#type_, { #dimension }>(#value as #float_type); + pub const #constant_name: #quantity_type::<#type_, { #dimension }> = #quantity_type::<#type_, { #dimension }>(#magnitude as #float_type); } }) .collect() diff --git a/crates/diman_unit_system/src/codegen/units.rs b/crates/diman_unit_system/src/codegen/units.rs index 4662ac4..7a0b741 100644 --- a/crates/diman_unit_system/src/codegen/units.rs +++ b/crates/diman_unit_system/src/codegen/units.rs @@ -52,7 +52,7 @@ impl Codegen { let quantity_type = &self.defs.quantity_type; let unit_name = &unit.name; let conversion_method_name = format_ident!("in_{}", unit_name); - let magnitude = unit.magnitude; + let magnitude = unit.magnitude.as_f64(); let base_type = &base_type.name; quote! { impl #quantity_type<#storage_type, {#dimension}> { @@ -72,24 +72,22 @@ impl Codegen { ) -> TokenStream { let dimension_type = &self.defs.dimension_type; let quantity_type = &self.defs.quantity_type; - let Unit { - name: unit_name, - magnitude, - .. - } = unit; + let unit_name = &unit.name; + let magnitude = unit.magnitude; let name = &float_type.name; let span = dimension_type.span(); // Without const_fn_floating_point_arithmetic (https://github.com/rust-lang/rust/issues/57241) // we cannot make unit constructors a const fn in general (since it requires the unstable // const_fn_floating_point_arithmetic feature). The following allows the constructor with 1.0 // conversion factor to be const. - let const_fn = *magnitude == 1.0; + let const_fn = magnitude.is_one(); + let magnitude = magnitude.as_f64(); let fn_def = if const_fn { quote! { const fn } } else { quote! { fn } }; - let value = if const_fn { + let magnitude = if const_fn { quote! { val } } else { quote! { val * #magnitude as #name } @@ -97,7 +95,7 @@ impl Codegen { quote_spanned! {span => impl #quantity_type<#name, {#quantity_dimension}> { pub #fn_def #unit_name(val: #name) -> #quantity_type<#name, {#quantity_dimension}> { - #quantity_type::<#name, {#quantity_dimension}>(#value) + #quantity_type::<#name, {#quantity_dimension}>(#magnitude) } } } @@ -111,11 +109,8 @@ impl Codegen { ) -> TokenStream { let dimension_type = &self.defs.dimension_type; let quantity_type = &self.defs.quantity_type; - let Unit { - name: unit_name, - magnitude, - .. - } = unit; + let unit_name = &unit.name; + let magnitude = unit.magnitude.as_f64(); let VectorType { name, float_type, diff --git a/crates/diman_unit_system/src/dimension_math.rs b/crates/diman_unit_system/src/dimension_math.rs index 6aed613..3ebe338 100644 --- a/crates/diman_unit_system/src/dimension_math.rs +++ b/crates/diman_unit_system/src/dimension_math.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use diman_lib::dimension_exponent::DimensionExponent; +use diman_lib::{dimension_exponent::DimensionExponent, magnitude::Magnitude}; use proc_macro2::Ident; use crate::{ @@ -16,7 +16,7 @@ pub struct BaseDimensions { #[derive(Clone)] pub struct DimensionsAndMagnitude { pub dimensions: BaseDimensions, - pub magnitude: f64, + pub magnitude: Magnitude, } impl PartialEq for BaseDimensions { @@ -109,7 +109,7 @@ impl MulDiv for BaseDimensions { } impl DimensionsAndMagnitude { - pub fn magnitude(magnitude: f64) -> Self { + pub fn magnitude(magnitude: Magnitude) -> Self { Self { dimensions: BaseDimensions::none(), magnitude, @@ -119,7 +119,7 @@ impl DimensionsAndMagnitude { pub(crate) fn dimensions(dimensions: BaseDimensions) -> Self { Self { dimensions, - magnitude: 1.0, + magnitude: Magnitude::new(1.0), } } } diff --git a/crates/diman_unit_system/src/parse/mod.rs b/crates/diman_unit_system/src/parse/mod.rs index c3a2c37..f59192b 100644 --- a/crates/diman_unit_system/src/parse/mod.rs +++ b/crates/diman_unit_system/src/parse/mod.rs @@ -1,5 +1,6 @@ mod attributes; +use diman_lib::magnitude::Magnitude; use syn::{ parenthesized, parse::{Parse, ParseStream}, @@ -148,14 +149,14 @@ impl Parse for Exponent { } } -impl Parse for crate::types::Factor { +impl Parse for crate::types::Factor { fn parse(input: ParseStream) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(Ident) { Ok(Self::Other(input.parse()?)) } else if lookahead.peek(Lit) { let factor: Number = input.parse()?; - Ok(Self::Concrete(factor.float)) + Ok(Self::Concrete(Magnitude::new(factor.float))) } else { Err(lookahead.error()) } diff --git a/crates/diman_unit_system/src/types/mod.rs b/crates/diman_unit_system/src/types/mod.rs index 45e694b..0683830 100644 --- a/crates/diman_unit_system/src/types/mod.rs +++ b/crates/diman_unit_system/src/types/mod.rs @@ -2,6 +2,7 @@ pub mod base_dimension; pub mod expression; pub mod prefixes; +use diman_lib::magnitude::Magnitude; use proc_macro2::Span; use syn::*; @@ -72,7 +73,7 @@ pub struct Symbol(pub Ident); #[derive(Clone)] pub struct ConstantEntry { pub name: Ident, - pub rhs: Expr, Exponent>, + pub rhs: Expr, Exponent>, pub dimension_annotation: Option, } @@ -83,7 +84,7 @@ pub struct UnitTemplate { pub aliases: Vec, pub prefixes: Vec, pub dimension_annotation: Option, - pub definition: Definition, + pub definition: Definition, } #[derive(Clone)] @@ -91,7 +92,7 @@ pub struct UnitEntry { pub name: Ident, pub symbol: Option, pub dimension_annotation: Option, - pub definition: Definition, + pub definition: Definition, pub autogenerated_from: Option, } @@ -128,8 +129,10 @@ impl UnitTemplate { &self, prefix: Option<&Prefix>, alias: Option<&Alias>, - ) -> Definition { - let factor = prefix.map(|prefix| prefix.factor()).unwrap_or(1.0); + ) -> Definition { + let factor = prefix + .map(|prefix| prefix.factor()) + .unwrap_or(Magnitude::new(1.0)); if alias.is_none() && prefix.is_none() { self.definition.clone() } else { @@ -214,7 +217,7 @@ pub struct Dimension { pub struct Unit { pub name: Ident, pub dimensions: BaseDimensions, - pub magnitude: f64, + pub magnitude: Magnitude, pub symbol: Option, pub is_base_unit: bool, } @@ -222,7 +225,7 @@ pub struct Unit { pub struct Constant { pub name: Ident, pub dimensions: BaseDimensions, - pub magnitude: f64, + pub magnitude: Magnitude, } pub struct Defs { diff --git a/crates/diman_unit_system/src/types/prefixes.rs b/crates/diman_unit_system/src/types/prefixes.rs index 7a37ce1..104cc11 100644 --- a/crates/diman_unit_system/src/types/prefixes.rs +++ b/crates/diman_unit_system/src/types/prefixes.rs @@ -1,3 +1,5 @@ +use diman_lib::magnitude::Magnitude; + macro_rules! make_prefix_enum { ($enum_name: ident, $(($variant_name: ident, $lowercase_name: ident, $name: literal, $short: literal, $factor: literal)),*) => { #[derive(Clone, Copy, PartialEq)] @@ -24,10 +26,10 @@ macro_rules! make_prefix_enum { } } - pub fn factor(self) -> f64 { + pub fn factor(self) -> Magnitude { match self { $( - Self::$variant_name => $factor, + Self::$variant_name => Magnitude::new($factor), )* } } From 78f9af3a67e6c6dcf19c5c74b3dbc1a7cf7dad9a Mon Sep 17 00:00:00 2001 From: tehforsch Date: Mon, 15 Jan 2024 22:41:50 +0100 Subject: [PATCH 02/40] set up magnitude faulty float multiplication --- crates/diman_lib/src/dimension_exponent.rs | 2 +- crates/diman_lib/src/magnitude.rs | 146 +++++++++++++++++++-- 2 files changed, 135 insertions(+), 13 deletions(-) diff --git a/crates/diman_lib/src/dimension_exponent.rs b/crates/diman_lib/src/dimension_exponent.rs index 763d0ce..36c7e6f 100644 --- a/crates/diman_lib/src/dimension_exponent.rs +++ b/crates/diman_lib/src/dimension_exponent.rs @@ -19,7 +19,7 @@ impl DimensionExponent for i64 { } fn float_pow(num: Magnitude, exponent: Self) -> Magnitude { - num.powi(exponent as i32) + num.powi(exponent as i64) } fn from_int(i: i32) -> Self { diff --git a/crates/diman_lib/src/magnitude.rs b/crates/diman_lib/src/magnitude.rs index dd00821..19961c8 100644 --- a/crates/diman_lib/src/magnitude.rs +++ b/crates/diman_lib/src/magnitude.rs @@ -1,27 +1,81 @@ -use std::ops::{Div, Mul}; +use std::{ + marker::ConstParamTy, + ops::{Div, Mul}, +}; -#[derive(Clone, Copy, PartialEq)] -pub struct Magnitude(f64); +#[derive(Clone, Copy, PartialEq, Eq, ConstParamTy, Debug)] +pub struct Magnitude { + mantissa: u64, + exponent: i16, + sign: i8, +} + +// From num-traits +fn integer_decode_f64(f: f64) -> (u64, i16, i8) { + let bits: u64 = f.to_bits(); + let sign: i8 = if bits >> 63 == 0 { 1 } else { -1 }; + let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16; + let mantissa = if exponent == 0 { + (bits & 0xfffffffffffff) << 1 + } else { + (bits & 0xfffffffffffff) | 0x10000000000000 + }; + // Exponent bias + mantissa shift + exponent -= 1023 + 52; + (mantissa, exponent, sign) +} impl Magnitude { pub fn new(val: f64) -> Self { - Self(val) + let (mantissa, exponent, sign) = integer_decode_f64(val); + Self { + mantissa, + exponent, + sign, + } + } + + pub fn as_f64(self) -> f64 { + let sign_f = self.sign as f64; + let mantissa_f = self.mantissa as f64; + let exponent_f = 2.0f64.powf(self.exponent as f64); + + sign_f * mantissa_f * exponent_f } - pub const fn as_f64(self) -> f64 { - self.0 + pub fn as_f32(self) -> f32 { + self.as_f64() as f32 } pub fn is_one(&self) -> bool { - self.0 == 1.0 + self.as_f64() == 1.0 } - pub(crate) fn powi(&self, exponent: i32) -> Magnitude { - Self(self.0.powi(exponent)) + pub fn powi(&self, exponent: i64) -> Magnitude { + Self::new(self.as_f64().powi(exponent as i32)) } pub(crate) fn pow_rational(&self, num: i64, denom: i64) -> Self { - Self(self.0.powf(num as f64 / denom as f64)) + Self::new(self.as_f64().powf(num as f64 / denom as f64)) + } + + pub fn mul(self, other: Magnitude) -> Self { + let m1: u32 = (self.mantissa >> 26) as u32; + let m2: u32 = (other.mantissa >> 26) as u32; + let mantissa = (m1 as u64) * (m2 as u64); + Self { + mantissa, + exponent: (self.exponent + 52) + (other.exponent + 52) - 52, + sign: self.sign * other.sign, + } + } + + pub const fn div(self, other: Magnitude) -> Self { + Self { + mantissa: 0, + exponent: 0, + sign: 0, + } } } @@ -29,7 +83,7 @@ impl Mul for Magnitude { type Output = Self; fn mul(self, rhs: Self) -> Self::Output { - Self(self.0 * rhs.0) + Self::mul(self, rhs) } } @@ -37,6 +91,74 @@ impl Div for Magnitude { type Output = Self; fn div(self, rhs: Self) -> Self::Output { - Self(self.0 / rhs.0) + Self::div(self, rhs) + } +} + +impl Mul for f64 { + type Output = Self; + fn mul(self, rhs: Magnitude) -> Self::Output { + self * rhs.as_f64() + } +} + +impl Div for f64 { + type Output = Self; + fn div(self, rhs: Magnitude) -> Self::Output { + self * rhs.as_f64() + } +} + +impl Mul for f32 { + type Output = Self; + fn mul(self, rhs: Magnitude) -> Self::Output { + self * rhs.as_f32() + } +} + +impl Div for f32 { + type Output = Self; + fn div(self, rhs: Magnitude) -> Self::Output { + self / rhs.as_f32() + } +} + +#[cfg(test)] +mod tests { + use crate::magnitude::Magnitude; + + #[test] + fn magnitude_mul() { + let check_equality = |x: f64, y: f64| { + let product = (Magnitude::new(x) * Magnitude::new(y)).as_f64(); + assert_eq!(product, x * y); + }; + check_equality(1.0, 1.0); + check_equality(1.5, 1.0); + check_equality(1.0, 1.5); + check_equality(2.0, 2.0); + for exp in -100..100 { + let x = 2.0f64.powi(exp); + let y = 2.0f64.powi(-exp); + check_equality(x, x); + check_equality(x, y); + check_equality(y, x); + check_equality(1.1 * x, y); + } + } + + #[test] + fn magnitude_as_f64_round_trip() { + let check_equality = |x: f64| { + assert_eq!(Magnitude::new(x).as_f64(), x); + }; + for x in 0..10000 { + let x = (x as f64) * 0.01; + check_equality(x); + } + for exp in -50..50 { + let x = 2.0f64.powi(exp); + check_equality(x); + } } } From ac482ebf360601034a5ac23f6f1b43a3e1aedf65 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Tue, 16 Jan 2024 11:52:44 +0100 Subject: [PATCH 03/40] generate unit code --- crates/diman_lib/src/magnitude.rs | 59 ++++-- .../src/codegen/dimensions.rs | 55 ------ crates/diman_unit_system/src/codegen/mod.rs | 4 +- .../src/codegen/quantity_type.rs | 92 ++++++++++ .../src/codegen/unit_type.rs | 169 ++++++++++++++++++ crates/diman_unit_system/src/codegen/units.rs | 155 ++++------------ 6 files changed, 343 insertions(+), 191 deletions(-) create mode 100644 crates/diman_unit_system/src/codegen/quantity_type.rs create mode 100644 crates/diman_unit_system/src/codegen/unit_type.rs diff --git a/crates/diman_lib/src/magnitude.rs b/crates/diman_lib/src/magnitude.rs index 19961c8..c16735b 100644 --- a/crates/diman_lib/src/magnitude.rs +++ b/crates/diman_lib/src/magnitude.rs @@ -5,9 +5,9 @@ use std::{ #[derive(Clone, Copy, PartialEq, Eq, ConstParamTy, Debug)] pub struct Magnitude { - mantissa: u64, - exponent: i16, - sign: i8, + pub mantissa: u64, + pub exponent: i16, + pub sign: i8, } // From num-traits @@ -59,7 +59,7 @@ impl Magnitude { Self::new(self.as_f64().powf(num as f64 / denom as f64)) } - pub fn mul(self, other: Magnitude) -> Self { + pub const fn mul(self, other: Magnitude) -> Self { let m1: u32 = (self.mantissa >> 26) as u32; let m2: u32 = (other.mantissa >> 26) as u32; let mantissa = (m1 as u64) * (m2 as u64); @@ -71,10 +71,14 @@ impl Magnitude { } pub const fn div(self, other: Magnitude) -> Self { + let m1: u32 = (self.mantissa >> 26) as u32; + let m2: u32 = (other.mantissa >> 26) as u32; + let mantissa = (m1 as u64) / (m2 as u64); + let mantissa = self.mantissa / other.mantissa; Self { - mantissa: 0, - exponent: 0, - sign: 0, + mantissa, + exponent: (self.exponent + 52) - (other.exponent + 52), + sign: self.sign * other.sign, } } } @@ -105,7 +109,7 @@ impl Mul for f64 { impl Div for f64 { type Output = Self; fn div(self, rhs: Magnitude) -> Self::Output { - self * rhs.as_f64() + self / rhs.as_f64() } } @@ -127,23 +131,42 @@ impl Div for f32 { mod tests { use crate::magnitude::Magnitude; + fn operator_test_cases() -> impl Iterator { + let mut vals = vec![(1.0, 1.0), (1.5, 1.0), (1.0, 1.5), (2.0, 2.0)]; + for exp in -100..100 { + let x = 2.0f64.powi(exp); + let y = 2.0f64.powi(-exp); + vals.push((x, x)); + vals.push((x, x)); + vals.push((x, y)); + vals.push((y, x)); + vals.push((1.1 * x, y)); + } + vals.into_iter() + } + #[test] fn magnitude_mul() { let check_equality = |x: f64, y: f64| { let product = (Magnitude::new(x) * Magnitude::new(y)).as_f64(); assert_eq!(product, x * y); }; - check_equality(1.0, 1.0); - check_equality(1.5, 1.0); - check_equality(1.0, 1.5); - check_equality(2.0, 2.0); - for exp in -100..100 { - let x = 2.0f64.powi(exp); - let y = 2.0f64.powi(-exp); - check_equality(x, x); + for (x, y) in operator_test_cases() { + check_equality(x, y); + } + } + + #[test] + fn magnitude_div() { + let check_equality = |x: f64, y: f64| { + let product = (Magnitude::new(x) / Magnitude::new(y)).as_f64(); + dbg!(Magnitude::new(x)); + dbg!(Magnitude::new(y)); + dbg!(Magnitude::new(x) / Magnitude::new(y)); + assert_eq!(product, x / y); + }; + for (x, y) in operator_test_cases() { check_equality(x, y); - check_equality(y, x); - check_equality(1.1 * x, y); } } diff --git a/crates/diman_unit_system/src/codegen/dimensions.rs b/crates/diman_unit_system/src/codegen/dimensions.rs index c19c472..a339c6c 100644 --- a/crates/diman_unit_system/src/codegen/dimensions.rs +++ b/crates/diman_unit_system/src/codegen/dimensions.rs @@ -5,61 +5,6 @@ use quote::{quote, quote_spanned}; use super::{storage_types::StorageType, Codegen}; impl Codegen { - pub(crate) fn gen_quantity(&self) -> TokenStream { - let dimension_type = &self.defs.dimension_type; - let quantity_type = &self.defs.quantity_type; - let span = quantity_type.span(); - let functions = self.quantity_functions(); - quote_spanned! {span => - #[derive(Clone, Copy, Eq, Default)] - #[repr(transparent)] - pub struct #quantity_type(pub(crate) S); - #functions - } - } - - fn quantity_functions(&self) -> TokenStream { - let dimension_type = &self.defs.dimension_type; - let quantity_type = &self.defs.quantity_type; - quote! { - impl #quantity_type { - /// Get the value of a dimensionless quantity - pub fn value(self) -> S { - self.0 - } - - /// Get a reference to the value of a dimensionless quantity - pub fn value_ref(&self) -> &S { - &self.0 - } - } - - impl #quantity_type { - /// Return the value of a quantity, regardless of whether - /// it is dimensionless or not. Use this carefully, since the - /// result depends on the underlying base units - pub fn value_unchecked(self) -> S { - self.0 - } - - /// Create a new quantity for the dimension with a given value. - /// Use carefully, since the constructed quantity depends on the - /// used base units. - pub const fn new_unchecked(s: S) -> Self { - Self(s) - } - } - - impl core::ops::Deref for #quantity_type { - type Target = S; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - } - } - pub fn get_dimension_expr(&self, dim: &BaseDimensions) -> TokenStream { let dimension_type = &self.defs.dimension_type; let field_updates: TokenStream = dim diff --git a/crates/diman_unit_system/src/codegen/mod.rs b/crates/diman_unit_system/src/codegen/mod.rs index 37ad2c4..3df25b3 100644 --- a/crates/diman_unit_system/src/codegen/mod.rs +++ b/crates/diman_unit_system/src/codegen/mod.rs @@ -8,11 +8,13 @@ mod hdf5; #[cfg(feature = "mpi")] mod mpi; mod num_traits; +mod quantity_type; #[cfg(feature = "rand")] mod rand; #[cfg(feature = "serde")] mod serde; mod storage_types; +mod unit_type; mod units; mod vector_methods; @@ -55,7 +57,7 @@ impl Codegen { self.gen_dimension(), self.gen_quantity(), self.gen_definitions_for_storage_types(), - self.gen_unit_constructors(), + self.gen_units(), self.gen_numeric_trait_impls(), self.gen_debug_trait_impl(), self.gen_float_methods(), diff --git a/crates/diman_unit_system/src/codegen/quantity_type.rs b/crates/diman_unit_system/src/codegen/quantity_type.rs new file mode 100644 index 0000000..089d9a7 --- /dev/null +++ b/crates/diman_unit_system/src/codegen/quantity_type.rs @@ -0,0 +1,92 @@ +use proc_macro2::TokenStream; + +use super::Codegen; + +use quote::{quote, quote_spanned}; + +impl Codegen { + pub(crate) fn gen_quantity(&self) -> TokenStream { + let dimension_type = &self.defs.dimension_type; + let quantity_type = &self.defs.quantity_type; + let span = quantity_type.span(); + let functions = self.quantity_functions(); + quote_spanned! {span => + #[derive(Clone, Copy, Eq, Default)] + #[repr(transparent)] + pub struct #quantity_type(pub(crate) S); + #functions + } + } + + fn quantity_functions(&self) -> TokenStream { + let dimension_type = &self.defs.dimension_type; + let quantity_type = &self.defs.quantity_type; + quote! { + impl #quantity_type { + /// Get the value of a dimensionless quantity + pub fn value(self) -> S { + self.0 + } + + /// Get a reference to the value of a dimensionless quantity + pub fn value_ref(&self) -> &S { + &self.0 + } + } + + impl #quantity_type { + /// Return the value of a quantity, regardless of whether + /// it is dimensionless or not. Use this carefully, since the + /// result depends on the underlying base units + pub fn value_unchecked(self) -> S { + self.0 + } + + /// Return a reference to the value of a quantity, regardless of whether + /// it is dimensionless or not. Use this carefully, since the + /// result depends on the underlying base units + pub fn value_unchecked_ref(&self) -> &S { + &self.0 + } + + /// Create a new quantity for the dimension with a given value. + /// Use carefully, since the constructed quantity depends on the + /// used base units. + pub const fn new_unchecked(s: S) -> Self { + Self(s) + } + } + + impl #quantity_type + where + S: core::ops::Div + core::fmt::Debug, + { + pub fn value_in(self, _: Unit) -> S { + dbg!(R.as_f64(), &self.value_unchecked_ref()); + self.value_unchecked() / R + } + } + + impl #quantity_type { + pub fn round_in(self, unit: Unit) -> f64 { + self.value_in(unit).round() + } + } + + impl #quantity_type { + pub fn round_in(self, unit: Unit) -> f32 { + self.value_in(unit).round() + } + } + + + impl core::ops::Deref for #quantity_type { + type Target = S; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + } + } +} diff --git a/crates/diman_unit_system/src/codegen/unit_type.rs b/crates/diman_unit_system/src/codegen/unit_type.rs new file mode 100644 index 0000000..870cdbf --- /dev/null +++ b/crates/diman_unit_system/src/codegen/unit_type.rs @@ -0,0 +1,169 @@ +use proc_macro2::TokenStream; +use quote::quote; + +use super::Codegen; + +impl Codegen { + pub fn gen_unit_type(&self) -> TokenStream { + let dimension_type = &self.defs.dimension_type; + let trait_impls = self.gen_unit_trait_impls(); + quote! { + pub struct Unit; + #trait_impls + } + } + + fn gen_unit_trait_impls(&self) -> TokenStream { + quote! { + use core::ops::{Mul, Div}; + // Unit * Unit + impl + Mul> for Unit + where + Unit<{ DL.add(DR) }, { FL.mul(FR) }>:, + { + type Output = Unit<{ DL.add(DR) }, { FL.mul(FR) }>; + fn mul(self, _: Unit) -> Self::Output { + Unit + } + } + + // Unit / Unit + impl + Div> for Unit + where + Unit<{ DL.sub(DR) }, { FL.div(FR) }>:, + { + type Output = Unit<{ DL.sub(DR) }, { FL.div(FR) }>; + fn div(self, _: Unit) -> Self::Output { + Unit + } + } + + // Unit * Quantity + impl Mul> + for Unit + where + S: Mul, + Quantity<(), { DL.add(DR) }>:, + { + type Output = Quantity; + fn mul(self, x: Quantity) -> Self::Output { + Quantity(x.value_unchecked() * FL) + } + } + + // Quantity * Unit + impl Mul> + for Quantity + where + S: Mul, + Quantity<(), { DL.add(DR) }>:, + { + type Output = Quantity; + fn mul(self, _: Unit) -> Self::Output { + Quantity(self.value_unchecked() * FR) + } + } + + // Unit / Quantity + impl Div> + for Unit + where + S: Div, + Quantity<(), { DL.sub(DR) }>:, + { + type Output = Quantity; + fn div(self, x: Quantity) -> Self::Output { + Quantity(x.value_unchecked() / FL) + } + } + + // Quantity / Unit + impl Div> + for Quantity + where + S: Div, + Quantity<(), { DL.sub(DR) }>:, + { + type Output = Quantity; + fn div(self, _: Unit) -> Self::Output { + Quantity(self.value_unchecked() / FR) + } + } + + // f64 * Unit + impl Mul> for f64 { + type Output = Quantity; + fn mul(self, _: Unit) -> Self::Output { + Quantity(self * F) + } + } + + // f64 / Unit + impl Div> for f64 { + type Output = Quantity; + fn div(self, _: Unit) -> Self::Output { + Quantity(self / F) + } + } + + // Unit * f64 + impl Mul for Unit { + type Output = Quantity; + fn mul(self, f: f64) -> Self::Output { + Quantity(F.as_f64() * f) + } + } + + // Unit / f64 + impl Div for Unit { + type Output = Quantity; + fn div(self, f: f64) -> Self::Output { + Quantity(F.as_f64() / f) + } + } + + // f32 * Unit + impl Mul> for f32 { + type Output = Quantity; + fn mul(self, _: Unit) -> Self::Output { + Quantity(self * F) + } + } + + // f32 / Unit + impl Div> for f32 { + type Output = Quantity; + fn div(self, _: Unit) -> Self::Output { + Quantity(self / F) + } + } + + // Unit * f32 + impl Mul for Unit { + type Output = Quantity; + fn mul(self, f: f32) -> Self::Output { + Quantity(F.as_f32() * f) + } + } + + // Unit / f32 + impl Div for Unit { + type Output = Quantity; + fn div(self, f: f32) -> Self::Output { + Quantity(F.as_f32() / f) + } + } + + impl Unit { + pub fn new(self, val: S) -> Quantity + where + S: Mul, + { + Quantity(val * F.as_f64()) + } + } + } + } +} diff --git a/crates/diman_unit_system/src/codegen/units.rs b/crates/diman_unit_system/src/codegen/units.rs index 7a0b741..7b0fec8 100644 --- a/crates/diman_unit_system/src/codegen/units.rs +++ b/crates/diman_unit_system/src/codegen/units.rs @@ -1,139 +1,60 @@ use proc_macro2::TokenStream; -use quote::{format_ident, quote, quote_spanned}; -use syn::Type; +use quote::quote; -use super::{ - storage_types::{FloatType, VectorType}, - Codegen, -}; +use super::Codegen; use crate::types::Unit; +use diman_lib::magnitude::Magnitude; impl Codegen { - pub fn gen_unit_constructors(&self) -> TokenStream { - self.defs + pub fn gen_units(&self) -> TokenStream { + let def_unit_type = self.gen_unit_type(); + let units: TokenStream = self + .defs .units .iter() .map(|unit| { - let dimension = self.get_dimension_expr(&unit.dimensions); - let vector_impls: TokenStream = self - .vector_types() - .iter() - .map(|vector_type| self.vector_unit_constructor(vector_type, unit, &dimension)) - .collect(); - let float_impls: TokenStream = self - .float_types() - .iter() - .map(|float_type| self.float_unit_constructor(float_type, unit, &dimension)) - .collect(); - let conversion_impls: TokenStream = self - .storage_types() - .map(|storage_type| { - let type_ = storage_type.name(); - let float_type = storage_type.base_storage(); - self.conversion_impls(type_, float_type, unit, &dimension) - }) - .collect(); + let unit = self.unit_const(unit); quote! { - #conversion_impls - #float_impls - #vector_impls + #unit } }) - .collect() - } - - fn conversion_impls( - &self, - storage_type: &Type, - base_type: &FloatType, - unit: &Unit, - dimension: &TokenStream, - ) -> TokenStream { - let quantity_type = &self.defs.quantity_type; - let unit_name = &unit.name; - let conversion_method_name = format_ident!("in_{}", unit_name); - let magnitude = unit.magnitude.as_f64(); - let base_type = &base_type.name; + .collect(); + let path_prefix = self.caller_type.path_prefix(); quote! { - impl #quantity_type<#storage_type, {#dimension}> { - pub fn #conversion_method_name(self) -> #storage_type { - let magnitude: #base_type = #magnitude as #base_type; - self.0 / magnitude - } + pub use #path_prefix::magnitude::Magnitude; + mod unit_type { + use super::Dimension; + use super::Magnitude; + use super::Quantity; + #def_unit_type + } + pub use unit_type::Unit; + #[allow(non_upper_case_globals)] + pub mod units { + use super::Magnitude; + use super::Unit; + use super::Dimension; + #units } } } - fn float_unit_constructor( - &self, - float_type: &FloatType, - unit: &Unit, - quantity_dimension: &TokenStream, - ) -> TokenStream { - let dimension_type = &self.defs.dimension_type; - let quantity_type = &self.defs.quantity_type; - let unit_name = &unit.name; - let magnitude = unit.magnitude; - let name = &float_type.name; - let span = dimension_type.span(); - // Without const_fn_floating_point_arithmetic (https://github.com/rust-lang/rust/issues/57241) - // we cannot make unit constructors a const fn in general (since it requires the unstable - // const_fn_floating_point_arithmetic feature). The following allows the constructor with 1.0 - // conversion factor to be const. - let const_fn = magnitude.is_one(); - let magnitude = magnitude.as_f64(); - let fn_def = if const_fn { - quote! { const fn } - } else { - quote! { fn } - }; - let magnitude = if const_fn { - quote! { val } - } else { - quote! { val * #magnitude as #name } - }; - quote_spanned! {span => - impl #quantity_type<#name, {#quantity_dimension}> { - pub #fn_def #unit_name(val: #name) -> #quantity_type<#name, {#quantity_dimension}> { - #quantity_type::<#name, {#quantity_dimension}>(#magnitude) - } - } + fn unit_const(&self, unit: &Unit) -> TokenStream { + let dimension = self.get_dimension_expr(&unit.dimensions); + let name = &unit.name; + let magnitude = self.get_magnitude_expr(unit.magnitude); + quote! { + pub const #name: Unit<{ #dimension }, { #magnitude }> = Unit; } } - fn vector_unit_constructor( - &self, - vector_type: &VectorType, - unit: &Unit, - quantity_dimension: &TokenStream, - ) -> TokenStream { - let dimension_type = &self.defs.dimension_type; - let quantity_type = &self.defs.quantity_type; - let unit_name = &unit.name; - let magnitude = unit.magnitude.as_f64(); - let VectorType { - name, - float_type, - num_dims, - .. - } = &vector_type; - let float_type = &float_type.name; - let fn_args = match num_dims { - 2 => quote! { x: #float_type, y: #float_type }, - 3 => quote! { x: #float_type, y: #float_type, z: #float_type }, - _ => unreachable!(), - }; - let call_args = match num_dims { - 2 => quote! { x, y }, - 3 => quote! { x, y, z }, - _ => unreachable!(), - }; - let span = dimension_type.span(); - quote_spanned! {span => - impl #quantity_type<#name, {#quantity_dimension}> { - pub fn #unit_name(#fn_args) -> #quantity_type<#name, {#quantity_dimension}> { - #quantity_type::<#name, {#quantity_dimension}>(#name::new(#call_args) * (#magnitude as #float_type)) - } + fn get_magnitude_expr(&self, magnitude: Magnitude) -> TokenStream { + let (mantissa, exponent, sign) = (magnitude.mantissa, magnitude.exponent, magnitude.sign); + quote! { + Magnitude { + mantissa: #mantissa, + exponent: #exponent, + sign: #sign, } } } From 71ff80e671361278199f02c682895853b849982b Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 21 Jan 2024 17:34:41 +0100 Subject: [PATCH 04/40] use mantissa/exponent rep --- crates/diman_lib/src/dimension_exponent.rs | 2 +- crates/diman_lib/src/magnitude.rs | 136 +++--------------- .../src/codegen/debug_trait.rs | 2 +- .../src/codegen/dimensions.rs | 2 +- .../src/codegen/quantity_type.rs | 18 +-- .../src/codegen/unit_type.rs | 66 ++++----- .../diman_unit_system/src/dimension_math.rs | 2 +- crates/diman_unit_system/src/parse/mod.rs | 2 +- crates/diman_unit_system/src/types/mod.rs | 2 +- .../diman_unit_system/src/types/prefixes.rs | 2 +- 10 files changed, 66 insertions(+), 168 deletions(-) diff --git a/crates/diman_lib/src/dimension_exponent.rs b/crates/diman_lib/src/dimension_exponent.rs index 36c7e6f..77a949f 100644 --- a/crates/diman_lib/src/dimension_exponent.rs +++ b/crates/diman_lib/src/dimension_exponent.rs @@ -19,7 +19,7 @@ impl DimensionExponent for i64 { } fn float_pow(num: Magnitude, exponent: Self) -> Magnitude { - num.powi(exponent as i64) + Magnitude::from_f64(num.into_f64().powi(exponent as i32)) } fn from_int(i: i32) -> Self { diff --git a/crates/diman_lib/src/magnitude.rs b/crates/diman_lib/src/magnitude.rs index c16735b..fee4c35 100644 --- a/crates/diman_lib/src/magnitude.rs +++ b/crates/diman_lib/src/magnitude.rs @@ -3,7 +3,15 @@ use std::{ ops::{Div, Mul}, }; +pub const MAX_NUM_FACTORS: usize = 10; + #[derive(Clone, Copy, PartialEq, Eq, ConstParamTy, Debug)] +struct Factor { + f: u64, + exp: i16, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, ConstParamTy)] pub struct Magnitude { pub mantissa: u64, pub exponent: i16, @@ -26,8 +34,8 @@ fn integer_decode_f64(f: f64) -> (u64, i16, i8) { } impl Magnitude { - pub fn new(val: f64) -> Self { - let (mantissa, exponent, sign) = integer_decode_f64(val); + pub fn from_f64(f: f64) -> Self { + let (mantissa, exponent, sign) = integer_decode_f64(f); Self { mantissa, exponent, @@ -35,95 +43,36 @@ impl Magnitude { } } - pub fn as_f64(self) -> f64 { - let sign_f = self.sign as f64; - let mantissa_f = self.mantissa as f64; - let exponent_f = 2.0f64.powf(self.exponent as f64); - - sign_f * mantissa_f * exponent_f + pub fn into_f64(self) -> f64 { + self.sign as f64 * self.mantissa as f64 * 2.0f64.powi(self.exponent as i32) } - pub fn as_f32(self) -> f32 { - self.as_f64() as f32 - } - - pub fn is_one(&self) -> bool { - self.as_f64() == 1.0 + pub fn into_f32(self) -> f32 { + self.into_f64() as f32 } - pub fn powi(&self, exponent: i64) -> Magnitude { - Self::new(self.as_f64().powi(exponent as i32)) + pub fn pow_rational(&self, num: i64, denom: i64) -> Magnitude { + Self::from_f64(self.into_f64().powf(num as f64 / denom as f64)) } - pub(crate) fn pow_rational(&self, num: i64, denom: i64) -> Self { - Self::new(self.as_f64().powf(num as f64 / denom as f64)) - } - - pub const fn mul(self, other: Magnitude) -> Self { - let m1: u32 = (self.mantissa >> 26) as u32; - let m2: u32 = (other.mantissa >> 26) as u32; - let mantissa = (m1 as u64) * (m2 as u64); - Self { - mantissa, - exponent: (self.exponent + 52) + (other.exponent + 52) - 52, - sign: self.sign * other.sign, - } - } - - pub const fn div(self, other: Magnitude) -> Self { - let m1: u32 = (self.mantissa >> 26) as u32; - let m2: u32 = (other.mantissa >> 26) as u32; - let mantissa = (m1 as u64) / (m2 as u64); - let mantissa = self.mantissa / other.mantissa; - Self { - mantissa, - exponent: (self.exponent + 52) - (other.exponent + 52), - sign: self.sign * other.sign, - } - } -} - -impl Mul for Magnitude { - type Output = Self; - - fn mul(self, rhs: Self) -> Self::Output { - Self::mul(self, rhs) + pub fn is_one(&self) -> bool { + self.into_f64() == 1.0 } } -impl Div for Magnitude { +impl Mul for Magnitude { type Output = Self; - fn div(self, rhs: Self) -> Self::Output { - Self::div(self, rhs) - } -} - -impl Mul for f64 { - type Output = Self; fn mul(self, rhs: Magnitude) -> Self::Output { - self * rhs.as_f64() + Self::from_f64(self.into_f64() * rhs.into_f64()) } } -impl Div for f64 { +impl Div for Magnitude { type Output = Self; - fn div(self, rhs: Magnitude) -> Self::Output { - self / rhs.as_f64() - } -} -impl Mul for f32 { - type Output = Self; - fn mul(self, rhs: Magnitude) -> Self::Output { - self * rhs.as_f32() - } -} - -impl Div for f32 { - type Output = Self; fn div(self, rhs: Magnitude) -> Self::Output { - self / rhs.as_f32() + Self::from_f64(self.into_f64() / rhs.into_f64()) } } @@ -131,49 +80,10 @@ impl Div for f32 { mod tests { use crate::magnitude::Magnitude; - fn operator_test_cases() -> impl Iterator { - let mut vals = vec![(1.0, 1.0), (1.5, 1.0), (1.0, 1.5), (2.0, 2.0)]; - for exp in -100..100 { - let x = 2.0f64.powi(exp); - let y = 2.0f64.powi(-exp); - vals.push((x, x)); - vals.push((x, x)); - vals.push((x, y)); - vals.push((y, x)); - vals.push((1.1 * x, y)); - } - vals.into_iter() - } - - #[test] - fn magnitude_mul() { - let check_equality = |x: f64, y: f64| { - let product = (Magnitude::new(x) * Magnitude::new(y)).as_f64(); - assert_eq!(product, x * y); - }; - for (x, y) in operator_test_cases() { - check_equality(x, y); - } - } - - #[test] - fn magnitude_div() { - let check_equality = |x: f64, y: f64| { - let product = (Magnitude::new(x) / Magnitude::new(y)).as_f64(); - dbg!(Magnitude::new(x)); - dbg!(Magnitude::new(y)); - dbg!(Magnitude::new(x) / Magnitude::new(y)); - assert_eq!(product, x / y); - }; - for (x, y) in operator_test_cases() { - check_equality(x, y); - } - } - #[test] fn magnitude_as_f64_round_trip() { let check_equality = |x: f64| { - assert_eq!(Magnitude::new(x).as_f64(), x); + assert_eq!(Magnitude::from_f64(x).into_f64(), x); }; for x in 0..10000 { let x = (x as f64) * 0.01; diff --git a/crates/diman_unit_system/src/codegen/debug_trait.rs b/crates/diman_unit_system/src/codegen/debug_trait.rs index 086aca7..97dafeb 100644 --- a/crates/diman_unit_system/src/codegen/debug_trait.rs +++ b/crates/diman_unit_system/src/codegen/debug_trait.rs @@ -24,7 +24,7 @@ impl Codegen { let units: TokenStream = units .filter_map(|unit| { let dim = self.get_dimension_expr(&unit.dimensions); - let magnitude = unit.magnitude.as_f64(); + let magnitude = unit.magnitude.into_f64(); let symbol = &unit.symbol.as_ref()?.0.to_string(); Some(quote! { #runtime_unit::new( diff --git a/crates/diman_unit_system/src/codegen/dimensions.rs b/crates/diman_unit_system/src/codegen/dimensions.rs index a339c6c..63dfa9b 100644 --- a/crates/diman_unit_system/src/codegen/dimensions.rs +++ b/crates/diman_unit_system/src/codegen/dimensions.rs @@ -87,7 +87,7 @@ impl Codegen { let dimension = self.get_dimension_expr(&constant.dimensions); let quantity_type = &self.defs.quantity_type; let constant_name = &constant.name; - let magnitude = constant.magnitude.as_f64(); + let magnitude = constant.magnitude.into_f64(); let float_type = &type_.base_storage().name; let type_ = type_.name(); // TODO(minor): The allow(clippy::approx_constant) diff --git a/crates/diman_unit_system/src/codegen/quantity_type.rs b/crates/diman_unit_system/src/codegen/quantity_type.rs index 089d9a7..4ee24d4 100644 --- a/crates/diman_unit_system/src/codegen/quantity_type.rs +++ b/crates/diman_unit_system/src/codegen/quantity_type.rs @@ -23,12 +23,12 @@ impl Codegen { let quantity_type = &self.defs.quantity_type; quote! { impl #quantity_type { - /// Get the value of a dimensionless quantity + /// Return the stored value of a dimensionless quantity. pub fn value(self) -> S { self.0 } - /// Get a reference to the value of a dimensionless quantity + /// Get a reference to the stored value of a dimensionless quantity. pub fn value_ref(&self) -> &S { &self.0 } @@ -62,24 +62,10 @@ impl Codegen { S: core::ops::Div + core::fmt::Debug, { pub fn value_in(self, _: Unit) -> S { - dbg!(R.as_f64(), &self.value_unchecked_ref()); self.value_unchecked() / R } } - impl #quantity_type { - pub fn round_in(self, unit: Unit) -> f64 { - self.value_in(unit).round() - } - } - - impl #quantity_type { - pub fn round_in(self, unit: Unit) -> f32 { - self.value_in(unit).round() - } - } - - impl core::ops::Deref for #quantity_type { type Target = S; diff --git a/crates/diman_unit_system/src/codegen/unit_type.rs b/crates/diman_unit_system/src/codegen/unit_type.rs index 870cdbf..86db2db 100644 --- a/crates/diman_unit_system/src/codegen/unit_type.rs +++ b/crates/diman_unit_system/src/codegen/unit_type.rs @@ -16,29 +16,31 @@ impl Codegen { fn gen_unit_trait_impls(&self) -> TokenStream { quote! { use core::ops::{Mul, Div}; - // Unit * Unit - impl - Mul> for Unit - where - Unit<{ DL.add(DR) }, { FL.mul(FR) }>:, - { - type Output = Unit<{ DL.add(DR) }, { FL.mul(FR) }>; - fn mul(self, _: Unit) -> Self::Output { - Unit - } - } - - // Unit / Unit - impl - Div> for Unit - where - Unit<{ DL.sub(DR) }, { FL.div(FR) }>:, - { - type Output = Unit<{ DL.sub(DR) }, { FL.div(FR) }>; - fn div(self, _: Unit) -> Self::Output { - Unit - } - } + // The following would be possible if + // Unit::mul / Unit::div could be made const. + // // Unit * Unit + // impl + // Mul> for Unit + // where + // Unit<{ DL.add(DR) }, { FL.mul(FR) }>:, + // { + // type Output = Unit<{ DL.add(DR) }, { FL.mul(FR) }>; + // fn mul(self, _: Unit) -> Self::Output { + // Unit + // } + // } + + // // Unit / Unit + // impl + // Div> for Unit + // where + // Unit<{ DL.sub(DR) }, { FL.div(FR) }>:, + // { + // type Output = Unit<{ DL.sub(DR) }, { FL.div(FR) }>; + // fn div(self, _: Unit) -> Self::Output { + // Unit + // } + // } // Unit * Quantity impl Mul> @@ -96,7 +98,7 @@ impl Codegen { impl Mul> for f64 { type Output = Quantity; fn mul(self, _: Unit) -> Self::Output { - Quantity(self * F) + Quantity(self * F.into_f64()) } } @@ -104,7 +106,7 @@ impl Codegen { impl Div> for f64 { type Output = Quantity; fn div(self, _: Unit) -> Self::Output { - Quantity(self / F) + Quantity(self / F.into_f64()) } } @@ -112,7 +114,7 @@ impl Codegen { impl Mul for Unit { type Output = Quantity; fn mul(self, f: f64) -> Self::Output { - Quantity(F.as_f64() * f) + Quantity(F.into_f64() * f) } } @@ -120,7 +122,7 @@ impl Codegen { impl Div for Unit { type Output = Quantity; fn div(self, f: f64) -> Self::Output { - Quantity(F.as_f64() / f) + Quantity(F.into_f64()) } } @@ -128,7 +130,7 @@ impl Codegen { impl Mul> for f32 { type Output = Quantity; fn mul(self, _: Unit) -> Self::Output { - Quantity(self * F) + Quantity(self * F.into_f32()) } } @@ -136,7 +138,7 @@ impl Codegen { impl Div> for f32 { type Output = Quantity; fn div(self, _: Unit) -> Self::Output { - Quantity(self / F) + Quantity(self / F.into_f32()) } } @@ -144,7 +146,7 @@ impl Codegen { impl Mul for Unit { type Output = Quantity; fn mul(self, f: f32) -> Self::Output { - Quantity(F.as_f32() * f) + Quantity(F.into_f32() * f) } } @@ -152,7 +154,7 @@ impl Codegen { impl Div for Unit { type Output = Quantity; fn div(self, f: f32) -> Self::Output { - Quantity(F.as_f32() / f) + Quantity(F.into_f32() / f) } } @@ -161,7 +163,7 @@ impl Codegen { where S: Mul, { - Quantity(val * F.as_f64()) + Quantity(val * F.into_f64()) } } } diff --git a/crates/diman_unit_system/src/dimension_math.rs b/crates/diman_unit_system/src/dimension_math.rs index 3ebe338..e39482f 100644 --- a/crates/diman_unit_system/src/dimension_math.rs +++ b/crates/diman_unit_system/src/dimension_math.rs @@ -119,7 +119,7 @@ impl DimensionsAndMagnitude { pub(crate) fn dimensions(dimensions: BaseDimensions) -> Self { Self { dimensions, - magnitude: Magnitude::new(1.0), + magnitude: Magnitude::from_f64(1.0), } } } diff --git a/crates/diman_unit_system/src/parse/mod.rs b/crates/diman_unit_system/src/parse/mod.rs index f59192b..4407f10 100644 --- a/crates/diman_unit_system/src/parse/mod.rs +++ b/crates/diman_unit_system/src/parse/mod.rs @@ -156,7 +156,7 @@ impl Parse for crate::types::Factor { Ok(Self::Other(input.parse()?)) } else if lookahead.peek(Lit) { let factor: Number = input.parse()?; - Ok(Self::Concrete(Magnitude::new(factor.float))) + Ok(Self::Concrete(Magnitude::from_f64(factor.float))) } else { Err(lookahead.error()) } diff --git a/crates/diman_unit_system/src/types/mod.rs b/crates/diman_unit_system/src/types/mod.rs index 0683830..7c68a27 100644 --- a/crates/diman_unit_system/src/types/mod.rs +++ b/crates/diman_unit_system/src/types/mod.rs @@ -132,7 +132,7 @@ impl UnitTemplate { ) -> Definition { let factor = prefix .map(|prefix| prefix.factor()) - .unwrap_or(Magnitude::new(1.0)); + .unwrap_or(Magnitude::from_f64(1.0)); if alias.is_none() && prefix.is_none() { self.definition.clone() } else { diff --git a/crates/diman_unit_system/src/types/prefixes.rs b/crates/diman_unit_system/src/types/prefixes.rs index 104cc11..785b8d3 100644 --- a/crates/diman_unit_system/src/types/prefixes.rs +++ b/crates/diman_unit_system/src/types/prefixes.rs @@ -29,7 +29,7 @@ macro_rules! make_prefix_enum { pub fn factor(self) -> Magnitude { match self { $( - Self::$variant_name => Magnitude::new($factor), + Self::$variant_name => Magnitude::from_f64($factor), )* } } From 38c25fb8dfc033b96f1686f7f38bf206564d2b4f Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 21 Jan 2024 17:45:09 +0100 Subject: [PATCH 05/40] Impl Mul / Div for Magnitude/f64 --- crates/diman_lib/src/magnitude.rs | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/crates/diman_lib/src/magnitude.rs b/crates/diman_lib/src/magnitude.rs index fee4c35..0ade71c 100644 --- a/crates/diman_lib/src/magnitude.rs +++ b/crates/diman_lib/src/magnitude.rs @@ -76,6 +76,38 @@ impl Div for Magnitude { } } +impl Mul for Magnitude { + type Output = Self; + + fn mul(self, rhs: f64) -> Self::Output { + Self::from_f64(self.into_f64() * rhs) + } +} + +impl Div for Magnitude { + type Output = Self; + + fn div(self, rhs: f64) -> Self::Output { + Self::from_f64(self.into_f64() / rhs) + } +} + +impl Mul for f64 { + type Output = Self; + + fn mul(self, rhs: Magnitude) -> Self::Output { + self * rhs.into_f64() + } +} + +impl Div for f64 { + type Output = Self; + + fn div(self, rhs: Magnitude) -> Self::Output { + self / rhs.into_f64() + } +} + #[cfg(test)] mod tests { use crate::magnitude::Magnitude; From 861f6aba3b5aace4afd98f38200540657acb265f Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 21 Jan 2024 17:48:33 +0100 Subject: [PATCH 06/40] move dimensions inside of module --- .../diman_unit_system/src/codegen/dimensions.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/diman_unit_system/src/codegen/dimensions.rs b/crates/diman_unit_system/src/codegen/dimensions.rs index 63dfa9b..530e8ba 100644 --- a/crates/diman_unit_system/src/codegen/dimensions.rs +++ b/crates/diman_unit_system/src/codegen/dimensions.rs @@ -26,7 +26,8 @@ impl Codegen { } pub(crate) fn gen_definitions_for_storage_types(&self) -> TokenStream { - self.storage_types() + let defs: TokenStream = self + .storage_types() .map(|type_| { self.definitions_for_storage_type( &*type_, @@ -34,7 +35,17 @@ impl Codegen { type_.generate_constants(), ) }) - .collect() + .collect(); + let dimension_type = &self.defs.dimension_type; + let quantity_type = &self.defs.quantity_type; + quote! { + pub mod dimensions { + use super::#dimension_type; + use super::#quantity_type; + use super::Exponent; + #defs + } + } } fn definitions_for_storage_type( From 7669d09f532993a5f0623a970eb7087f1b6c15f3 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 21 Jan 2024 17:56:30 +0100 Subject: [PATCH 07/40] generate only generic dimensions --- .../src/codegen/dimensions.rs | 49 ++++--------------- crates/diman_unit_system/src/codegen/mod.rs | 2 +- 2 files changed, 11 insertions(+), 40 deletions(-) diff --git a/crates/diman_unit_system/src/codegen/dimensions.rs b/crates/diman_unit_system/src/codegen/dimensions.rs index 530e8ba..f992646 100644 --- a/crates/diman_unit_system/src/codegen/dimensions.rs +++ b/crates/diman_unit_system/src/codegen/dimensions.rs @@ -25,19 +25,10 @@ impl Codegen { } } - pub(crate) fn gen_definitions_for_storage_types(&self) -> TokenStream { - let defs: TokenStream = self - .storage_types() - .map(|type_| { - self.definitions_for_storage_type( - &*type_, - type_.module_name(), - type_.generate_constants(), - ) - }) - .collect(); + pub(crate) fn gen_dimensions(&self) -> TokenStream { let dimension_type = &self.defs.dimension_type; let quantity_type = &self.defs.quantity_type; + let defs = self.gen_dimension_definitions(); quote! { pub mod dimensions { use super::#dimension_type; @@ -48,46 +39,26 @@ impl Codegen { } } - fn definitions_for_storage_type( - &self, - type_: &dyn StorageType, - module_name: &TokenStream, - gen_constants: bool, - ) -> TokenStream { + fn gen_dimension_definitions(&self) -> TokenStream { let dimension_type = &self.defs.dimension_type; let quantity_type = &self.defs.quantity_type; - let quantities = self.quantity_definitions_for_storage_type(type_); - let constants = if gen_constants { - self.constant_definitions_for_storage_type(type_) - } else { - quote! {} - }; - quote! { - pub mod #module_name { - use super::#dimension_type; - use super::#quantity_type; - use super::Exponent; - #quantities - #constants - } - } - } - - fn quantity_definitions_for_storage_type(&self, type_: &dyn StorageType) -> TokenStream { - self.defs + let dimensions: TokenStream = self + .defs .dimensions .iter() .map(|quantity| { let dimension = self.get_dimension_expr(&quantity.dimensions); let quantity_type = &self.defs.quantity_type; let quantity_name = &quantity.name; - let type_ = type_.name(); let span = self.defs.dimension_type.span(); quote_spanned! {span => - pub type #quantity_name = #quantity_type::<#type_, { #dimension }>; + pub type #quantity_name = #quantity_type::; } }) - .collect() + .collect(); + quote! { + #dimensions + } } fn constant_definitions_for_storage_type(&self, type_: &dyn StorageType) -> TokenStream { diff --git a/crates/diman_unit_system/src/codegen/mod.rs b/crates/diman_unit_system/src/codegen/mod.rs index 3df25b3..0cd020d 100644 --- a/crates/diman_unit_system/src/codegen/mod.rs +++ b/crates/diman_unit_system/src/codegen/mod.rs @@ -56,7 +56,7 @@ impl Codegen { join([ self.gen_dimension(), self.gen_quantity(), - self.gen_definitions_for_storage_types(), + self.gen_dimensions(), self.gen_units(), self.gen_numeric_trait_impls(), self.gen_debug_trait_impl(), From 59836215ccc50c85e8ed11774dbe0bbe583d771e Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 21 Jan 2024 18:01:20 +0100 Subject: [PATCH 08/40] generate constants --- .../src/codegen/dimensions.rs | 29 +-------------- crates/diman_unit_system/src/codegen/mod.rs | 4 +-- .../{units.rs => units_and_constants.rs} | 36 ++++++++++++++++--- 3 files changed, 34 insertions(+), 35 deletions(-) rename crates/diman_unit_system/src/codegen/{units.rs => units_and_constants.rs} (58%) diff --git a/crates/diman_unit_system/src/codegen/dimensions.rs b/crates/diman_unit_system/src/codegen/dimensions.rs index f992646..a03f881 100644 --- a/crates/diman_unit_system/src/codegen/dimensions.rs +++ b/crates/diman_unit_system/src/codegen/dimensions.rs @@ -2,7 +2,7 @@ use crate::dimension_math::BaseDimensions; use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; -use super::{storage_types::StorageType, Codegen}; +use super::Codegen; impl Codegen { pub fn get_dimension_expr(&self, dim: &BaseDimensions) -> TokenStream { @@ -40,8 +40,6 @@ impl Codegen { } fn gen_dimension_definitions(&self) -> TokenStream { - let dimension_type = &self.defs.dimension_type; - let quantity_type = &self.defs.quantity_type; let dimensions: TokenStream = self .defs .dimensions @@ -60,29 +58,4 @@ impl Codegen { #dimensions } } - - fn constant_definitions_for_storage_type(&self, type_: &dyn StorageType) -> TokenStream { - self - .defs.constants - .iter() - .map(|constant| { - let dimension = self.get_dimension_expr(&constant.dimensions); - let quantity_type = &self.defs.quantity_type; - let constant_name = &constant.name; - let magnitude = constant.magnitude.into_f64(); - let float_type = &type_.base_storage().name; - let type_ = type_.name(); - // TODO(minor): The allow(clippy::approx_constant) - // exists to allow definitions of, for example, PI in - // unit_system calls. A better solution would - // probably be to define PI (and possibly some other - // mathematical constants) for use in the unit_system - // macro, but this is an easy fix for now. - quote! { - #[allow(clippy::approx_constant)] - pub const #constant_name: #quantity_type::<#type_, { #dimension }> = #quantity_type::<#type_, { #dimension }>(#magnitude as #float_type); - } - }) - .collect() - } } diff --git a/crates/diman_unit_system/src/codegen/mod.rs b/crates/diman_unit_system/src/codegen/mod.rs index 0cd020d..fe47669 100644 --- a/crates/diman_unit_system/src/codegen/mod.rs +++ b/crates/diman_unit_system/src/codegen/mod.rs @@ -15,7 +15,7 @@ mod rand; mod serde; mod storage_types; mod unit_type; -mod units; +mod units_and_constants; mod vector_methods; use proc_macro2::TokenStream; @@ -57,7 +57,7 @@ impl Codegen { self.gen_dimension(), self.gen_quantity(), self.gen_dimensions(), - self.gen_units(), + self.gen_units_and_constants(), self.gen_numeric_trait_impls(), self.gen_debug_trait_impl(), self.gen_float_methods(), diff --git a/crates/diman_unit_system/src/codegen/units.rs b/crates/diman_unit_system/src/codegen/units_and_constants.rs similarity index 58% rename from crates/diman_unit_system/src/codegen/units.rs rename to crates/diman_unit_system/src/codegen/units_and_constants.rs index 7b0fec8..f89e91a 100644 --- a/crates/diman_unit_system/src/codegen/units.rs +++ b/crates/diman_unit_system/src/codegen/units_and_constants.rs @@ -2,18 +2,29 @@ use proc_macro2::TokenStream; use quote::quote; use super::Codegen; -use crate::types::Unit; +use crate::types::{Constant, Unit}; use diman_lib::magnitude::Magnitude; impl Codegen { - pub fn gen_units(&self) -> TokenStream { + pub fn gen_units_and_constants(&self) -> TokenStream { let def_unit_type = self.gen_unit_type(); let units: TokenStream = self .defs .units .iter() .map(|unit| { - let unit = self.unit_const(unit); + let unit = self.gen_unit_def(unit); + quote! { + #unit + } + }) + .collect(); + let constants: TokenStream = self + .defs + .constants + .iter() + .map(|unit| { + let unit = self.gen_constant_def(unit); quote! { #unit } @@ -36,10 +47,16 @@ impl Codegen { use super::Dimension; #units } + pub mod constants { + use super::Magnitude; + use super::Unit; + use super::Dimension; + #constants + } } } - fn unit_const(&self, unit: &Unit) -> TokenStream { + fn gen_unit_def(&self, unit: &Unit) -> TokenStream { let dimension = self.get_dimension_expr(&unit.dimensions); let name = &unit.name; let magnitude = self.get_magnitude_expr(unit.magnitude); @@ -48,7 +65,16 @@ impl Codegen { } } - fn get_magnitude_expr(&self, magnitude: Magnitude) -> TokenStream { + fn gen_constant_def(&self, constant: &Constant) -> TokenStream { + let dimension = self.get_dimension_expr(&constant.dimensions); + let name = &constant.name; + let magnitude = self.get_magnitude_expr(constant.magnitude); + quote! { + pub const #name: Unit<{ #dimension }, { #magnitude }> = Unit; + } + } + + pub fn get_magnitude_expr(&self, magnitude: Magnitude) -> TokenStream { let (mantissa, exponent, sign) = (magnitude.mantissa, magnitude.exponent, magnitude.sign); quote! { Magnitude { From 5c912778500d6cca1659ac07d5d135bc7a36da72 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 21 Jan 2024 18:10:44 +0100 Subject: [PATCH 09/40] fix spans --- crates/diman_unit_system/src/codegen/dimensions.rs | 2 +- .../src/codegen/units_and_constants.rs | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/diman_unit_system/src/codegen/dimensions.rs b/crates/diman_unit_system/src/codegen/dimensions.rs index a03f881..3f79626 100644 --- a/crates/diman_unit_system/src/codegen/dimensions.rs +++ b/crates/diman_unit_system/src/codegen/dimensions.rs @@ -25,7 +25,7 @@ impl Codegen { } } - pub(crate) fn gen_dimensions(&self) -> TokenStream { + pub fn gen_dimensions(&self) -> TokenStream { let dimension_type = &self.defs.dimension_type; let quantity_type = &self.defs.quantity_type; let defs = self.gen_dimension_definitions(); diff --git a/crates/diman_unit_system/src/codegen/units_and_constants.rs b/crates/diman_unit_system/src/codegen/units_and_constants.rs index f89e91a..68b19e0 100644 --- a/crates/diman_unit_system/src/codegen/units_and_constants.rs +++ b/crates/diman_unit_system/src/codegen/units_and_constants.rs @@ -1,5 +1,5 @@ use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, quote_spanned}; use super::Codegen; use crate::types::{Constant, Unit}; @@ -24,9 +24,9 @@ impl Codegen { .constants .iter() .map(|unit| { - let unit = self.gen_constant_def(unit); + let constant = self.gen_constant_def(unit); quote! { - #unit + #constant } }) .collect(); @@ -60,7 +60,8 @@ impl Codegen { let dimension = self.get_dimension_expr(&unit.dimensions); let name = &unit.name; let magnitude = self.get_magnitude_expr(unit.magnitude); - quote! { + let span = self.defs.dimension_type.span(); + quote_spanned! {span=> pub const #name: Unit<{ #dimension }, { #magnitude }> = Unit; } } @@ -69,7 +70,8 @@ impl Codegen { let dimension = self.get_dimension_expr(&constant.dimensions); let name = &constant.name; let magnitude = self.get_magnitude_expr(constant.magnitude); - quote! { + let span = self.defs.dimension_type.span(); + quote_spanned! {span=> pub const #name: Unit<{ #dimension }, { #magnitude }> = Unit; } } From 37aed11947ad53c5be16c951a22619cd9f38c99a Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 21 Jan 2024 18:15:09 +0100 Subject: [PATCH 10/40] add magnitude f32 mul impl --- crates/diman_lib/src/magnitude.rs | 32 +++++++++++++++++++ .../src/codegen/unit_type.rs | 4 +-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/crates/diman_lib/src/magnitude.rs b/crates/diman_lib/src/magnitude.rs index 0ade71c..01bd337 100644 --- a/crates/diman_lib/src/magnitude.rs +++ b/crates/diman_lib/src/magnitude.rs @@ -108,6 +108,38 @@ impl Div for f64 { } } +impl Mul for Magnitude { + type Output = Self; + + fn mul(self, rhs: f32) -> Self::Output { + Self::from_f64((self.into_f32() * rhs) as f64) + } +} + +impl Div for Magnitude { + type Output = Self; + + fn div(self, rhs: f32) -> Self::Output { + Self::from_f64((self.into_f32() / rhs) as f64) + } +} + +impl Mul for f32 { + type Output = Self; + + fn mul(self, rhs: Magnitude) -> Self::Output { + self * rhs.into_f32() + } +} + +impl Div for f32 { + type Output = Self; + + fn div(self, rhs: Magnitude) -> Self::Output { + self / rhs.into_f32() + } +} + #[cfg(test)] mod tests { use crate::magnitude::Magnitude; diff --git a/crates/diman_unit_system/src/codegen/unit_type.rs b/crates/diman_unit_system/src/codegen/unit_type.rs index 86db2db..810dcdb 100644 --- a/crates/diman_unit_system/src/codegen/unit_type.rs +++ b/crates/diman_unit_system/src/codegen/unit_type.rs @@ -161,9 +161,9 @@ impl Codegen { impl Unit { pub fn new(self, val: S) -> Quantity where - S: Mul, + S: Mul, { - Quantity(val * F.into_f64()) + Quantity(val * F) } } } From 7b88b5bb3494794be7feea94c1065bc07d90e485 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Mon, 22 Jan 2024 13:35:14 +0100 Subject: [PATCH 11/40] revive float tests --- tests/float/mod.rs | 471 ++++++++++++++++++++++++--------------------- 1 file changed, 249 insertions(+), 222 deletions(-) diff --git a/tests/float/mod.rs b/tests/float/mod.rs index b53b8cc..2a6a995 100644 --- a/tests/float/mod.rs +++ b/tests/float/mod.rs @@ -1,124 +1,157 @@ macro_rules! gen_tests_for_float { ($float_name: ident, $mod_name: ident, $assert_is_close: path, $assert_is_close_float: path) => { mod $mod_name { - use crate::example_system::$float_name::Dimensionless; - use crate::example_system::$float_name::Energy; - use crate::example_system::$float_name::Force; - use crate::example_system::$float_name::Length; - use crate::example_system::$float_name::Mass; - use crate::example_system::$float_name::Time; - use crate::example_system::$float_name::Velocity; - use crate::example_system::$float_name::SOLAR_MASS; - use crate::example_system::$float_name::SOLAR_MASS_AWKWARD; - use crate::example_system::$float_name::SOLAR_MASS_GRAMS; + use crate::example_system::constants::{ + SOLAR_MASS, SOLAR_MASS_AWKWARD, SOLAR_MASS_GRAMS, + }; + use crate::example_system::dimensions::{ + Dimensionless, Energy, Force, Length, Mass, Time, Velocity, + }; + use crate::example_system::units; use $assert_is_close as assert_is_close; use $assert_is_close_float as assert_is_close_float; + // These help with what would otherwise require lots of + // type annotations for the float which are awkward to do + // because we can't write 1.0f64 or similar literals here + + fn meters(x: $float_name) -> Length<$float_name> { + x * units::meters + } + + fn kilometers(x: $float_name) -> Length<$float_name> { + x * units::kilometers + } + + fn seconds(x: $float_name) -> Time<$float_name> { + x * units::seconds + } + + fn kilograms(x: $float_name) -> Mass<$float_name> { + x * units::kilograms + } + + fn dimensionless(x: $float_name) -> Dimensionless<$float_name> { + x * units::dimensionless + } + + fn meters_per_second(x: $float_name) -> Velocity<$float_name> { + x * units::meters_per_second + } + + fn newtons(x: $float_name) -> Force<$float_name> { + x * units::newtons + } + + fn joules(x: $float_name) -> Energy<$float_name> { + x * units::joules + } + #[test] fn add_same_unit() { - let x = Length::meters(1.0); - let y = Length::meters(10.0); - assert_is_close(x + y, Length::meters(11.0)); + let x = meters(1.0); + let y = meters(10.0); + assert_is_close(x + y, meters(11.0)); } #[test] fn add_different_units() { - let x = Length::meters(1.0); - let y = Length::kilometers(10.0); - assert_is_close(x + y, Length::meters(10001.0)); + let x = meters(1.0); + let y = kilometers(10.0); + assert_is_close(x + y, meters(10001.0)); } #[test] fn add_quantity_ref() { - let x = Length::meters(1.0); - let y = Length::meters(10.0); - assert_is_close(x + &y, Length::meters(11.0)); + let x = meters(1.0); + let y = meters(10.0); + assert_is_close(x + &y, meters(11.0)); } #[test] fn add_ref_quantity() { - let x = Length::meters(1.0); - let y = Length::meters(10.0); - assert_is_close(&x + y, Length::meters(11.0)); + let x = meters(1.0); + let y = meters(10.0); + assert_is_close(&x + y, meters(11.0)); } #[test] fn add_ref_ref() { - let x = Length::meters(1.0); - let y = Length::meters(10.0); - assert_is_close(&x + &y, Length::meters(11.0)); + let x = meters(1.0); + let y = meters(10.0); + assert_is_close(&x + &y, meters(11.0)); } #[test] fn add_quantity_type() { - let x = Dimensionless::dimensionless(1.0); + let x = dimensionless(1.0); let y = 10.0; - assert_is_close(x + y, Dimensionless::dimensionless(11.0)); + assert_is_close(x + y, dimensionless(11.0)); } #[test] fn add_type_quantity() { - let x = Dimensionless::dimensionless(1.0); + let x = dimensionless(1.0); let y = 10.0; - assert_is_close(y + x, Dimensionless::dimensionless(11.0)); + assert_is_close(y + x, dimensionless(11.0)); } #[test] fn add_ref_type() { - let x = &Dimensionless::dimensionless(1.0); + let x = &dimensionless(1.0); let y = 10.0; - assert_is_close(x + y, Dimensionless::dimensionless(11.0)); + assert_is_close(x + y, dimensionless(11.0)); } #[test] fn add_quantity_reftype() { - let x = Dimensionless::dimensionless(1.0); + let x = dimensionless(1.0); let y = &10.0; - assert_is_close(x + y, Dimensionless::dimensionless(11.0)); + assert_is_close(x + y, dimensionless(11.0)); } #[test] fn add_ref_reftype() { - let x = &Dimensionless::dimensionless(1.0); + let x = &dimensionless(1.0); let y = &10.0; - assert_is_close(x + y, Dimensionless::dimensionless(11.0)); + assert_is_close(x + y, dimensionless(11.0)); } #[test] fn add_assign_quantity_quantity() { - let mut x = Length::meters(1.0); - let y = Length::kilometers(10.0); + let mut x = meters(1.0); + let y = kilometers(10.0); x += y; - assert_is_close(x, Length::meters(10001.0)); + assert_is_close(x, meters(10001.0)); } #[test] fn add_assign_quantity_ref() { - let mut x = Length::meters(1.0); - let y = Length::kilometers(10.0); + let mut x = meters(1.0); + let y = kilometers(10.0); x += &y; - assert_is_close(x, Length::meters(10001.0)); + assert_is_close(x, meters(10001.0)); } #[test] fn add_assign_ref_ref() { - let mut x = &mut Length::meters(1.0); - let y = Length::kilometers(10.0); + let mut x = &mut meters(1.0); + let y = kilometers(10.0); x += &y; - assert_is_close(*x, Length::meters(10001.0)); + assert_is_close(*x, meters(10001.0)); } #[test] fn add_assign_quantity_type() { - let mut x = Dimensionless::dimensionless(1.0); + let mut x = dimensionless(1.0); let y = 10.0; x += y; - assert_is_close(x, Dimensionless::dimensionless(11.0)); + assert_is_close(x, dimensionless(11.0)); } #[test] fn add_assign_type_quantity() { - let x = Dimensionless::dimensionless(1.0); + let x = dimensionless(1.0); let mut y = 10.0; y += x; assert_is_close_float(y, 11.0); @@ -126,122 +159,117 @@ macro_rules! gen_tests_for_float { #[test] fn add_assign_ref_type() { - let mut x = &mut Dimensionless::dimensionless(1.0); + let mut x = &mut dimensionless(1.0); let y = 10.0; x += y; - assert_is_close(*x, Dimensionless::dimensionless(11.0)); + assert_is_close(*x, dimensionless(11.0)); } #[test] fn sum_quantity_type() { - let items = [ - Length::meters(3.0), - Length::kilometers(3.0), - Length::meters(9.0), - Length::kilometers(1.0), - ]; - assert_is_close(items.into_iter().sum(), Length::meters(4012.0)); + let items = [meters(3.0), kilometers(3.0), meters(9.0), kilometers(1.0)]; + assert_is_close(items.into_iter().sum(), meters(4012.0)); } #[test] fn sub_different_units() { - let x = Length::meters(1.0); - let y = Length::kilometers(10.0); - assert_is_close(x - y, Length::meters(-9999.0)); + let x = meters(1.0); + let y = kilometers(10.0); + assert_is_close(x - y, meters(-9999.0)); } #[test] fn sub_quantity_ref() { - let x = Length::meters(1.0); - let y = Length::meters(10.0); - assert_is_close(x - &y, Length::meters(-9.0)); + let x = meters(1.0); + let y = meters(10.0); + assert_is_close(x - &y, meters(-9.0)); } #[test] fn sub_ref_ref() { - let x = Length::meters(1.0); - let y = Length::meters(10.0); - assert_is_close(&x - &y, Length::meters(-9.0)); + let x = meters(1.0); + let y = meters(10.0); + assert_is_close(&x - &y, meters(-9.0)); } #[test] fn sub_quantity_type() { - let x = Dimensionless::dimensionless(1.0); + let x = dimensionless(1.0); let y = 10.0; - assert_is_close(x - y, Dimensionless::dimensionless(-9.0)); + assert_is_close(x - y, dimensionless(-9.0)); } #[test] fn sub_type_quantity() { - let x = Dimensionless::dimensionless(1.0); + let x = dimensionless(1.0); let y = 10.0; - assert_is_close(y - x, Dimensionless::dimensionless(9.0)); + assert_is_close(y - x, dimensionless(9.0)); } #[test] fn sub_ref_type() { - let x = &Dimensionless::dimensionless(1.0); + let x = &dimensionless(1.0); let y = 10.0; - assert_is_close(x - y, Dimensionless::dimensionless(-9.0)); + assert_is_close(x - y, dimensionless(-9.0)); } #[test] fn sub_quantity_reftype() { - let x = Dimensionless::dimensionless(1.0); + let x = dimensionless(1.0); let y = &10.0; - assert_is_close(x - y, Dimensionless::dimensionless(-9.0)); + assert_is_close(x - y, dimensionless(-9.0)); } #[test] fn sub_ref_reftype() { - let x = &Dimensionless::dimensionless(1.0); + let x = &dimensionless(1.0); let y = &10.0; - assert_is_close(x - y, Dimensionless::dimensionless(-9.0)); + assert_is_close(x - y, dimensionless(-9.0)); } #[test] fn sub_assign_quantity_quantity() { - let mut x = Length::meters(1.0); - let y = Length::kilometers(10.0); + let mut x = meters(1.0); + let y = kilometers(10.0); x -= y; - assert_is_close(x, Length::meters(-9999.0)); + assert_is_close(x, meters(-9999.0)); } #[test] fn sub_assign_quantity_ref() { - let mut x = Length::meters(1.0); - let y = Length::kilometers(10.0); + let mut x = meters(1.0); + let y = kilometers(10.0); x -= &y; - assert_is_close(x, Length::meters(-9999.0)); + assert_is_close(x, meters(-9999.0)); } #[test] fn sub_assign_ref_quantity() { - let mut x = &mut Length::meters(1.0); - let y = Length::kilometers(10.0); + let mut x = &mut meters(1.0); + let y = kilometers(10.0); x -= y; - assert_is_close(*x, Length::meters(-9999.0)); + assert_is_close(*x, meters(-9999.0)); } #[test] fn sub_assign_ref_ref() { - let mut x = &mut Length::meters(1.0); - let y = Length::kilometers(10.0); + let mut x = &mut meters(1.0); + let y = kilometers(10.0); x -= &y; - assert_is_close(*x, Length::meters(-9999.0)); + assert_is_close(*x, meters(-9999.0)); } #[test] fn sub_assign_quantity_type() { - let mut x = Dimensionless::dimensionless(1.0); + let mut x = dimensionless(1.0); let y = 10.0; x -= y; - assert_is_close(x, Dimensionless::dimensionless(-9.0)); + assert_is_close(x, dimensionless(-9.0)); } #[test] fn sub_assign_type_quantity() { - let x = Dimensionless::dimensionless(1.0); + let x = dimensionless(1.0); let mut y = 10.0; y -= x; assert_is_close_float(y, 9.0); @@ -249,127 +277,127 @@ macro_rules! gen_tests_for_float { #[test] fn sub_assign_ref_type() { - let mut x = &mut Dimensionless::dimensionless(1.0); + let mut x = &mut dimensionless(1.0); let y = 10.0; x -= y; - assert_is_close(*x, Dimensionless::dimensionless(-9.0)); + assert_is_close(*x, dimensionless(-9.0)); } #[test] fn neg_quantity() { - let x = Length::meters(5.0); - let y = Length::meters(2.0); - assert_is_close(x + (-y), Length::meters(3.0)); - assert_is_close(x - y, Length::meters(3.0)); + let x = meters(5.0); + let y = meters(2.0); + assert_is_close(x + (-y), meters(3.0)); + assert_is_close(x - y, meters(3.0)); } #[test] fn mul_quantity_quantity() { - let x = Force::newtons(2.0); - let y = Length::meters(3.0); - assert_is_close(x * y, Energy::joules(6.0)); + let x = newtons(2.0); + let y = meters(3.0); + assert_is_close(x * y, joules(6.0)); } #[test] fn mul_quantity_ref() { - let x = Force::newtons(2.0); - let y = Length::meters(3.0); - assert_is_close(x * &y, Energy::joules(6.0)); + let x = newtons(2.0); + let y = meters(3.0); + assert_is_close(x * &y, joules(6.0)); } #[test] fn mul_ref_quantity() { - let x = Force::newtons(2.0); - let y = Length::meters(3.0); - assert_is_close(&x * y, Energy::joules(6.0)); + let x = newtons(2.0); + let y = meters(3.0); + assert_is_close(&x * y, joules(6.0)); } #[test] fn mul_ref_ref() { - let x = Force::newtons(2.0); - let y = Length::meters(3.0); - assert_is_close(&x * &y, Energy::joules(6.0)); + let x = newtons(2.0); + let y = meters(3.0); + assert_is_close(&x * &y, joules(6.0)); } #[test] fn mul_quantity_type() { - let x = Force::newtons(2.0); + let x = newtons(2.0); let y = 3.0; - assert_is_close(x * y, Force::newtons(6.0)); + assert_is_close(x * y, newtons(6.0)); } #[test] fn mul_type_quantity() { let x = 3.0; - let y = Force::newtons(2.0); - assert_is_close(x * y, Force::newtons(6.0)); + let y = newtons(2.0); + assert_is_close(x * y, newtons(6.0)); } #[test] fn mul_quantity_typeref() { - let x = Force::newtons(2.0); + let x = newtons(2.0); let y = &3.0; - assert_is_close(x * y, Force::newtons(6.0)); + assert_is_close(x * y, newtons(6.0)); } #[test] fn mul_ref_type() { - let x = &Force::newtons(2.0); + let x = &newtons(2.0); let y = 3.0; - assert_is_close(x * y, Force::newtons(6.0)); + assert_is_close(x * y, newtons(6.0)); } #[test] fn mul_ref_typeref() { - let x = &Force::newtons(2.0); + let x = &newtons(2.0); let y = &3.0; - assert_is_close(x * y, Force::newtons(6.0)); + assert_is_close(x * y, newtons(6.0)); } #[test] fn mul_assign_quantity_quantity() { - let mut x = Force::newtons(2.0); - let y = Dimensionless::dimensionless(3.0); + let mut x = newtons(2.0); + let y = dimensionless(3.0); x *= y; - assert_is_close(x, Force::newtons(6.0)); + assert_is_close(x, newtons(6.0)); } #[test] fn mul_assign_quantity_ref() { - let mut x = Force::newtons(2.0); - let y = Dimensionless::dimensionless(3.0); + let mut x = newtons(2.0); + let y = dimensionless(3.0); x *= &y; - assert_is_close(x, Force::newtons(6.0)); + assert_is_close(x, newtons(6.0)); } #[test] fn mul_assign_ref_quantity() { - let mut x = &mut Force::newtons(2.0); - let y = Dimensionless::dimensionless(3.0); + let mut x = &mut newtons(2.0); + let y = dimensionless(3.0); x *= y; - assert_is_close(*x, Force::newtons(6.0)); + assert_is_close(*x, newtons(6.0)); } #[test] fn mul_assign_ref_ref() { - let mut x = &mut Force::newtons(2.0); - let y = Dimensionless::dimensionless(3.0); + let mut x = &mut newtons(2.0); + let y = dimensionless(3.0); x *= &y; - assert_is_close(*x, Force::newtons(6.0)); + assert_is_close(*x, newtons(6.0)); } #[test] fn mul_assign_quantity_type() { - let mut x = Force::newtons(2.0); + let mut x = newtons(2.0); let y = 3.0; x *= y; - assert_is_close(x, Force::newtons(6.0)); + assert_is_close(x, newtons(6.0)); } #[test] fn mul_assign_type_quantity() { let mut x = 3.0; - let y = Dimensionless::dimensionless(2.0); + let y = dimensionless(2.0); x *= y; assert_is_close_float(x, 6.0); } @@ -377,118 +405,118 @@ macro_rules! gen_tests_for_float { #[test] fn mul_assign_type_ref() { let mut x = 3.0; - let y = &Dimensionless::dimensionless(2.0); + let y = &dimensionless(2.0); x *= y; assert_is_close_float(x, 6.0); } #[test] fn div_quantity_quantity() { - let x = Length::meters(6.0); - let y = Time::seconds(2.0); - assert_is_close(x / y, Velocity::meters_per_second(3.0)); + let x = meters(6.0); + let y = seconds(2.0); + assert_is_close(x / y, meters_per_second(3.0)); } #[test] fn div_quantity_ref() { - let x = Length::meters(6.0); - let y = Time::seconds(2.0); - assert_is_close(x / &y, Velocity::meters_per_second(3.0)); + let x = meters(6.0); + let y = seconds(2.0); + assert_is_close(x / &y, meters_per_second(3.0)); } #[test] fn div_ref_quantity() { - let x = Length::meters(6.0); - let y = Time::seconds(2.0); - assert_is_close(&x / y, Velocity::meters_per_second(3.0)); + let x = meters(6.0); + let y = seconds(2.0); + assert_is_close(&x / y, meters_per_second(3.0)); } #[test] fn div_ref_ref() { - let x = Length::meters(6.0); - let y = Time::seconds(2.0); - assert_is_close(&x / &y, Velocity::meters_per_second(3.0)); + let x = meters(6.0); + let y = seconds(2.0); + assert_is_close(&x / &y, meters_per_second(3.0)); } #[test] fn div_assign_quantity_quantity() { - let mut x = Force::newtons(2.0); - let y = Dimensionless::dimensionless(4.0); + let mut x = newtons(2.0); + let y = dimensionless(4.0); x /= y; - assert_is_close(x, Force::newtons(0.5)); + assert_is_close(x, newtons(0.5)); } #[test] fn div_assign_quantity_ref() { - let mut x = Force::newtons(2.0); - let y = Dimensionless::dimensionless(4.0); + let mut x = newtons(2.0); + let y = dimensionless(4.0); x /= &y; - assert_is_close(x, Force::newtons(0.5)); + assert_is_close(x, newtons(0.5)); } #[test] fn div_assign_ref_quantity() { - let mut x = &mut Force::newtons(2.0); - let y = Dimensionless::dimensionless(4.0); + let mut x = &mut newtons(2.0); + let y = dimensionless(4.0); x /= y; - assert_is_close(*x, Force::newtons(0.5)); + assert_is_close(*x, newtons(0.5)); } #[test] fn div_assign_ref_ref() { - let mut x = &mut Force::newtons(2.0); - let y = Dimensionless::dimensionless(4.0); + let mut x = &mut newtons(2.0); + let y = dimensionless(4.0); x /= &y; - assert_is_close(*x, Force::newtons(0.5)); + assert_is_close(*x, newtons(0.5)); } #[test] fn div_quantity_type() { - let x = Length::meters(6.0); + let x = meters(6.0); let y = 2.0; - assert_is_close(x / y, Length::meters(3.0)); + assert_is_close(x / y, meters(3.0)); } #[test] fn div_quantity_reftype() { - let x = Length::meters(6.0); + let x = meters(6.0); let y = &2.0; - assert_is_close(x / y, Length::meters(3.0)); + assert_is_close(x / y, meters(3.0)); } #[test] fn div_ref_type() { - let x = &Length::meters(6.0); + let x = &meters(6.0); let y = 2.0; - assert_is_close(x / y, Length::meters(3.0)); + assert_is_close(x / y, meters(3.0)); } #[test] fn div_ref_reftype() { - let x = &Length::meters(6.0); + let x = &meters(6.0); let y = &2.0; - assert_is_close(x / y, Length::meters(3.0)); + assert_is_close(x / y, meters(3.0)); } #[test] fn div_assign_quantity_type() { - let mut x = Length::meters(6.0); + let mut x = meters(6.0); let y = 2.0; x /= y; - assert_is_close(x, Length::meters(3.0)); + assert_is_close(x, meters(3.0)); } #[test] fn div_type_quantity() { let x = 2.0; - let y = Velocity::meters_per_second(6.0); - assert_is_close(x / y, Time::seconds(2.0) / Length::meters(6.0)); + let y = meters_per_second(6.0); + assert_is_close(x / y, seconds(2.0) / meters(6.0)); } #[test] fn div_assign_type_quantity() { let mut x = 6.0; - let y = Dimensionless::dimensionless(2.0); + let y = dimensionless(2.0); x /= y; assert_is_close_float(x, 3.0); } @@ -496,70 +524,76 @@ macro_rules! gen_tests_for_float { #[cfg(any(feature = "std", feature = "num-traits-libm"))] #[test] fn sqrt_float_quantity() { - let x = Length::meters(6.0).powi::<2>(); - let y = Time::seconds(2.0).powi::<2>(); - assert_is_close((x / y).sqrt(), Velocity::meters_per_second(3.0)); + let x = meters(6.0).powi::<2>(); + let y = seconds(2.0).powi::<2>(); + assert_is_close((x / y).sqrt(), meters_per_second(3.0)); } #[cfg(any(feature = "std", feature = "num-traits-libm"))] #[test] fn cbrt_float_quantity() { - let x = Length::meters(4.0).powi::<3>(); - let y = Time::seconds(1.0).powi::<3>(); - assert_is_close((x / y).cbrt(), Velocity::meters_per_second(4.0)); + let x = meters(4.0).powi::<3>(); + let y = seconds(1.0).powi::<3>(); + assert_is_close((x / y).cbrt(), meters_per_second(4.0)); } #[test] fn constant() { - assert_is_close(SOLAR_MASS, Mass::kilograms(1.988477e30)); - assert_is_close(SOLAR_MASS_GRAMS, Mass::kilograms(1.988477e30)); - assert_is_close(SOLAR_MASS_AWKWARD, Mass::kilograms(1.988477e30)); + assert_is_close((1.0 as $float_name) * SOLAR_MASS, kilograms(1.988477e30)); + assert_is_close( + (1.0 as $float_name) * SOLAR_MASS_GRAMS, + kilograms(1.988477e30), + ); + assert_is_close( + (1.0 as $float_name) * SOLAR_MASS_AWKWARD, + kilograms(1.988477e30), + ); } #[cfg(any(feature = "std", feature = "num-traits-libm"))] #[test] fn log2() { - let x = Dimensionless::dimensionless(128.0); - assert_is_close(x.log2(), Dimensionless::dimensionless(7.0)); + let x = dimensionless(128.0); + assert_is_close(x.log2(), dimensionless(7.0)); } #[test] fn deref_dimensionless() { - let x = Dimensionless::dimensionless(128.3); + let x = dimensionless(128.3); assert_eq!(x.round(), 128.0); } #[test] fn partial_eq_quantity_quantity() { - let x = Length::meters(50.0); - let y = Length::meters(50.0); + let x = meters(50.0); + let y = meters(50.0); assert!(x == y); } #[test] fn partial_eq_quantity_ref() { - let x = Length::meters(50.0); - let y = Length::meters(50.0); + let x = meters(50.0); + let y = meters(50.0); assert!(x == &y); } #[test] fn partial_eq_ref_quantity() { - let x = Length::meters(50.0); - let y = Length::meters(50.0); + let x = meters(50.0); + let y = meters(50.0); assert!(&x == y); } #[test] fn partial_eq_ref_ref() { - let x = Length::meters(50.0); - let y = Length::meters(50.0); + let x = meters(50.0); + let y = meters(50.0); assert!(&x == &y); } #[test] fn partial_eq_quantity_type() { - let x = Dimensionless::dimensionless(50.0); + let x = dimensionless(50.0); let y = 50.0; assert!(x == y); } @@ -567,20 +601,20 @@ macro_rules! gen_tests_for_float { #[test] fn partial_eq_type_quantity() { let x = 50.0; - let y = Dimensionless::dimensionless(50.0); + let y = dimensionless(50.0); assert!(x == y); } #[test] fn partial_ord_quantity_quantity() { - let x = Length::meters(50.0); - let y = Length::meters(49.0); + let x = meters(50.0); + let y = meters(49.0); assert!(x > y); } #[test] fn partial_ord_quantity_type() { - let x = Dimensionless::dimensionless(50.0); + let x = dimensionless(50.0); let y = 49.0; assert!(x >= y); } @@ -588,61 +622,54 @@ macro_rules! gen_tests_for_float { #[test] fn partial_ord_type_quantity() { let x = 50.0; - let y = Dimensionless::dimensionless(49.0); + let y = dimensionless(49.0); assert!(x > y); } #[test] fn clamp_quantity_quantity() { - let x = Length::meters(10.0); - let y = Length::kilometers(20.0); - assert!(Length::meters(1.0).clamp(x, y) == x); - assert!(Length::meters(50.0).clamp(x, y) == Length::meters(50.0)); - assert!(Length::meters(50000.0).clamp(x, y) == y); + let x = meters(10.0); + let y = kilometers(20.0); + assert!(meters(1.0).clamp(x, y) == x); + assert!(meters(50.0).clamp(x, y) == meters(50.0)); + assert!(meters(50000.0).clamp(x, y) == y); } #[test] fn clamp_quantity_type() { let x = 10.0; let y = 20.0; - assert!(Dimensionless::dimensionless(5.0).clamp(x, y) == x); - assert!( - Dimensionless::dimensionless(15.0).clamp(x, y) - == Dimensionless::dimensionless(15.0) - ); - assert!(Dimensionless::dimensionless(50.0).clamp(x, y) == y); + assert!(dimensionless(5.0).clamp(x, y) == x); + assert!(dimensionless(15.0).clamp(x, y) == dimensionless(15.0)); + assert!(dimensionless(50.0).clamp(x, y) == y); } #[test] fn max_quantity_quantity() { - let x = Length::meters(10.0); - assert!(Length::meters(5.0).max(x) == x); - assert!(Length::meters(15.0).max(x) == Length::meters(15.0)); + let x = meters(10.0); + assert!(meters(5.0).max(x) == x); + assert!(meters(15.0).max(x) == meters(15.0)); } #[test] fn max_quantity_type() { let x = 10.0; - assert!(Dimensionless::dimensionless(5.0).max(x) == x); - assert!( - Dimensionless::dimensionless(15.0).max(x) == Dimensionless::dimensionless(15.0) - ); + assert!(dimensionless(5.0).max(x) == x); + assert!(dimensionless(15.0).max(x) == dimensionless(15.0)); } #[test] fn min_quantity_quantity() { - let x = Length::meters(10.0); - assert!(Length::meters(5.0).min(x) == Length::meters(5.0)); - assert!(Length::meters(15.0).min(x) == x); + let x = meters(10.0); + assert!(meters(5.0).min(x) == meters(5.0)); + assert!(meters(15.0).min(x) == x); } #[test] fn min_quantity_type() { let x = 10.0; - assert!( - Dimensionless::dimensionless(5.0).min(x) == Dimensionless::dimensionless(5.0) - ); - assert!(Dimensionless::dimensionless(15.0).min(x) == x); + assert!(dimensionless(5.0).min(x) == dimensionless(5.0)); + assert!(dimensionless(15.0).min(x) == x); } } }; From ce8f6b4c1729a0cf213a9cf557a5b2195c398c1b Mon Sep 17 00:00:00 2001 From: tehforsch Date: Mon, 22 Jan 2024 16:52:39 +0100 Subject: [PATCH 12/40] revive type aliases test --- tests/type_aliases/mod.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/tests/type_aliases/mod.rs b/tests/type_aliases/mod.rs index c472e0f..bec62af 100644 --- a/tests/type_aliases/mod.rs +++ b/tests/type_aliases/mod.rs @@ -1,26 +1,21 @@ +// These just need to compile. macro_rules! gen_tests_for_float { ($float_name: ident, $mod_name: ident) => { mod $mod_name { - use crate::example_system::$float_name::Length; - use crate::example_system::$float_name::Time; + use crate::example_system::dimensions::Length; + use crate::example_system::dimensions::Time; use diman::Product; use diman::Quotient; - fn product_1(length: Length, time: Time) -> Product { + #[allow(unused)] + fn product_1(length: Length<$float_name>, time: Time<$float_name>) -> Product, Time<$float_name>> { length * time } - fn quotient_1(length: Length, time: Time) -> Quotient { + #[allow(unused)] + fn quotient_1(length: Length<$float_name>, time: Time<$float_name>) -> Quotient, Time<$float_name>> { length / time } - - #[test] - fn type_aliases() { - // All of these really just need compile, so no need to check for equality. (In principle - // we don't even need this test) - product_1(Length::meters(2.0), Time::seconds(2.0)); - quotient_1(Length::meters(2.0), Time::seconds(2.0)); - } } }; } From 9b849bc99a53b5e03975ee8987f3936a8b89b958 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Mon, 22 Jan 2024 17:04:08 +0100 Subject: [PATCH 13/40] fix unit aliases / type aliases test --- tests/float/mod.rs | 48 +++++++++++---------------------------- tests/type_aliases/mod.rs | 10 ++++++-- tests/unit_aliases/mod.rs | 35 +++++++++++++++++----------- tests/utils/mod.rs | 13 +++++++++++ 4 files changed, 56 insertions(+), 50 deletions(-) diff --git a/tests/float/mod.rs b/tests/float/mod.rs index 2a6a995..66d3122 100644 --- a/tests/float/mod.rs +++ b/tests/float/mod.rs @@ -8,44 +8,22 @@ macro_rules! gen_tests_for_float { Dimensionless, Energy, Force, Length, Mass, Time, Velocity, }; use crate::example_system::units; + use crate::make_annotated_unit_constructor; use $assert_is_close as assert_is_close; use $assert_is_close_float as assert_is_close_float; - // These help with what would otherwise require lots of - // type annotations for the float which are awkward to do - // because we can't write 1.0f64 or similar literals here - - fn meters(x: $float_name) -> Length<$float_name> { - x * units::meters - } - - fn kilometers(x: $float_name) -> Length<$float_name> { - x * units::kilometers - } - - fn seconds(x: $float_name) -> Time<$float_name> { - x * units::seconds - } - - fn kilograms(x: $float_name) -> Mass<$float_name> { - x * units::kilograms - } - - fn dimensionless(x: $float_name) -> Dimensionless<$float_name> { - x * units::dimensionless - } - - fn meters_per_second(x: $float_name) -> Velocity<$float_name> { - x * units::meters_per_second - } - - fn newtons(x: $float_name) -> Force<$float_name> { - x * units::newtons - } - - fn joules(x: $float_name) -> Energy<$float_name> { - x * units::joules - } + make_annotated_unit_constructor!(meters, Length<$float_name>, $float_name); + make_annotated_unit_constructor!(kilometers, Length<$float_name>, $float_name); + make_annotated_unit_constructor!(seconds, Time<$float_name>, $float_name); + make_annotated_unit_constructor!(kilograms, Mass<$float_name>, $float_name); + make_annotated_unit_constructor!( + dimensionless, + Dimensionless<$float_name>, + $float_name + ); + make_annotated_unit_constructor!(meters_per_second, Velocity<$float_name>, $float_name); + make_annotated_unit_constructor!(newtons, Force<$float_name>, $float_name); + make_annotated_unit_constructor!(joules, Energy<$float_name>, $float_name); #[test] fn add_same_unit() { diff --git a/tests/type_aliases/mod.rs b/tests/type_aliases/mod.rs index bec62af..648bd01 100644 --- a/tests/type_aliases/mod.rs +++ b/tests/type_aliases/mod.rs @@ -8,12 +8,18 @@ macro_rules! gen_tests_for_float { use diman::Quotient; #[allow(unused)] - fn product_1(length: Length<$float_name>, time: Time<$float_name>) -> Product, Time<$float_name>> { + fn product_1( + length: Length<$float_name>, + time: Time<$float_name>, + ) -> Product, Time<$float_name>> { length * time } #[allow(unused)] - fn quotient_1(length: Length<$float_name>, time: Time<$float_name>) -> Quotient, Time<$float_name>> { + fn quotient_1( + length: Length<$float_name>, + time: Time<$float_name>, + ) -> Quotient, Time<$float_name>> { length / time } } diff --git a/tests/unit_aliases/mod.rs b/tests/unit_aliases/mod.rs index d171778..f39880f 100644 --- a/tests/unit_aliases/mod.rs +++ b/tests/unit_aliases/mod.rs @@ -17,30 +17,39 @@ unit_system!( macro_rules! gen_tests_for_float { ($float_name: ident, $mod_name: ident, $assert_is_close: path, $assert_is_close_float: path) => { mod $mod_name { - use super::$float_name::Length; + use super::dimensions::Length; + use super::units; + use crate::make_annotated_unit_constructor; + make_annotated_unit_constructor!(meters, Length<$float_name>, $float_name); + make_annotated_unit_constructor!(metres, Length<$float_name>, $float_name); + make_annotated_unit_constructor!(centimeters, Length<$float_name>, $float_name); + make_annotated_unit_constructor!(centimetres, Length<$float_name>, $float_name); + make_annotated_unit_constructor!(kilometers, Length<$float_name>, $float_name); + make_annotated_unit_constructor!(foo, Length<$float_name>, $float_name); + make_annotated_unit_constructor!(kilofoo, Length<$float_name>, $float_name); #[test] fn unit_aliases() { - assert_eq!(Length::meters(100.0), Length::metres(100.0)); - let x = Length::meters(100.0); - assert_eq!(x.in_meters(), 100.0); - assert_eq!(x.in_metres(), 100.0); + assert_eq!(meters(100.0), metres(100.0)); + let x = meters(100.0); + assert_eq!(x.value_in(units::meters), 100.0); + assert_eq!(x.value_in(units::metres), 100.0); } #[test] fn prefixed_aliases() { - assert_eq!(Length::centimeters(100.0), Length::centimetres(100.0)); - let x = Length::centimeters(100.0); - assert_eq!(x.in_meters(), 1.0); - assert_eq!(x.in_metres(), 1.0); - assert_eq!(x.in_centimeters(), 100.0); - assert_eq!(x.in_centimetres(), 100.0); + assert_eq!(centimeters(100.0), centimetres(100.0)); + let x = centimeters(100.0); + assert_eq!(x.value_in(units::meters), 1.0); + assert_eq!(x.value_in(units::metres), 1.0); + assert_eq!(x.value_in(units::centimeters), 100.0); + assert_eq!(x.value_in(units::centimetres), 100.0); } #[test] fn explicit_prefix() { - assert_eq!(Length::foo(100.0), Length::meters(25.0)); - assert_eq!(Length::kilofoo(100.0), Length::kilometers(25.0)); + assert_eq!(foo(100.0), meters(25.0)); + assert_eq!(kilofoo(100.0), kilometers(25.0)); } } }; diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 5bad308..46639c1 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -33,3 +33,16 @@ pub(crate) fn assert_is_close_float_f32(x: f32, y: f32) { pub(crate) fn assert_is_close_float_f64(x: f64, y: f64) { assert!((x - y).abs() < f64::EPSILON, "{} {}", x, y) } + +// These help with what would otherwise require lots of +// type annotations for the float which are awkward to do +// because we can't write 1.0f64 or similar literals here + +#[macro_export] +macro_rules! make_annotated_unit_constructor { + ($unit: ident, $quantity: ty, $float_name: ident) => { + fn $unit(x: $float_name) -> $quantity { + x * units::$unit + } + }; +} From 6c8ee33fccf68ab4ec4e09d164c5a458afb84758 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Mon, 22 Jan 2024 17:05:22 +0100 Subject: [PATCH 14/40] revive debug tests --- tests/debug/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/debug/mod.rs b/tests/debug/mod.rs index e730475..989db6d 100644 --- a/tests/debug/mod.rs +++ b/tests/debug/mod.rs @@ -1,14 +1,14 @@ #[cfg(test)] mod tests { - use crate::example_system::f64::{Area, Energy, Length}; + use crate::example_system::units::{joules, meters, square_meters}; #[test] fn debug() { - assert_eq!(format!("{:?}", Length::meters(50.0)), "50 m"); - assert_eq!(format!("{:?}", Area::square_meters(50.0)), "50 m^2"); + assert_eq!(format!("{:?}", 50.0 * meters), "50 m"); + assert_eq!(format!("{:?}", 50.0 * square_meters), "50 m^2"); // Unknown unit - let x = Length::meters(1.0) * Energy::joules(50.0); + let x = 50.0 * joules * meters; assert_eq!(format!("{:?}", x), "50 m^3 s^-2 kg"); - assert_eq!(format!("{:?}", Energy::joules(50.0)), "50 J"); + assert_eq!(format!("{:?}", 50.0 * joules), "50 J"); } } From f09ef38aa42fb5b0bc2f5c605fbedea8cbb52045 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Mon, 22 Jan 2024 17:09:19 +0100 Subject: [PATCH 15/40] revive dimension defs tests --- tests/dimension_defs/mod.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/dimension_defs/mod.rs b/tests/dimension_defs/mod.rs index a5c0244..5d93345 100644 --- a/tests/dimension_defs/mod.rs +++ b/tests/dimension_defs/mod.rs @@ -1,18 +1,16 @@ -use crate::example_system::f64::Area; -use crate::example_system::f64::InverseTemperature; -use crate::example_system::f64::Length; -use crate::example_system::f64::Temperature; -use crate::example_system::f64::Volume; +use crate::example_system::dimensions::InverseTemperature; +use crate::example_system::dimensions::Volume; +use crate::example_system::units::{cubic_meters, kelvins, meters, square_meters}; use crate::utils::assert_is_close_f64; #[test] fn one_over_dimension_def() { - let _: InverseTemperature = 1.0 / Temperature::kelvins(5.0); + let _: InverseTemperature = 1.0 / (5.0 * kelvins); } #[test] fn exponent_dimension_def() { - let vol: Volume = Area::square_meters(10.0) * Length::meters(2.0); - let vol2: Volume = Volume::cubic_meters(20.0); + let vol: Volume = 20.0 * square_meters * meters; + let vol2: Volume = 20.0 * cubic_meters; assert_is_close_f64(vol, vol2); } From 645a3e643d4863d52835b10428cecc570f72d40d Mon Sep 17 00:00:00 2001 From: tehforsch Date: Mon, 22 Jan 2024 17:12:01 +0100 Subject: [PATCH 16/40] revive gas test --- tests/gas/mod.rs | 47 +++++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/tests/gas/mod.rs b/tests/gas/mod.rs index 20142ca..dac13fa 100644 --- a/tests/gas/mod.rs +++ b/tests/gas/mod.rs @@ -1,47 +1,50 @@ //! Example showing gas equation of state conversions -use diman::si::f64::{ - Dimensionless, EnergyDensity, MassDensity, MomentumDensity, Pressure, SpecificEnergy, - SpecificHeatCapacity, Temperature, Velocity, +use diman::si::{ + dimensions::{ + Dimensionless, EnergyDensity, MassDensity, MomentumDensity, Pressure, SpecificEnergy, + SpecificHeatCapacity, Temperature, Velocity, + }, + units::{joules_per_kilogram_kelvin, kelvin, meters_per_second, pascals}, }; #[derive(Debug)] struct Primitive { - pressure: Pressure, - velocity: [Velocity; 3], - temperature: Temperature, + pressure: Pressure, + velocity: [Velocity; 3], + temperature: Temperature, } impl Primitive { fn ntp() -> Self { Self { - pressure: Pressure::pascals(100e3), - velocity: [Velocity::meters_per_second(100.0); 3], - temperature: Temperature::kelvin(293.15), + pressure: pascals.new(100e3), + velocity: [meters_per_second.new(100.0); 3], + temperature: kelvin.new(293.15), } } } #[derive(Debug)] struct Conservative { - density: MassDensity, - momentum: [MomentumDensity; 3], - energy: EnergyDensity, + density: MassDensity, + momentum: [MomentumDensity; 3], + energy: EnergyDensity, } #[derive(Debug)] struct IdealGas { #[allow(dead_code)] - cp: SpecificHeatCapacity, - cv: SpecificHeatCapacity, - r: SpecificHeatCapacity, - gamma: Dimensionless, + cp: SpecificHeatCapacity, + cv: SpecificHeatCapacity, + r: SpecificHeatCapacity, + gamma: Dimensionless, } impl IdealGas { fn air() -> Self { - let cp = SpecificHeatCapacity::joules_per_kilogram_kelvin(1004.); - let cv = SpecificHeatCapacity::joules_per_kilogram_kelvin(717.); + let cp = joules_per_kilogram_kelvin.new(1004.); + let cv = joules_per_kilogram_kelvin.new(717.); Self { cp, cv, @@ -54,13 +57,13 @@ impl IdealGas { impl From<&Primitive> for Conservative { fn from(s: &Primitive) -> Self { let gas = IdealGas::air(); - let density: MassDensity = s.pressure / (gas.r * s.temperature); - let energy_internal: SpecificEnergy = gas.cv * s.temperature; + let density: MassDensity = s.pressure / (gas.r * s.temperature); + let energy_internal: SpecificEnergy = gas.cv * s.temperature; let energy_kinetic = 0.5 * s.velocity .iter() .map(|v| v.powi::<2>()) - .sum::(); + .sum::>(); Self { density, momentum: s.velocity.map(|v| density * v), @@ -77,7 +80,7 @@ impl From<&Conservative> for Primitive { * velocity .iter() .map(|v| v.powi::<2>()) - .sum::(); + .sum::>(); let e_internal = c.energy / c.density - energy_kinetic; Self { pressure: (gas.gamma - 1.0) * c.density * e_internal, From a625fbbd0121f5a8f06abbe7fa892c56772f1590 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Mon, 22 Jan 2024 18:44:56 +0100 Subject: [PATCH 17/40] revive glam tests --- .../src/codegen/storage_types.rs | 3 + .../src/codegen/unit_type.rs | 105 ++++++++-------- tests/glam/mod.rs | 115 ++++++++---------- 3 files changed, 104 insertions(+), 119 deletions(-) diff --git a/crates/diman_unit_system/src/codegen/storage_types.rs b/crates/diman_unit_system/src/codegen/storage_types.rs index 1e30394..5a7b426 100644 --- a/crates/diman_unit_system/src/codegen/storage_types.rs +++ b/crates/diman_unit_system/src/codegen/storage_types.rs @@ -15,6 +15,7 @@ pub struct VectorType { pub struct FloatType { pub name: Type, pub module_name: TokenStream, + pub conversion_method: TokenStream, #[cfg(feature = "mpi")] pub mpi_type: TokenStream, #[cfg(feature = "hdf5")] @@ -131,6 +132,7 @@ impl Codegen { FloatType { name: f32_ty, module_name: quote! { f32 }, + conversion_method: quote! { into_f32 }, #[cfg(feature = "mpi")] mpi_type: quote! { ::mpi::ffi::RSMPI_FLOAT }, #[cfg(feature = "hdf5")] @@ -146,6 +148,7 @@ impl Codegen { FloatType { name: f64_ty, module_name: quote! { f64 }, + conversion_method: quote! { into_f64 }, #[cfg(feature = "mpi")] mpi_type: quote! { ::mpi::ffi::RSMPI_DOUBLE }, #[cfg(feature = "hdf5")] diff --git a/crates/diman_unit_system/src/codegen/unit_type.rs b/crates/diman_unit_system/src/codegen/unit_type.rs index 810dcdb..b4250de 100644 --- a/crates/diman_unit_system/src/codegen/unit_type.rs +++ b/crates/diman_unit_system/src/codegen/unit_type.rs @@ -1,5 +1,6 @@ use proc_macro2::TokenStream; use quote::quote; +use syn::Type; use super::Codegen; @@ -7,9 +8,11 @@ impl Codegen { pub fn gen_unit_type(&self) -> TokenStream { let dimension_type = &self.defs.dimension_type; let trait_impls = self.gen_unit_trait_impls(); + let storage_type_impls = self.gen_unit_trait_impls_for_storage_types(); quote! { pub struct Unit; #trait_impls + #storage_type_impls } } @@ -94,78 +97,68 @@ impl Codegen { } } - // f64 * Unit - impl Mul> for f64 { - type Output = Quantity; - fn mul(self, _: Unit) -> Self::Output { - Quantity(self * F.into_f64()) - } - } - - // f64 / Unit - impl Div> for f64 { - type Output = Quantity; - fn div(self, _: Unit) -> Self::Output { - Quantity(self / F.into_f64()) - } - } - - // Unit * f64 - impl Mul for Unit { - type Output = Quantity; - fn mul(self, f: f64) -> Self::Output { - Quantity(F.into_f64() * f) + impl Unit { + pub fn new(self, val: S) -> Quantity + where + S: Mul, + { + Quantity(val * F) } } + } + } - // Unit / f64 - impl Div for Unit { - type Output = Quantity; - fn div(self, f: f64) -> Self::Output { - Quantity(F.into_f64()) - } - } + fn gen_unit_trait_impls_for_storage_types(&self) -> TokenStream { + self.storage_types() + .map(|ty| { + let name = &ty.name(); + let conversion_method = &ty.base_storage().conversion_method; + self.gen_unit_numeric_traits_impls_for_type(name, conversion_method) + }) + .collect() + } - // f32 * Unit - impl Mul> for f32 { - type Output = Quantity; + fn gen_unit_numeric_traits_impls_for_type( + &self, + name: &Type, + conversion_to_float: &TokenStream, + ) -> TokenStream { + let into = quote! { + F.#conversion_to_float() + }; + let res = quote! { + // X * Unit + impl Mul> for #name { + type Output = Quantity<#name, D>; fn mul(self, _: Unit) -> Self::Output { - Quantity(self * F.into_f32()) + Quantity(self * #into) } } - // f32 / Unit - impl Div> for f32 { - type Output = Quantity; + // X / Unit + impl Div> for #name { + type Output = Quantity<#name, D>; fn div(self, _: Unit) -> Self::Output { - Quantity(self / F.into_f32()) - } - } - - // Unit * f32 - impl Mul for Unit { - type Output = Quantity; - fn mul(self, f: f32) -> Self::Output { - Quantity(F.into_f32() * f) + Quantity(self / #into) } } - // Unit / f32 - impl Div for Unit { - type Output = Quantity; - fn div(self, f: f32) -> Self::Output { - Quantity(F.into_f32() / f) + // Unit * X + impl Mul<#name> for Unit { + type Output = Quantity<#name, D>; + fn mul(self, f: #name) -> Self::Output { + Quantity(#into * f) } } - impl Unit { - pub fn new(self, val: S) -> Quantity - where - S: Mul, - { - Quantity(val * F) + // Unit / X + impl Div<#name> for Unit { + type Output = Quantity<#name, D>; + fn div(self, f: #name) -> Self::Output { + Quantity(#into / f) } } - } + }; + res } } diff --git a/tests/glam/mod.rs b/tests/glam/mod.rs index 84293dd..a1f7e27 100644 --- a/tests/glam/mod.rs +++ b/tests/glam/mod.rs @@ -2,42 +2,47 @@ macro_rules! gen_tests_for_vector_2 { ($float_name: ident, $mod_name: ident, $vec_name: ty, $assert_is_close: path) => { mod $mod_name { - use crate::example_system::$float_name::Length; - use crate::example_system::$float_name::Time; - use crate::example_system::$mod_name::Length as VecLength; - use crate::example_system::$mod_name::Velocity as VecVelocity; + use crate::example_system::dimensions::{Length, Time}; + use crate::example_system::units::{self, meters_per_second}; + use crate::make_annotated_unit_constructor; use $assert_is_close as assert_is_close; - use $vec_name as Vec; + make_annotated_unit_constructor!(meters, Length<$float_name>, $float_name); + make_annotated_unit_constructor!(seconds, Time<$float_name>, $float_name); + + use $vec_name as Vec; #[test] fn debug_vector_2() { - assert_eq!(format!("{:?}", VecLength::meters(1.0, 5.0)), "[1, 5] m"); + assert_eq!( + format!("{:?}", Vec::new(1.0, 5.0) * meters(1.0)), + "[1, 5] m" + ); } #[test] fn mul_vec2() { - let multiplied = Vec::new(1.0, 2.0) * Length::meters(5.0); - assert_is_close(multiplied.x(), Length::meters(5.0)); - assert_is_close(multiplied.y(), Length::meters(10.0)); - let multiplied = Length::meters(5.0) * Vec::new(1.0, 2.0); - assert_is_close(multiplied.x(), Length::meters(5.0)); - assert_is_close(multiplied.y(), Length::meters(10.0)); + let multiplied = Vec::new(1.0, 2.0) * meters(5.0); + assert_is_close(multiplied.x(), meters(5.0)); + assert_is_close(multiplied.y(), meters(10.0)); + let multiplied: Length = meters(5.0) * Vec::new(1.0, 2.0); + assert_is_close(multiplied.x(), meters(5.0)); + assert_is_close(multiplied.y(), meters(10.0)); } #[test] fn mul_quantity_vec2() { - let multiplied = VecVelocity::meters_per_second(1.0, 2.0) * Time::seconds(5.0); - assert_is_close(multiplied.x(), Length::meters(5.0)); - assert_is_close(multiplied.y(), Length::meters(10.0)); - let multiplied = Time::seconds(5.0) * VecVelocity::meters_per_second(1.0, 2.0); - assert_is_close(multiplied.x(), Length::meters(5.0)); - assert_is_close(multiplied.y(), Length::meters(10.0)); + let multiplied = (Vec::new(1.0, 2.0) * meters_per_second) * seconds(5.0); + assert_is_close(multiplied.x(), meters(5.0)); + assert_is_close(multiplied.y(), meters(10.0)); + let multiplied = seconds(5.0) * (Vec::new(1.0, 2.0) * meters_per_second); + assert_is_close(multiplied.x(), meters(5.0)); + assert_is_close(multiplied.y(), meters(10.0)); } #[test] fn div_vec2() { - let divided = Vec::new(1.0, 2.0) / Length::meters(0.2); - let base = 1.0 / Length::meters(1.0); + let divided = Vec::new(1.0, 2.0) / meters(0.2); + let base = 1.0 / meters(1.0); assert_is_close(divided.x(), 5.0 * base); assert_is_close(divided.y(), 10.0 * base); } @@ -49,67 +54,51 @@ macro_rules! gen_tests_for_vector_2 { macro_rules! gen_tests_for_vector_3 { ($float_name: ident, $mod_name: ident, $vec_name: ty, $assert_is_close: path) => { mod $mod_name { - use crate::example_system::$float_name::Length; - use crate::example_system::$float_name::Time; - use crate::example_system::$mod_name::Length as VecLength; - use crate::example_system::$mod_name::Velocity as VecVelocity; + use crate::example_system::dimensions::{Length, Time}; + use crate::example_system::units::{self, meters_per_second}; + use crate::make_annotated_unit_constructor; use $assert_is_close as assert_is_close; - use $vec_name as Vec; + make_annotated_unit_constructor!(meters, Length<$float_name>, $float_name); + make_annotated_unit_constructor!(seconds, Time<$float_name>, $float_name); + + use $vec_name as Vec; #[test] fn debug_vector_3() { assert_eq!( - format!("{:?}", VecLength::meters(1.0, 5.0, 6.0)), - "[1, 5, 6] m" + format!("{:?}", Vec::new(1.0, 5.0, 10.0) * meters(1.0)), + "[1, 5, 10] m" ); } #[test] fn mul_vec3() { - let multiplied = Vec::new(1.0, 2.0, 3.0) * Length::meters(5.0); - assert_is_close(multiplied.x(), Length::meters(5.0)); - assert_is_close(multiplied.y(), Length::meters(10.0)); - assert_is_close(multiplied.z(), Length::meters(15.0)); - let multiplied = Length::meters(5.0) * Vec::new(1.0, 2.0, 3.0); - assert_is_close(multiplied.x(), Length::meters(5.0)); - assert_is_close(multiplied.y(), Length::meters(10.0)); - assert_is_close(multiplied.z(), Length::meters(15.0)); + let multiplied = Vec::new(1.0, 2.0, 3.0) * meters(5.0); + assert_is_close(multiplied.x(), meters(5.0)); + assert_is_close(multiplied.y(), meters(10.0)); + assert_is_close(multiplied.z(), meters(15.0)); + let multiplied: Length = meters(5.0) * Vec::new(1.0, 2.0, 3.0); + assert_is_close(multiplied.x(), meters(5.0)); + assert_is_close(multiplied.y(), meters(10.0)); + assert_is_close(multiplied.z(), meters(15.0)); } - // #[test] - // fn mul_assign_vec3() { - // let mut vec = Vec3Length::meters(1.0, 2.0, 3.0); - // vec *= 3.0; - // assert_is_close(vec.x(), Length::meters(3.0)); - // assert_is_close(vec.y(), Length::meters(6.0)); - // assert_is_close(vec.z(), Length::meters(9.0)); - // } - - // #[test] - // fn div_assign_vec3() { - // let mut vec = Vec3Length::meters(1.0, 2.0, 3.0); - // vec /= 2.0; - // assert_is_close(vec.x(), Length::meters(0.5)); - // assert_is_close(vec.y(), Length::meters(1.0)); - // assert_is_close(vec.z(), Length::meters(1.5)); - // } - #[test] fn mul_quantity_vec3() { - let multiplied = VecVelocity::meters_per_second(1.0, 2.0, 3.0) * Time::seconds(5.0); - assert_is_close(multiplied.x(), Length::meters(5.0)); - assert_is_close(multiplied.y(), Length::meters(10.0)); - assert_is_close(multiplied.z(), Length::meters(15.0)); - let multiplied = Time::seconds(5.0) * VecVelocity::meters_per_second(1.0, 2.0, 3.0); - assert_is_close(multiplied.x(), Length::meters(5.0)); - assert_is_close(multiplied.y(), Length::meters(10.0)); - assert_is_close(multiplied.z(), Length::meters(15.0)); + let multiplied = (Vec::new(1.0, 2.0, 3.0) * meters_per_second) * seconds(5.0); + assert_is_close(multiplied.x(), meters(5.0)); + assert_is_close(multiplied.y(), meters(10.0)); + assert_is_close(multiplied.z(), meters(15.0)); + let multiplied = seconds(5.0) * (Vec::new(1.0, 2.0, 3.0) * meters_per_second); + assert_is_close(multiplied.x(), meters(5.0)); + assert_is_close(multiplied.y(), meters(10.0)); + assert_is_close(multiplied.z(), meters(15.0)); } #[test] fn div_vec3() { - let divided = Vec::new(1.0, 2.0, 3.0) / Length::meters(0.2); - let base = 1.0 / Length::meters(1.0); + let divided = Vec::new(1.0, 2.0, 3.0) / meters(0.2); + let base = 1.0 / meters(1.0); assert_is_close(divided.x(), 5.0 * base); assert_is_close(divided.y(), 10.0 * base); assert_is_close(divided.z(), 15.0 * base); From b56c8d257c8942a09c7c70c53afec57b1d549991 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Mon, 22 Jan 2024 18:49:34 +0100 Subject: [PATCH 18/40] revive mpi tests --- tests/mpi/mod.rs | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/tests/mpi/mod.rs b/tests/mpi/mod.rs index 9f8d77d..971aec2 100644 --- a/tests/mpi/mod.rs +++ b/tests/mpi/mod.rs @@ -13,14 +13,14 @@ lazy_static::lazy_static! { macro_rules! gen_tests_for_float { ($float_name: ident) => { mod $float_name { - use crate::example_system::$float_name::Length; + use crate::example_system::units::meters; use mpi::traits::Communicator; #[test] - fn pack_unpack_f64_quantity() { + fn pack_unpack_float_quantity() { let world = super::MPI_UNIVERSE.world(); - let q1 = Length::meters(1.0); - let mut q2 = Length::meters(2.0); + let q1 = 1.0 as $float_name * meters; + let mut q2 = 2.0 as $float_name * meters; let a = world.pack(&q1); unsafe { world.unpack_into(&a, &mut q2, 0); @@ -33,16 +33,17 @@ macro_rules! gen_tests_for_float { #[cfg(any(feature = "glam-vec2", feature = "glam-dvec2"))] macro_rules! gen_tests_for_vector_2 { - ($vec_mod_name: ident) => { + ($vec_mod_name: ident, $vec_name: ident) => { mod $vec_mod_name { - use crate::example_system::$vec_mod_name::Length as VecLength; + use crate::example_system::units::meters; + use glam::$vec_name; use mpi::topology::Communicator; #[test] fn pack_unpack_vec_quantity() { let world = super::MPI_UNIVERSE.world(); - let q1 = VecLength::meters(1.0, 2.0); - let mut q2 = VecLength::meters(3.0, 4.0); + let q1 = <$vec_name>::new(1.0, 2.0) * meters; + let mut q2 = <$vec_name>::new(3.0, 4.0) * meters; let a = world.pack(&q1); unsafe { world.unpack_into(&a, &mut q2, 0); @@ -55,16 +56,17 @@ macro_rules! gen_tests_for_vector_2 { #[cfg(any(feature = "glam-vec3", feature = "glam-dvec3"))] macro_rules! gen_tests_for_vector_3 { - ($vec_mod_name: ident) => { + ($vec_mod_name: ident, $vec_name: ident) => { mod $vec_mod_name { - use crate::example_system::$vec_mod_name::Length as VecLength; + use crate::example_system::units::meters; + use glam::$vec_name; use mpi::topology::Communicator; #[test] fn pack_unpack_vec_quantity() { let world = super::MPI_UNIVERSE.world(); - let q1 = VecLength::meters(1.0, 2.0, 3.0); - let mut q2 = VecLength::meters(3.0, 4.0, 5.0); + let q1 = <$vec_name>::new(1.0, 2.0, 3.0) * meters; + let mut q2 = <$vec_name>::new(4.0, 5.0, 6.0) * meters; let a = world.pack(&q1); unsafe { world.unpack_into(&a, &mut q2, 0); @@ -82,13 +84,13 @@ gen_tests_for_float!(f32); gen_tests_for_float!(f64); #[cfg(all(feature = "f32", feature = "glam-vec2"))] -gen_tests_for_vector_2!(vec2); +gen_tests_for_vector_2!(vec2, Vec2); #[cfg(all(feature = "f64", feature = "glam-dvec2"))] -gen_tests_for_vector_2!(dvec2); +gen_tests_for_vector_2!(dvec2, DVec2); #[cfg(all(feature = "f32", feature = "glam-vec3"))] -gen_tests_for_vector_3!(vec3); +gen_tests_for_vector_3!(vec3, Vec3); #[cfg(all(feature = "f64", feature = "glam-dvec3"))] -gen_tests_for_vector_3!(dvec3); +gen_tests_for_vector_3!(dvec3, DVec3); From 76034b50e01530e017be085e39d87061dd54634c Mon Sep 17 00:00:00 2001 From: tehforsch Date: Mon, 22 Jan 2024 19:31:59 +0100 Subject: [PATCH 19/40] revive serde tests --- crates/diman_lib/src/dimension_exponent.rs | 2 +- crates/diman_lib/src/magnitude.rs | 2 +- .../src/codegen/dimensions.rs | 1 + .../src/codegen/float_methods.rs | 2 +- crates/diman_unit_system/src/codegen/serde.rs | 4 +- .../src/codegen/units_and_constants.rs | 3 + tests/serde/mod.rs | 84 ++++++++++--------- 7 files changed, 54 insertions(+), 44 deletions(-) diff --git a/crates/diman_lib/src/dimension_exponent.rs b/crates/diman_lib/src/dimension_exponent.rs index 77a949f..47c91a1 100644 --- a/crates/diman_lib/src/dimension_exponent.rs +++ b/crates/diman_lib/src/dimension_exponent.rs @@ -1,4 +1,4 @@ -use std::ops::{AddAssign, Mul, Neg}; +use core::ops::{AddAssign, Mul, Neg}; use crate::magnitude::Magnitude; diff --git a/crates/diman_lib/src/magnitude.rs b/crates/diman_lib/src/magnitude.rs index 01bd337..489318c 100644 --- a/crates/diman_lib/src/magnitude.rs +++ b/crates/diman_lib/src/magnitude.rs @@ -1,4 +1,4 @@ -use std::{ +use core::{ marker::ConstParamTy, ops::{Div, Mul}, }; diff --git a/crates/diman_unit_system/src/codegen/dimensions.rs b/crates/diman_unit_system/src/codegen/dimensions.rs index 3f79626..9982a4c 100644 --- a/crates/diman_unit_system/src/codegen/dimensions.rs +++ b/crates/diman_unit_system/src/codegen/dimensions.rs @@ -30,6 +30,7 @@ impl Codegen { let quantity_type = &self.defs.quantity_type; let defs = self.gen_dimension_definitions(); quote! { + #[allow(unused)] pub mod dimensions { use super::#dimension_type; use super::#quantity_type; diff --git a/crates/diman_unit_system/src/codegen/float_methods.rs b/crates/diman_unit_system/src/codegen/float_methods.rs index b3d5a4f..4720ad4 100644 --- a/crates/diman_unit_system/src/codegen/float_methods.rs +++ b/crates/diman_unit_system/src/codegen/float_methods.rs @@ -7,7 +7,7 @@ impl Codegen { fn ensure_float_traits(&self) -> TokenStream { let path_prefix = match self.caller_type { CallerType::Internal => quote! { ::num_traits::float }, - CallerType::External => quote! { diman::internal::num_traits_reexport }, + CallerType::External => quote! { ::diman::internal::num_traits_reexport }, }; if cfg!(feature = "num-traits-libm") { quote! { diff --git a/crates/diman_unit_system/src/codegen/serde.rs b/crates/diman_unit_system/src/codegen/serde.rs index 67645b9..baff9a4 100644 --- a/crates/diman_unit_system/src/codegen/serde.rs +++ b/crates/diman_unit_system/src/codegen/serde.rs @@ -22,7 +22,7 @@ impl Codegen { let all_units_storage = self.runtime_unit_storage(self.defs.units.iter()); quote! { - use std::marker::PhantomData; + use core::marker::PhantomData; use std::str::SplitWhitespace; use serde::de::{self}; @@ -80,7 +80,7 @@ impl Codegen { .ok_or_else(|| E::custom(format!("unknown unit: {}", &unit)))?; Ok(( unit.dimension.clone().mul(exponent), - Exponent::float_pow(unit.magnitude, Exponent::from_int(exponent)), + Exponent::float_pow(Magnitude::from_f64(unit.magnitude), Exponent::from_int(exponent)).into_f64(), )) } } diff --git a/crates/diman_unit_system/src/codegen/units_and_constants.rs b/crates/diman_unit_system/src/codegen/units_and_constants.rs index 68b19e0..38adc96 100644 --- a/crates/diman_unit_system/src/codegen/units_and_constants.rs +++ b/crates/diman_unit_system/src/codegen/units_and_constants.rs @@ -39,14 +39,17 @@ impl Codegen { use super::Quantity; #def_unit_type } + #[allow(unused)] pub use unit_type::Unit; #[allow(non_upper_case_globals)] + #[allow(unused)] pub mod units { use super::Magnitude; use super::Unit; use super::Dimension; #units } + #[allow(unused)] pub mod constants { use super::Magnitude; use super::Unit; diff --git a/tests/serde/mod.rs b/tests/serde/mod.rs index 21c5ffa..6d5028a 100644 --- a/tests/serde/mod.rs +++ b/tests/serde/mod.rs @@ -2,44 +2,42 @@ macro_rules! gen_tests_for_float { ($float_name: ident, $assert_is_close: path) => { mod $float_name { - use crate::example_system::$float_name::Energy; - use crate::example_system::$float_name::Length; - use crate::example_system::$float_name::Time; - use crate::example_system::$float_name::Velocity; + use crate::example_system::dimensions::{Energy, Length, Time, Velocity}; + use crate::example_system::units; + use crate::make_annotated_unit_constructor; use $assert_is_close as assert_is_close; + make_annotated_unit_constructor!(meters, Length<$float_name>, $float_name); + make_annotated_unit_constructor!(kilometers, Length<$float_name>, $float_name); + make_annotated_unit_constructor!(seconds, Time<$float_name>, $float_name); + make_annotated_unit_constructor!(joules, Energy<$float_name>, $float_name); #[test] fn deserialize_float() { - let q: Length = serde_yaml::from_str("5.0 km").unwrap(); - assert_is_close(q, Length::kilometers(5.0)); - let q: Velocity = serde_yaml::from_str("5.0 km s^-1").unwrap(); - assert_is_close(q, Length::kilometers(5.0) / Time::seconds(1.0)); + let q: Length<$float_name> = serde_yaml::from_str("5.0 km").unwrap(); + assert_is_close(q, kilometers(5.0)); + let q: Velocity<$float_name> = serde_yaml::from_str("5.0 km s^-1").unwrap(); + assert_is_close(q, kilometers(5.0) / seconds(1.0)); } #[test] #[should_panic(expected = "mismatch in dimensions")] fn deserialize_float_dimension_mismatch() { - let q: Length = serde_yaml::from_str("5.0 kg").unwrap(); - assert_is_close(q, Length::kilometers(5.0)); + let q: Length<$float_name> = serde_yaml::from_str("5.0 kg").unwrap(); + assert_is_close(q, kilometers(5.0)); } #[test] fn serialize_float() { assert_eq!( - serde_yaml::to_string(&Length::kilometers(5.0)) - .unwrap() - .trim(), + serde_yaml::to_string(&kilometers(5.0)).unwrap().trim(), "5000 m" ); - assert_eq!( - serde_yaml::to_string(&Energy::joules(5.0)).unwrap().trim(), - "5 J" - ); + assert_eq!(serde_yaml::to_string(&joules(5.0)).unwrap().trim(), "5 J"); } #[test] fn serialize_float_unnamed_dimension() { - let unnamed_dimension = Energy::joules(5.0) * Length::meters(1.0); + let unnamed_dimension = joules(5.0) * meters(1.0); assert_eq!( serde_yaml::to_string(&unnamed_dimension).unwrap().trim(), "5 m^3 s^-2 kg" @@ -53,40 +51,44 @@ macro_rules! gen_tests_for_float { macro_rules! gen_tests_for_vector_2 { ($float_name: ident, $mod_name: ident, $vec_name: ty, $assert_is_close: path) => { mod $mod_name { - use crate::example_system::$float_name::Length; - use crate::example_system::$mod_name::Dimensionless as Vec2Dimensionless; - use crate::example_system::$mod_name::Length as Vec2Length; + use crate::example_system::dimensions::Length; + use crate::example_system::units; + use crate::example_system::units::{dimensionless, meters}; use $assert_is_close as assert_is_close; + use $vec_name as Vec2; + + use crate::make_annotated_unit_constructor; + make_annotated_unit_constructor!(kilometers, Length<$float_name>, $float_name); #[test] fn deserialize_vector() { - let q: Vec2Length = serde_yaml::from_str("(5.0 3.0) km").unwrap(); - assert_is_close(q.x(), Length::kilometers(5.0)); - assert_is_close(q.y(), Length::kilometers(3.0)); + let q: Length = serde_yaml::from_str("(5.0 3.0) km").unwrap(); + assert_is_close(q.x(), kilometers(5.0)); + assert_is_close(q.y(), kilometers(3.0)); } #[test] #[should_panic] fn deserialize_vector_fails_with_fewer_than_2_components() { - let _: Vec2Length = serde_yaml::from_str("(5.0) km").unwrap(); + let _: Length = serde_yaml::from_str("(5.0) km").unwrap(); } #[test] #[should_panic] fn deserialize_vector_fails_with_more_than_2_components() { - let _: Vec2Length = serde_yaml::from_str("(5.0 3.0 7.0) km").unwrap(); + let _: Length = serde_yaml::from_str("(5.0 3.0 7.0) km").unwrap(); } #[test] fn serialize_vector() { - let x = Vec2Length::meters(5.3, 1.1); + let x = meters * Vec2::new(5.3, 1.1); let result: String = serde_yaml::to_string(&x).unwrap(); assert_eq!(result, "(5.3 1.1) m\n"); } #[test] fn serialize_dimensionless_vector() { - let x = Vec2Dimensionless::dimensionless(5.3, 1.1); + let x = dimensionless * Vec2::new(5.3, 1.1); let result: String = serde_yaml::to_string(&x).unwrap(); assert_eq!(result, "(5.3 1.1)\n"); } @@ -98,41 +100,45 @@ macro_rules! gen_tests_for_vector_2 { macro_rules! gen_tests_for_vector_3 { ($float_name: ident, $mod_name: ident, $vec_name: ty, $assert_is_close: path) => { mod $mod_name { - use crate::example_system::$float_name::Length; - use crate::example_system::$mod_name::Dimensionless as Vec3Dimensionless; - use crate::example_system::$mod_name::Length as Vec3Length; + use crate::example_system::dimensions::Length; + use crate::example_system::units; + use crate::example_system::units::{dimensionless, meters}; use $assert_is_close as assert_is_close; + use $vec_name as Vec3; + + use crate::make_annotated_unit_constructor; + make_annotated_unit_constructor!(kilometers, Length<$float_name>, $float_name); #[test] fn deserialize_vector() { - let q: Vec3Length = serde_yaml::from_str("(5.0 3.0 7.0) km").unwrap(); - assert_is_close(q.x(), Length::kilometers(5.0)); - assert_is_close(q.y(), Length::kilometers(3.0)); - assert_is_close(q.z(), Length::kilometers(7.0)); + let q: Length = serde_yaml::from_str("(5.0 3.0 7.0) km").unwrap(); + assert_is_close(q.x(), kilometers(5.0)); + assert_is_close(q.y(), kilometers(3.0)); + assert_is_close(q.z(), kilometers(7.0)); } #[test] #[should_panic] fn deserialize_vector_fails_with_fewer_than_3_components() { - let _: Vec3Length = serde_yaml::from_str("(5.0 4.0) km").unwrap(); + let _: Length = serde_yaml::from_str("(5.0 4.0) km").unwrap(); } #[test] #[should_panic] fn deserialize_vector_fails_with_more_than_3_components() { - let _: Vec3Length = serde_yaml::from_str("(5.0 3.0 7.0 9.0) km").unwrap(); + let _: Length = serde_yaml::from_str("(5.0 3.0 7.0 9.0) km").unwrap(); } #[test] fn serialize_vector() { - let x = Vec3Length::meters(5.3, 1.1, 2.2); + let x = meters * Vec3::new(5.3, 1.1, 2.2); let result: String = serde_yaml::to_string(&x).unwrap(); assert_eq!(result, "(5.3 1.1 2.2) m\n"); } #[test] fn serialize_dimensionless_vector() { - let x = Vec3Dimensionless::dimensionless(5.3, 1.1, 2.2); + let x = dimensionless * Vec3::new(5.3, 1.1, 2.2); let result: String = serde_yaml::to_string(&x).unwrap(); assert_eq!(result, "(5.3 1.1 2.2)\n"); } From e3d21c77dabd8c2b82d8edfe668dd248849d655e Mon Sep 17 00:00:00 2001 From: tehforsch Date: Mon, 22 Jan 2024 19:49:00 +0100 Subject: [PATCH 20/40] revive rand tests --- tests/rand/mod.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/rand/mod.rs b/tests/rand/mod.rs index 31cc728..5f4a5b2 100644 --- a/tests/rand/mod.rs +++ b/tests/rand/mod.rs @@ -3,15 +3,20 @@ macro_rules! gen_tests_for_float { mod $float_name { use rand::Rng; - use crate::example_system::$float_name::Length; + use crate::example_system::dimensions::Length; + use crate::example_system::units; + use crate::make_annotated_unit_constructor; + + make_annotated_unit_constructor!(meters, Length<$float_name>, $float_name); + make_annotated_unit_constructor!(kilometers, Length<$float_name>, $float_name); #[test] fn test_random_quantity_generation() { let mut rng = rand::thread_rng(); for _ in 0..100 { - let x = rng.gen_range(Length::meters(0.0)..Length::kilometers(1.0)); - assert!(Length::meters(0.0) <= x); - assert!(x < Length::meters(1000.0)); + let x = rng.gen_range(meters(0.0)..kilometers(1.0)); + assert!(meters(0.0) <= x); + assert!(x < meters(1000.0)); } } } From 19c70864a0719c40e688dad64ab9439287ab653e Mon Sep 17 00:00:00 2001 From: tehforsch Date: Mon, 22 Jan 2024 19:56:05 +0100 Subject: [PATCH 21/40] revive rational dimension tests --- .../src/codegen/units_and_constants.rs | 2 ++ tests/rational_dimensions/mod.rs | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/diman_unit_system/src/codegen/units_and_constants.rs b/crates/diman_unit_system/src/codegen/units_and_constants.rs index 38adc96..f3c098e 100644 --- a/crates/diman_unit_system/src/codegen/units_and_constants.rs +++ b/crates/diman_unit_system/src/codegen/units_and_constants.rs @@ -47,6 +47,7 @@ impl Codegen { use super::Magnitude; use super::Unit; use super::Dimension; + use super::Exponent; #units } #[allow(unused)] @@ -54,6 +55,7 @@ impl Codegen { use super::Magnitude; use super::Unit; use super::Dimension; + use super::Exponent; #constants } } diff --git a/tests/rational_dimensions/mod.rs b/tests/rational_dimensions/mod.rs index 7e2c7b8..daa9a28 100644 --- a/tests/rational_dimensions/mod.rs +++ b/tests/rational_dimensions/mod.rs @@ -21,16 +21,25 @@ diman::unit_system!( macro_rules! gen_tests_for_float { ($mod_name: ident, $float_name: ident, $assert_is_close: path) => { mod $mod_name { + use super::dimensions::{Length, Sorptivity, Time}; + use super::units; + use crate::make_annotated_unit_constructor; + make_annotated_unit_constructor!(micrometers, Length<$float_name>, $float_name); + make_annotated_unit_constructor!(milliseconds, Time<$float_name>, $float_name); + make_annotated_unit_constructor!( + meters_per_sqrt_second, + Sorptivity<$float_name>, + $float_name + ); #[test] fn rational_dimensions_allowed() { - use super::$float_name::{Length, Sorptivity, Time}; - let l = Length::micrometers(2.0); - let t = Time::milliseconds(5.0); - let sorptivity: Sorptivity = l / t.sqrt(); + let l = micrometers(2.0); + let t = milliseconds(5.0); + let sorptivity: Sorptivity<$float_name> = l / t.sqrt(); let val = l.value_unchecked() / t.value_unchecked().sqrt(); $assert_is_close( sorptivity.value_unchecked(), - Sorptivity::meters_per_sqrt_second(val).value_unchecked(), + meters_per_sqrt_second(val).value_unchecked(), ); } } From 3746e69ed9e288ac8bd7b57d32f8265214d178dc Mon Sep 17 00:00:00 2001 From: tehforsch Date: Mon, 22 Jan 2024 20:09:04 +0100 Subject: [PATCH 22/40] revive compile_fail tests --- ...t_unknown_method_on_quantity_type_alias.rs | 2 +- ...known_method_on_quantity_type_alias.stderr | 27 +++++++++++-------- .../resolver_partial_resolution.rs | 4 +-- .../type_mismatch_add_type_quantity.rs | 4 +-- .../type_mismatch_add_type_quantity.stderr | 14 +++++----- .../compile_fail/type_mismatch_dimension.rs | 5 ++-- .../type_mismatch_dimension.stderr | 6 ++--- .../type_mismatch_div_quantity.rs | 4 +-- .../type_mismatch_div_quantity.stderr | 4 +-- .../type_mismatch_div_quantity_type.rs | 4 +-- .../type_mismatch_div_quantity_type.stderr | 14 +++++----- .../type_mismatch_div_type_quantity.rs | 4 +-- .../type_mismatch_div_type_quantity.stderr | 14 +++++----- .../type_mismatch_function_call.rs | 4 +-- .../type_mismatch_function_call.stderr | 4 +-- .../type_mismatch_mul_quantity.rs | 4 +-- .../type_mismatch_mul_quantity.stderr | 6 ++--- .../type_mismatch_mul_quantity_type.rs | 4 +-- .../type_mismatch_mul_quantity_type.stderr | 14 +++++----- .../type_mismatch_mul_type_quantity.rs | 4 +-- .../type_mismatch_mul_type_quantity.stderr | 14 +++++----- 21 files changed, 78 insertions(+), 82 deletions(-) diff --git a/crates/diman_unit_system/tests/compile_fail/float_unknown_method_on_quantity_type_alias.rs b/crates/diman_unit_system/tests/compile_fail/float_unknown_method_on_quantity_type_alias.rs index 58ce085..c5a2dbe 100644 --- a/crates/diman_unit_system/tests/compile_fail/float_unknown_method_on_quantity_type_alias.rs +++ b/crates/diman_unit_system/tests/compile_fail/float_unknown_method_on_quantity_type_alias.rs @@ -3,6 +3,6 @@ pub mod example_system; fn main() { - use example_system::f64::Length; + use example_system::dimensions::Length; Length::unknown_method(49.0); } diff --git a/crates/diman_unit_system/tests/compile_fail/float_unknown_method_on_quantity_type_alias.stderr b/crates/diman_unit_system/tests/compile_fail/float_unknown_method_on_quantity_type_alias.stderr index 8ab08ca..7590d5d 100644 --- a/crates/diman_unit_system/tests/compile_fail/float_unknown_method_on_quantity_type_alias.stderr +++ b/crates/diman_unit_system/tests/compile_fail/float_unknown_method_on_quantity_type_alias.stderr @@ -2,20 +2,25 @@ error[E0599]: no function or associated item named `unknown_method` found for st --> tests/compile_fail/float_unknown_method_on_quantity_type_alias.rs:7:13 | 7 | Length::unknown_method(49.0); - | ^^^^^^^^^^^^^^ function or associated item not found in `Quantity` + | ^^^^^^^^^^^^^^ function or associated item not found in `Quantity<_, Dimension>` | ::: tests/compile_fail/example_system/mod.rs | | quantity_type Quantity; | -------- function or associated item `unknown_method` not found for this struct | -note: if you're trying to build a new `Quantity` consider using one of the following associated functions: - Quantity::::dimensionless - Quantity::::grams - Quantity::::joules - Quantity::::kilograms - and $N others - --> tests/compile_fail/example_system/mod.rs - | - | dimension_type Dimension; - | ^^^^^^^^^ +note: if you're trying to build a new `Quantity<_, Dimension>` consider using one of the following associated functions: + Quantity::::new_unchecked + Quantity::::zero + Quantity::::zero + --> tests/compile_fail/example_system/mod.rs + | + | / unit_system_internal!( + | | quantity_type Quantity; + | | dimension_type Dimension; + | | dimension Length; +... | + | | constant SOLAR_MASS_AWKWARD = 1.988477e30 * kilograms / (seconds / seconds); + | | ); + | |_^ + = note: this error originates in the macro `unit_system_internal` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/diman_unit_system/tests/compile_fail/resolver_partial_resolution.rs b/crates/diman_unit_system/tests/compile_fail/resolver_partial_resolution.rs index 4791e59..b8ee0f4 100644 --- a/crates/diman_unit_system/tests/compile_fail/resolver_partial_resolution.rs +++ b/crates/diman_unit_system/tests/compile_fail/resolver_partial_resolution.rs @@ -16,6 +16,6 @@ unit_system_internal!( ); fn main() { - use crate::f64::*; - let l = Length::meters(1.0); + use crate::units::meters; + let l = 1.0 * meters; } diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_add_type_quantity.rs b/crates/diman_unit_system/tests/compile_fail/type_mismatch_add_type_quantity.rs index 7dd8c16..66835e8 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_add_type_quantity.rs +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_add_type_quantity.rs @@ -1,8 +1,8 @@ #![allow(incomplete_features)] #![feature(generic_const_exprs, adt_const_params)] pub mod example_system; -use example_system::f64::*; +use example_system::units::dimensionless; fn main() { - let x: () = 1.0 + Dimensionless::dimensionless(1.0); + let x: () = 1.0 + dimensionless.new(1.0); } diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_add_type_quantity.stderr b/crates/diman_unit_system/tests/compile_fail/type_mismatch_add_type_quantity.stderr index bb3fb98..3d912a1 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_add_type_quantity.stderr +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_add_type_quantity.stderr @@ -1,10 +1,8 @@ -error[E0308]: mismatched types - --> tests/compile_fail/type_mismatch_add_type_quantity.rs:7:17 +error[E0271]: type mismatch resolving `>>::Output == ()` + --> tests/compile_fail/type_mismatch_add_type_quantity.rs:7:21 | -7 | let x: () = 1.0 + Dimensionless::dimensionless(1.0); - | -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `Quantity` - | | - | expected due to this +7 | let x: () = 1.0 + dimensionless.new(1.0); + | ^ expected `Quantity`, found `()` | - = note: expected unit type `()` - found struct `Quantity` + = note: expected struct `Quantity` + found unit type `()` diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_dimension.rs b/crates/diman_unit_system/tests/compile_fail/type_mismatch_dimension.rs index 57a773a..c82e137 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_dimension.rs +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_dimension.rs @@ -1,8 +1,9 @@ #![allow(incomplete_features)] #![feature(generic_const_exprs, adt_const_params)] pub mod example_system; -use example_system::f64::*; +use example_system::units::meters; +use example_system::dimensions::Time; fn main() { - let x: Time = Length::meters(1.0); + let x: Time = meters.new(1.0); } diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_dimension.stderr b/crates/diman_unit_system/tests/compile_fail/type_mismatch_dimension.stderr index 528a028..2ae278d 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_dimension.stderr +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_dimension.stderr @@ -1,8 +1,8 @@ error[E0308]: mismatched types - --> tests/compile_fail/type_mismatch_dimension.rs:7:19 + --> tests/compile_fail/type_mismatch_dimension.rs:8:24 | -7 | let x: Time = Length::meters(1.0); - | ^^^^^^^^^^^^^^^^^^^ expected `Dimension { length: 0, time: 1, mass: 0, temperature: 0 }`, found `Dimension { length: 1, time: 0, mass: 0, temperature: 0 }` +8 | let x: Time = meters.new(1.0); + | ^^^^^^^^^^^^^^^ expected `Dimension { length: 0, time: 1, mass: 0, temperature: 0 }`, found `Dimension { length: 1, time: 0, mass: 0, temperature: 0 }` | = note: expected constant `Dimension { length: 0, time: 1, mass: 0, temperature: 0 }` found constant `Dimension { length: 1, time: 0, mass: 0, temperature: 0 }` diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_quantity.rs b/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_quantity.rs index 49f46a5..d8516c6 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_quantity.rs +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_quantity.rs @@ -1,8 +1,8 @@ #![allow(incomplete_features)] #![feature(generic_const_exprs, adt_const_params)] pub mod example_system; -use example_system::f64::*; +use example_system::units::meters; fn main() { - let x: () = Length::meters(1.0) / Length::meters(1.0); + let x: () = meters.new(1.0f64) / meters.new(1.0f64); } diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_quantity.stderr b/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_quantity.stderr index b65eb1a..cd0403c 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_quantity.stderr +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_quantity.stderr @@ -1,8 +1,8 @@ error[E0308]: mismatched types --> tests/compile_fail/type_mismatch_div_quantity.rs:7:17 | -7 | let x: () = Length::meters(1.0) / Length::meters(1.0); - | -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `Quantity` +7 | let x: () = meters.new(1.0f64) / meters.new(1.0f64); + | -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `Quantity` | | | expected due to this | diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_quantity_type.rs b/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_quantity_type.rs index 1b1b35a..2b1e4fd 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_quantity_type.rs +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_quantity_type.rs @@ -1,8 +1,8 @@ #![allow(incomplete_features)] #![feature(generic_const_exprs, adt_const_params)] pub mod example_system; -use example_system::f64::*; +use example_system::units::dimensionless; fn main() { - let x: () = 1.0 / Dimensionless::dimensionless(1.0); + let x: () = 1.0 / dimensionless.new(1.0); } diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_quantity_type.stderr b/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_quantity_type.stderr index 60e68c8..a7b2e76 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_quantity_type.stderr +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_quantity_type.stderr @@ -1,10 +1,8 @@ -error[E0308]: mismatched types - --> tests/compile_fail/type_mismatch_div_quantity_type.rs:7:17 +error[E0271]: type mismatch resolving `>>::Output == ()` + --> tests/compile_fail/type_mismatch_div_quantity_type.rs:7:21 | -7 | let x: () = 1.0 / Dimensionless::dimensionless(1.0); - | -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `Quantity` - | | - | expected due to this +7 | let x: () = 1.0 / dimensionless.new(1.0); + | ^ expected `Quantity`, found `()` | - = note: expected unit type `()` - found struct `Quantity` + = note: expected struct `Quantity` + found unit type `()` diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_type_quantity.rs b/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_type_quantity.rs index a2efe5a..6a65564 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_type_quantity.rs +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_type_quantity.rs @@ -1,8 +1,8 @@ #![allow(incomplete_features)] #![feature(generic_const_exprs, adt_const_params)] pub mod example_system; -use example_system::f64::*; +use example_system::units::dimensionless; fn main() { - let x: () = Dimensionless::dimensionless(1.0) / 1.0; + let x: () = dimensionless.new(1.0) / 1.0; } diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_type_quantity.stderr b/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_type_quantity.stderr index 549e086..a960eb1 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_type_quantity.stderr +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_div_type_quantity.stderr @@ -1,10 +1,8 @@ -error[E0308]: mismatched types - --> tests/compile_fail/type_mismatch_div_type_quantity.rs:7:17 +error[E0271]: type mismatch resolving ` as Div>::Output == ()` + --> tests/compile_fail/type_mismatch_div_type_quantity.rs:7:40 | -7 | let x: () = Dimensionless::dimensionless(1.0) / 1.0; - | -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `Quantity` - | | - | expected due to this +7 | let x: () = dimensionless.new(1.0) / 1.0; + | ^ expected `Quantity`, found `()` | - = note: expected unit type `()` - found struct `Quantity` + = note: expected struct `Quantity` + found unit type `()` diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_function_call.rs b/crates/diman_unit_system/tests/compile_fail/type_mismatch_function_call.rs index 2a3a87b..ed18699 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_function_call.rs +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_function_call.rs @@ -1,8 +1,8 @@ #![allow(incomplete_features)] #![feature(generic_const_exprs, adt_const_params)] pub mod example_system; -use example_system::f64::*; +use example_system::units::meters; fn main() { - let x: () = Length::meters(1.0).abs(); + let x: () = meters.new(1.0f64).abs(); } diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_function_call.stderr b/crates/diman_unit_system/tests/compile_fail/type_mismatch_function_call.stderr index 22f00b3..7b559af 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_function_call.stderr +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_function_call.stderr @@ -1,8 +1,8 @@ error[E0308]: mismatched types --> tests/compile_fail/type_mismatch_function_call.rs:7:17 | -7 | let x: () = Length::meters(1.0).abs(); - | -- ^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `Quantity` +7 | let x: () = meters.new(1.0f64).abs(); + | -- ^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `Quantity` | | | expected due to this | diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_quantity.rs b/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_quantity.rs index 52f30ae..bf0b5ae 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_quantity.rs +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_quantity.rs @@ -1,8 +1,8 @@ #![allow(incomplete_features)] #![feature(generic_const_exprs, adt_const_params)] pub mod example_system; -use example_system::f64::*; +use example_system::units::*; fn main() { - let x: () = Length::meters(1.0) * Length::meters(1.0); + let x: () = meters.new(1.0) * meters.new(1.0); } diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_quantity.stderr b/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_quantity.stderr index 5681f95..4dfa085 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_quantity.stderr +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_quantity.stderr @@ -1,10 +1,10 @@ error[E0308]: mismatched types --> tests/compile_fail/type_mismatch_mul_quantity.rs:7:17 | -7 | let x: () = Length::meters(1.0) * Length::meters(1.0); - | -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `Quantity` +7 | let x: () = meters.new(1.0) * meters.new(1.0); + | -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `Quantity<_, Dimension>` | | | expected due to this | = note: expected unit type `()` - found struct `Quantity` + found struct `Quantity<_, Dimension>` diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_quantity_type.rs b/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_quantity_type.rs index 6a44933..ca5c939 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_quantity_type.rs +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_quantity_type.rs @@ -1,8 +1,8 @@ #![allow(incomplete_features)] #![feature(generic_const_exprs, adt_const_params)] pub mod example_system; -use example_system::f64::*; +use example_system::units::dimensionless; fn main() { - let x: () = 1.0 * Dimensionless::dimensionless(1.0); + let x: () = 1.0 * dimensionless.new(1.0); } diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_quantity_type.stderr b/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_quantity_type.stderr index a914e46..938f813 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_quantity_type.stderr +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_quantity_type.stderr @@ -1,10 +1,8 @@ -error[E0308]: mismatched types - --> tests/compile_fail/type_mismatch_mul_quantity_type.rs:7:17 +error[E0271]: type mismatch resolving `>>::Output == ()` + --> tests/compile_fail/type_mismatch_mul_quantity_type.rs:7:21 | -7 | let x: () = 1.0 * Dimensionless::dimensionless(1.0); - | -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `Quantity` - | | - | expected due to this +7 | let x: () = 1.0 * dimensionless.new(1.0); + | ^ expected `Quantity`, found `()` | - = note: expected unit type `()` - found struct `Quantity` + = note: expected struct `Quantity` + found unit type `()` diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_type_quantity.rs b/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_type_quantity.rs index 81dca32..cc16688 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_type_quantity.rs +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_type_quantity.rs @@ -1,8 +1,8 @@ #![allow(incomplete_features)] #![feature(generic_const_exprs, adt_const_params)] pub mod example_system; -use example_system::f64::*; +use example_system::units::dimensionless; fn main() { - let x: () = Dimensionless::dimensionless(1.0) * 1.0; + let x: () = dimensionless.new(1.0) * 1.0; } diff --git a/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_type_quantity.stderr b/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_type_quantity.stderr index ba75c39..4d01b21 100644 --- a/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_type_quantity.stderr +++ b/crates/diman_unit_system/tests/compile_fail/type_mismatch_mul_type_quantity.stderr @@ -1,10 +1,8 @@ -error[E0308]: mismatched types - --> tests/compile_fail/type_mismatch_mul_type_quantity.rs:7:17 +error[E0271]: type mismatch resolving ` as Mul>::Output == ()` + --> tests/compile_fail/type_mismatch_mul_type_quantity.rs:7:40 | -7 | let x: () = Dimensionless::dimensionless(1.0) * 1.0; - | -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `Quantity` - | | - | expected due to this +7 | let x: () = dimensionless.new(1.0) * 1.0; + | ^ expected `Quantity`, found `()` | - = note: expected unit type `()` - found struct `Quantity` + = note: expected struct `Quantity` + found unit type `()` From 1710656d4cd56915abbe794a711e920eab9df721 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Mon, 22 Jan 2024 21:14:24 +0100 Subject: [PATCH 23/40] update doctests and readme --- README.md | 143 ++++++++++++++------------ src/lib.rs | 291 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 363 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index d09cff7..c0e9b36 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,26 @@ -# Diman + + Diman is a library for zero-cost compile time unit checking. ```rust -# #![feature(generic_const_exprs)] -use diman::si::f64::{Length, Time, Velocity}; +use diman::si::dimensions::{Length, Time, Velocity}; +use diman::si::units::{seconds, meters, kilometers, hours}; -fn get_velocity(x: Length, t: Time) -> Velocity { +fn get_velocity(x: Length, t: Time) -> Velocity { x / t } -let v1 = get_velocity(Length::kilometers(36.0), Time::hours(1.0)); -let v2 = get_velocity(Length::meters(10.0), Time::seconds(1.0)); +let v1 = get_velocity(36.0 * kilometers, 1.0 * hours); +let v2 = get_velocity(10.0 * meters, 1.0 * seconds); assert_eq!(v1, v2); ``` Let's try to assign add quantities with incompatible dimensions: ```rust compile_fail -use diman::si::f64::{Length, Time}; - -let time = Time::seconds(1.0); -let length = Length::meters(10.0); +# use diman::si::units::{seconds, meters}; +let time = 1.0 * seconds; +let length = 10.0 * meters; let sum = length + time; ``` This results in a compiler error: @@ -32,14 +32,14 @@ let sum = length + time; ``` -## Disclaimer -Diman is implemented using Rust's const generics feature. While `min_const_generics` has been stabilized since Rust 1.51, Diman uses more complex generic expressions and therefore requires the two currently unstable features `generic_const_exprs` and `adt_const_params`. +# Disclaimer +Diman is implemented using Rust's const generics feature. While `min_const_generics` has been stabilized since Rust 1.51, Diman uses more complex generic expressions and therefore requires the two currently unstable features `generic_const_exprs` and `adt_const_params`. Moreover, Diman is in its early stages of development and APIs will change. If you cannot use unstable Rust for your project or require a stable library, consider using [`uom`](https://crates.io/crates/uom) or [`dimensioned`](https://crates.io/crates/dimensioned), both of which do not require any experimental features and are much more mature libraries in general. -## Features +# Features * Invalid operations between physical quantities (adding length and time, for example) turn into compile errors. * Newly created quantities are automatically converted to an underlying base representation. This means that the used types are dimensions (such as `Length`) instead of concrete units (such as `meters`) which makes for more meaningful code. * Systems of dimensions and units can be user defined via the `unit_system!` macro. This gives the user complete freedom over the choice of dimensions and makes them part of the user's library, so that arbitrary new methods can be implemented on them. @@ -53,14 +53,10 @@ If you cannot use unstable Rust for your project or require a stable library, co * Quantities implement the `Equivalence` trait so that they can be sent via MPI using [`mpi`](https://crates.io/crates/mpi) (behind the `mpi` feature gate). * Random quantities can be generated via [`rand`](https://crates.io/crates/rand) (behind the `rand` feature gate, see the official documentation for more info). -## Design +# Design Diman aims to make it as easy as possible to add compile-time unit safety to Rust code. Physical quantities are represented by the `Quantity` struct, where `S` is the underlying storage type (`f32`, `f64`, ...) and `D` is the dimension of the quantity. For example, in order to represent the [SI system of units](https://www.nist.gov/pml/owm/metric-si/si-units), the quantity type would be defined using the `unit_system!` macro as follows: -```rust ignore -#![allow(incomplete_features)] -#![feature(generic_const_exprs, adt_const_params)] -use diman::unit_system; - -unit_system!( +```rust +diman::unit_system!( quantity_type Quantity; dimension_type Dimension; @@ -78,11 +74,8 @@ The macro will automatically implement all the required traits and methods for t The `unit_system!` macro also allows defining derived dimensions and units: -```rust ignore -#![allow(incomplete_features)] -#![feature(generic_const_exprs, adt_const_params)] -use diman::unit_system; -unit_system!( +```rust +diman::unit_system!( quantity_type Quantity; dimension_type Dimension; @@ -92,11 +85,12 @@ unit_system!( dimension Velocity = Length / Time; #[prefix(kilo, milli)] - #[symbol(m)] #[base(Length)] + #[symbol(m)] unit meters; #[base(Time)] + #[symbol(s)] unit seconds; unit hours: Time = 3600 * seconds; @@ -105,16 +99,15 @@ unit_system!( constant MY_FAVORITE_VELOCITY = 1000 * kilometers_per_hour; ); -use f64::{Length, Time, Velocity, MY_FAVORITE_VELOCITY}; -fn fast_enough(x: Length, t: Time) { +fn fast_enough(x: Length, t: Time) { let vel = x / t; - if vel > MY_FAVORITE_VELOCITY { - println!("{} m/s is definitely fast enough!", vel.in_meters_per_second()); + if vel > 1.0 * MY_FAVORITE_VELOCITY { + println!("{} m/s is definitely fast enough!", vel.value_in(meters_per_second)); } } -fast_enough(Length::kilometers(100.0), Time::hours(0.3)); +fast_enough(100.0 * kilometers, 0.3 * hours); ``` Here, `dimension` defines Quantities, which are concrete types, `unit` defines units, which are methods on the corresponding quantities and `constant` defines constants. @@ -124,9 +117,9 @@ Other than this, there are no differences between base dimensions and dimensions The macro also accepts more complex expressions such as `dimension Energy = Mass (Length / Time)^2`. The definitions do not have to be in any specific order. -## The Quantity type +# The Quantity type The macro will automatically implement numerical traits such as `Add`, `Sub`, `Mul`, and various other methods of the underlying storage type for `Quantity`. -`Quantity` should behave just like its underlying storage type whenever possible and allowed by the dimensions. +`Quantity` should behave just like its underlying storage type whenever possible and allowed by the dimensions. For example: * Addition of `Quantity` and `Float` is possible if and only if `D` is dimensionless. * `Quantity` implements the dimensionless methods of `S`, such as `abs` for dimensionless quantities. @@ -137,18 +130,18 @@ For example: * Similarly, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This is also not unit-safe. Some other, more complex operations are also allowed: -``` -use diman::si::f64::{Length, Volume}; -let x = Length::meters(3.0); +```rust +let x = 3.0f64 * meters; let vol = x.cubed(); -assert_eq!(vol, Volume::cubic_meters(27.0)) +assert_eq!(vol, 27.0 * cubic_meters) ``` This includes `squared`, `cubed`, `sqrt`, `cbrt` as well as `powi`. -## Prefixes +# Prefixes Unit prefixes can automatically be generated with the `#[prefix(...)]` attribute for unit statements. For example -```rust ignore +```rust +#[base(Length)] #[prefix(kilo, milli)] #[symbol(m)] unit meters; @@ -156,82 +149,100 @@ unit meters; will automatically generate the unit `meters` with symbol `m`, as well as `kilometers` and `millimeters` with symbols `km` and `mm` corresponding to `1e3 m` and `1e-3 m`. For simplicity, the attribute `#[metric_prefixes]` is provided, which will generate all metric prefixes from `atto-` to `exa-` automatically. -## Aliases +# Aliases Unit aliases can automatically be generated with the `#[alias(...)]` macro. For example -```rust ignore +```rust #[alias(metres)] unit meters; ``` will automatically generate a unit `metres` that has exactly the same definition as `meters`. This works with prefixes as expected (i.e. an alias is generated for every prefixed unit). -## Quantity products and quotients +# Quantity products and quotients Sometimes, intermediate types in computations are quantities that don't really have a nice name and are also not needed too many times. Having to add a definition to the unit system for this case can be cumbersome. This is why the `Product` and `Quotient` types are provided: ```rust -use diman::si::f64::{Length, Time}; +use diman::si::dimensions::{Length, Time}; use diman::{Product, Quotient}; -fn foo(l: Length, t: Time) -> Product { +fn foo(l: Length, t: Time) -> Product, Time> { l * t } -fn bar(l: Length, t: Time) -> Quotient { +fn bar(l: Length, t: Time) -> Quotient, Time> { l / t } ``` -## Rational dimensions -The `rational-dimensions` feature allows using quantities with rational exponents in their base dimensions, as opposed to just integer values. This allows expressing defining dimensions and units such as +# Rational dimensions +The `rational-dimensions` feature allows using quantities with rational exponents in their base dimensions, as opposed to just integer values. This allows expressing defining dimensions and units such as: ```rust ignore +# mod surround { +# use diman_unit_system::unit_system; +# unit_system!( +# quantity_type Quantity; +# dimension_type Dimension; +# dimension Length; +# dimension Time; +# #[base(Length)] +# #[symbol(m)] +# unit meters; +# #[base(Time)] +# #[symbol(s)] +# unit seconds; dimension Sorptivity = Length Time^(-1/2); unit meters_per_sqrt_second: Sorptivity = meters / seconds^(1/2); -``` -and using them in the usual manner -```rust ignore -let l = Length::micrometers(2.0); -let t = Time::milliseconds(5.0); +# ); +# } +# use surround::dimensions::Sorptivity; +# use surround::units::{micrometers,milliseconds}; +let l = 2.0 * micrometers; +let t = 5.0 * milliseconds; let sorptivity: Sorptivity = l / t.sqrt(); ``` The unit system generated with `rational-dimensions` supports a superset of features of a unit system generated without them. Still, this feature should be enabled only when necessary, since the compiler errors in case of dimension mismatches will be harder to read. -## `serde` +# `serde` Serialization and deserialization of the units is provided via `serde` if the `serde` feature gate is enabled: ```rust ignore -use diman::si::f64::{Length, Velocity}; -use serde::{Serialize, Deserialize}; +# use diman::si::dimensions::{Length, Velocity}; +# use diman::si::units::{meters, meters_per_second}; +# use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize, Debug, PartialEq)] struct Parameters { - my_length: Length, - my_vel: Velocity, + my_length: Length, + my_vel: Velocity, } -let params: Parameters = +let params: Parameters = serde_yaml::from_str(" my_length: 100 m my_vel: 10 m s^-1 ").unwrap(); assert_eq!( - params, + params, Parameters { - my_length: Length::meters(100.0), - my_vel: Velocity::meters_per_second(10.0), + my_length: 100.0 * meters, + my_vel: 10.0 * meters_per_second, } ) ``` -## `rand` +# `rand` Diman allows generating random quantities via `rand` if the `rand` feature gate is enabled: ```rust ignore -use rand::Rng; - -use diman::si::f64::Length; +# use rand::Rng; +# use diman::si::units::{meters, kilometers}; let mut rng = rand::thread_rng(); for _ in 0..100 { - let x = rng.gen_range(Length::meters(0.0)..Length::kilometers(1.0)); + let start = 0.0 * meters; + let end = 1.0 * kilometers; + let x = rng.gen_range(start..end); assert!(Length::meters(0.0) <= x); assert!(x < Length::meters(1000.0)); } ``` + + diff --git a/src/lib.rs b/src/lib.rs index 7f08486..295dba6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,286 @@ +//! Diman is a library for zero-cost compile time unit checking. +//! +//! ``` +//! # #![feature(generic_const_exprs)] +//! use diman::si::dimensions::{Length, Time, Velocity}; +//! use diman::si::units::{seconds, meters, kilometers, hours}; +//! +//! fn get_velocity(x: Length, t: Time) -> Velocity { +//! x / t +//! } +//! +//! let v1 = get_velocity(36.0 * kilometers, 1.0 * hours); +//! let v2 = get_velocity(10.0 * meters, 1.0 * seconds); +//! +//! assert_eq!(v1, v2); +//! ``` +//! +//! Let's try to assign add quantities with incompatible dimensions: +//! ```rust compile_fail +//! # use diman::si::units::{seconds, meters}; +//! let time = 1.0 * seconds; +//! let length = 10.0 * meters; +//! let sum = length + time; +//! ``` +//! This results in a compiler error: +//! ```text +//! let sum = length + time; +//! ^^^^ +//! = note: expected struct `Quantity<_, Dimension { length: 1, time: 0, mass: 0, temperature: 0, current: 0, amount_of_substance: 0, luminous_intensity: 0 }>` +//! found struct `Quantity<_, Dimension { length: 0, time: 1, mass: 0, temperature: 0, current: 0, amount_of_substance: 0, luminous_intensity: 0 }>` +//! ``` +//! +//! +//! # Disclaimer +//! Diman is implemented using Rust's const generics feature. While `min_const_generics` has been stabilized since Rust 1.51, Diman uses more complex generic expressions and therefore requires the two currently unstable features `generic_const_exprs` and `adt_const_params`. +//! +//! Moreover, Diman is in its early stages of development and APIs will change. +//! +//! If you cannot use unstable Rust for your project or require a stable library, consider using [`uom`](https://crates.io/crates/uom) or [`dimensioned`](https://crates.io/crates/dimensioned), both of which do not require any experimental features and are much more mature libraries in general. +//! +//! # Features +//! * Invalid operations between physical quantities (adding length and time, for example) turn into compile errors. +//! * Newly created quantities are automatically converted to an underlying base representation. This means that the used types are dimensions (such as `Length`) instead of concrete units (such as `meters`) which makes for more meaningful code. +//! * Systems of dimensions and units can be user defined via the `unit_system!` macro. This gives the user complete freedom over the choice of dimensions and makes them part of the user's library, so that arbitrary new methods can be implemented on them. +//! * The `rational-dimensions` features allows the usage of quantities and units with rational exponents. +//! * `f32` and `f64` float storage types (behind the `f32` and `f64` feature gate respectively). +//! * The `std` feature is enabled by default. If disabled, Diman will be a `no_std` crate, thus suitable for use on embedded devices such as GPU device kernels. +//! * The `num-traits-libm` feature uses [libm](https://crates.io/crates/libm) to provide math functions in `no_std` environments. While one can use libm in `std`, the libm implementations are generally slower so this is unlikely to be desirable. +//! * Vector storage types via [`glam`](https://crates.io/crates/glam/) (behind the `glam-vec2`, `glam-vec3`, `glam-dvec2` and `glam-dvec3` features). +//! * Serialization and Deserialization via [`serde`](https://crates.io/crates/serde) (behind the `serde` feature gate, see the official documentation for more info). +//! * HDF5 support using [`hdf5-rs`](https://crates.io/crates/hdf5-rs/) (behind the `hdf5` feature gate). +//! * Quantities implement the `Equivalence` trait so that they can be sent via MPI using [`mpi`](https://crates.io/crates/mpi) (behind the `mpi` feature gate). +//! * Random quantities can be generated via [`rand`](https://crates.io/crates/rand) (behind the `rand` feature gate, see the official documentation for more info). +//! +//! # Design +//! Diman aims to make it as easy as possible to add compile-time unit safety to Rust code. Physical quantities are represented by the `Quantity` struct, where `S` is the underlying storage type (`f32`, `f64`, ...) and `D` is the dimension of the quantity. For example, in order to represent the [SI system of units](https://www.nist.gov/pml/owm/metric-si/si-units), the quantity type would be defined using the `unit_system!` macro as follows: +//! ```rust +//! # #![allow(incomplete_features)] +//! # #![feature(generic_const_exprs, adt_const_params)] +//! # mod surround { +//! # use diman_unit_system::unit_system; +//! diman::unit_system!( +//! quantity_type Quantity; +//! dimension_type Dimension; +//! +//! dimension Length; +//! dimension Time; +//! dimension Mass; +//! dimension Temperature; +//! dimension Current; +//! dimension AmountOfSubstance; +//! dimension LuminousIntensity; +//! ); +//! # } +//! ``` +//! The first two statements imply that the macro should define a `Quantity` type, which is user-facing, and a `Dimension` type, which is used only internally and will surface in compiler error messages. +//! The macro will automatically implement all the required traits and methods for the `Quantity` type, such that addition and subtraction of two quantities is only allowed for quantities with the same `Dimension` type. During multiplication of two quantities, all the entries of the two dimensions are added. See below for a more comprehensive list of the implemented methods on `Quantity`. +//! +//! The `unit_system!` macro also allows defining derived dimensions and units: +//! +//! ```rust +//! # #![allow(incomplete_features)] +//! # #![feature(generic_const_exprs, adt_const_params)] +//! # mod surround { +//! diman::unit_system!( +//! quantity_type Quantity; +//! dimension_type Dimension; +//! +//! dimension Length; +//! dimension Time; +//! +//! dimension Velocity = Length / Time; +//! +//! #[prefix(kilo, milli)] +//! #[base(Length)] +//! #[symbol(m)] +//! unit meters; +//! +//! #[base(Time)] +//! #[symbol(s)] +//! unit seconds; +//! +//! unit hours: Time = 3600 * seconds; +//! unit meters_per_second: Velocity = meters / seconds; +//! unit kilometers_per_hour: Velocity = kilometers / hours; +//! constant MY_FAVORITE_VELOCITY = 1000 * kilometers_per_hour; +//! ); +//! # } +//! +//! # use surround::dimensions::{Length, Time, Velocity}; +//! # use surround::units::{meters_per_second,kilometers,hours}; +//! # use surround::constants::MY_FAVORITE_VELOCITY; +//! +//! fn fast_enough(x: Length, t: Time) { +//! let vel = x / t; +//! if vel > 1.0 * MY_FAVORITE_VELOCITY { +//! println!("{} m/s is definitely fast enough!", vel.value_in(meters_per_second)); +//! } +//! } +//! +//! fast_enough(100.0 * kilometers, 0.3 * hours); +//! ``` +//! +//! Here, `dimension` defines Quantities, which are concrete types, `unit` defines units, which are methods on the corresponding quantities and `constant` defines constants. +//! Dimensions without a right hand side are base dimensions (such as length, time, mass, temperature, ... in the SI system of units), whereas dimensions with a right hand side are derived dimensions. +//! The same thing holds for units - every unit is either a base unit for a given base dimension (denoted by the `#[base(...)]` attribute), or derived from base units and other derived units. Base units have the special property that the internal representation of the quantity will be in terms of the base unit (for example, a stored value `1.0` for a quantity with a `Length` dimension corresponds to `meter` in the above definitions). +//! Other than this, there are no differences between base dimensions and dimensions or base units and units and they can be treated equally in user code. +//! The macro also accepts more complex expressions such as `dimension Energy = Mass (Length / Time)^2`. +//! The definitions do not have to be in any specific order. +//! +//! # The Quantity type +//! The macro will automatically implement numerical traits such as `Add`, `Sub`, `Mul`, and various other methods of the underlying storage type for `Quantity`. +//! `Quantity` should behave just like its underlying storage type whenever possible and allowed by the dimensions. +//! For example: +//! * Addition of `Quantity` and `Float` is possible if and only if `D` is dimensionless. +//! * `Quantity` implements the dimensionless methods of `S`, such as `abs` for dimensionless quantities. +//! * It implements `Deref` to `S` if and only if `D` is dimensionless. +//! * `Debug` is implemented and will print the quantity in its representation of the "closest" unit. For example `Length::meters(100.0)` would be debug printed as `0.1 km`. If printing in a specific unit is required, conversion methods are available for each unit (such as `Length::in_meters`). +//! * `.value()` provides access to the underlying storage type of a dimensionless quantity. +//! * `.value_unchecked()` provides access to the underlying storage type for all quantities if absolutely required. This is not unit-safe since the value will depend on the unit system! +//! * Similarly, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This is also not unit-safe. +//! +//! Some other, more complex operations are also allowed: +//! ``` +//! # use diman::si::dimensions::{Length, Volume}; +//! # use diman::si::units::{meters,cubic_meters}; +//! let x = 3.0f64 * meters; +//! let vol = x.cubed(); +//! assert_eq!(vol, 27.0 * cubic_meters) +//! ``` +//! This includes `squared`, `cubed`, `sqrt`, `cbrt` as well as `powi`. +//! +//! # Prefixes +//! Unit prefixes can automatically be generated with the `#[prefix(...)]` attribute for unit statements. +//! For example +//! ```rust +//! # #![allow(incomplete_features)] +//! # #![feature(generic_const_exprs, adt_const_params)] +//! # mod surround { +//! # diman_unit_system::unit_system!( +//! # quantity_type Quantity; +//! # dimension_type Dimension; +//! # dimension Length; +//! #[base(Length)] +//! #[prefix(kilo, milli)] +//! #[symbol(m)] +//! unit meters; +//! # ); +//! # } +//! ``` +//! will automatically generate the unit `meters` with symbol `m`, as well as `kilometers` and `millimeters` with symbols `km` and `mm` corresponding to `1e3 m` and `1e-3 m`. +//! For simplicity, the attribute `#[metric_prefixes]` is provided, which will generate all metric prefixes from `atto-` to `exa-` automatically. +//! +//! # Aliases +//! Unit aliases can automatically be generated with the `#[alias(...)]` macro. For example +//! ```rust +//! # #![allow(incomplete_features)] +//! # #![feature(generic_const_exprs, adt_const_params)] +//! # mod surround { +//! # diman_unit_system::unit_system!( +//! # quantity_type Quantity; +//! # dimension_type Dimension; +//! # dimension Length; +//! # #[symbol(m)] +//! # #[base(Length)] +//! #[alias(metres)] +//! unit meters; +//! # ); +//! # } +//! ``` +//! will automatically generate a unit `metres` that has exactly the same definition as `meters`. This works with prefixes as expected (i.e. an alias is generated for every prefixed unit). +//! +//! # Quantity products and quotients +//! Sometimes, intermediate types in computations are quantities that don't really have a nice name and are also +//! not needed too many times. Having to add a definition to the unit system for this case can be cumbersome. +//! This is why the `Product` and `Quotient` types are provided: +//! ```rust +//! use diman::si::dimensions::{Length, Time}; +//! use diman::{Product, Quotient}; +//! fn foo(l: Length, t: Time) -> Product, Time> { +//! l * t +//! } +//! +//! fn bar(l: Length, t: Time) -> Quotient, Time> { +//! l / t +//! } +//! ``` +//! +//! # Rational dimensions +//! The `rational-dimensions` feature allows using quantities with rational exponents in their base dimensions, as opposed to just integer values. This allows expressing defining dimensions and units such as: +//! ```rust ignore +//! # mod surround { +//! # use diman_unit_system::unit_system; +//! # unit_system!( +//! # quantity_type Quantity; +//! # dimension_type Dimension; +//! # dimension Length; +//! # dimension Time; +//! # #[base(Length)] +//! # #[symbol(m)] +//! # unit meters; +//! # #[base(Time)] +//! # #[symbol(s)] +//! # unit seconds; +//! dimension Sorptivity = Length Time^(-1/2); +//! unit meters_per_sqrt_second: Sorptivity = meters / seconds^(1/2); +//! # ); +//! # } +//! # use surround::dimensions::Sorptivity; +//! # use surround::units::{micrometers,milliseconds}; +//! let l = 2.0 * micrometers; +//! let t = 5.0 * milliseconds; +//! let sorptivity: Sorptivity = l / t.sqrt(); +//! ``` +//! +//! The unit system generated with `rational-dimensions` supports a superset of features of a unit system generated without them. +//! Still, this feature should be enabled only when necessary, since the compiler errors in case of dimension mismatches will be harder to read. +//! +//! # `serde` +//! Serialization and deserialization of the units is provided via `serde` if the `serde` feature gate is enabled: +//! ```rust ignore +//! # use diman::si::dimensions::{Length, Velocity}; +//! # use diman::si::units::{meters, meters_per_second}; +//! # use serde::{Serialize, Deserialize}; +//! #[derive(Serialize, Deserialize, Debug, PartialEq)] +//! struct Parameters { +//! my_length: Length, +//! my_vel: Velocity, +//! } +//! +//! let params: Parameters = +//! serde_yaml::from_str(" +//! my_length: 100 m +//! my_vel: 10 m s^-1 +//! ").unwrap(); +//! assert_eq!( +//! params, +//! Parameters { +//! my_length: 100.0 * meters, +//! my_vel: 10.0 * meters_per_second, +//! } +//! ) +//! ``` +//! +//! # `rand` +//! Diman allows generating random quantities via `rand` if the `rand` feature gate is enabled: +//! ```rust ignore +//! # use rand::Rng; +//! # use diman::si::units::{meters, kilometers}; +//! +//! let mut rng = rand::thread_rng(); +//! for _ in 0..100 { +//! let start = 0.0 * meters; +//! let end = 1.0 * kilometers; +//! let x = rng.gen_range(start..end); +//! assert!(Length::meters(0.0) <= x); +//! assert!(x < Length::meters(1000.0)); +//! } +//! ``` + #![cfg_attr(not(feature = "std"), no_std)] #![allow(incomplete_features)] #![feature(generic_const_exprs, adt_const_params)] -#![doc = include_str!("../README.md")] // clippy bug: https://github.com/rust-lang/rust-clippy/issues/12133 #![allow(clippy::unconditional_recursion)] @@ -121,18 +400,20 @@ pub use diman_unit_system::unit_system; /// Constructs a product of quantities for one-off quantities. /// ``` /// # #![feature(generic_const_exprs)] -/// # use diman::si::f64::{Length, Time}; +/// # use diman::si::dimensions::{Length, Time}; +/// # use diman::si::units::{meters, seconds}; /// # use diman::Product; -/// let x: Product = Length::meters(10.0) * Time::seconds(2.0); +/// let x: Product, Time> = 20.0 * meters * seconds; /// ``` pub type Product = >::Output; /// Constructs a quotient of two quantities for one-off quantities. /// ``` /// # #![feature(generic_const_exprs)] -/// # use diman::si::f64::{Length, Time}; +/// # use diman::si::dimensions::{Length, Time}; +/// # use diman::si::units::{meters, seconds}; /// # use diman::Quotient; -/// let x: Quotient = Length::meters(10.0) / Time::seconds(2.0); +/// let x: Quotient, Time> = 10.0 * meters / seconds; /// ``` pub type Quotient = >::Output; From f58f049b3c50a81113f5c55d9d5057286a9e4442 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Mon, 22 Jan 2024 23:43:04 +0100 Subject: [PATCH 24/40] fix serde doctest --- crates/diman_unit_system/src/codegen/debug_trait.rs | 3 ++- crates/diman_unit_system/src/codegen/serde.rs | 4 ++-- src/lib.rs | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/diman_unit_system/src/codegen/debug_trait.rs b/crates/diman_unit_system/src/codegen/debug_trait.rs index 97dafeb..51214b7 100644 --- a/crates/diman_unit_system/src/codegen/debug_trait.rs +++ b/crates/diman_unit_system/src/codegen/debug_trait.rs @@ -35,8 +35,9 @@ impl Codegen { }) }) .collect(); + let dimension_type = &self.defs.dimension_type; quote! { - let units_array = &[#units]; + let units_array: &[#runtime_unit::<#dimension_type>] = &[#units]; let units = #runtime_unit_storage::new(units_array); } } diff --git a/crates/diman_unit_system/src/codegen/serde.rs b/crates/diman_unit_system/src/codegen/serde.rs index baff9a4..05cc3a9 100644 --- a/crates/diman_unit_system/src/codegen/serde.rs +++ b/crates/diman_unit_system/src/codegen/serde.rs @@ -25,7 +25,7 @@ impl Codegen { use core::marker::PhantomData; use std::str::SplitWhitespace; - use serde::de::{self}; + use serde::de; #[derive(Default)] struct QuantityVisitor(PhantomData); @@ -173,7 +173,7 @@ impl Codegen { let (total_dimension, total_factor) = read_unit_str(split)?; get_quantity_if_dimensions_match::<#float_type, D, E>( value, - (numerical_value * (total_factor as #float_type)), + numerical_value * (total_factor as #float_type), total_dimension, ) } diff --git a/src/lib.rs b/src/lib.rs index 295dba6..be8c639 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,7 +58,6 @@ //! # #![allow(incomplete_features)] //! # #![feature(generic_const_exprs, adt_const_params)] //! # mod surround { -//! # use diman_unit_system::unit_system; //! diman::unit_system!( //! quantity_type Quantity; //! dimension_type Dimension; From 3cf36b47a1e8dc3fd059093ffdf37a7d35ea370e Mon Sep 17 00:00:00 2001 From: tehforsch Date: Tue, 23 Jan 2024 00:01:21 +0100 Subject: [PATCH 25/40] mpi annotate --- tests/mpi/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/mpi/mod.rs b/tests/mpi/mod.rs index 971aec2..04a3574 100644 --- a/tests/mpi/mod.rs +++ b/tests/mpi/mod.rs @@ -58,6 +58,7 @@ macro_rules! gen_tests_for_vector_2 { macro_rules! gen_tests_for_vector_3 { ($vec_mod_name: ident, $vec_name: ident) => { mod $vec_mod_name { + use crate::example_system::dimensions::Length; use crate::example_system::units::meters; use glam::$vec_name; use mpi::topology::Communicator; @@ -65,8 +66,8 @@ macro_rules! gen_tests_for_vector_3 { #[test] fn pack_unpack_vec_quantity() { let world = super::MPI_UNIVERSE.world(); - let q1 = <$vec_name>::new(1.0, 2.0, 3.0) * meters; - let mut q2 = <$vec_name>::new(4.0, 5.0, 6.0) * meters; + let q1: Length<$vec_name> = <$vec_name>::new(1.0, 2.0, 3.0) * meters; + let mut q2: Length<$vec_name> = <$vec_name>::new(4.0, 5.0, 6.0) * meters; let a = world.pack(&q1); unsafe { world.unpack_into(&a, &mut q2, 0); From eca16f48bfeb6b94cc096e9cb779de2eb6194935 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 25 Feb 2024 09:49:39 +0100 Subject: [PATCH 26/40] update doc --- README.md | 33 +++++---------------------------- src/lib.rs | 28 ++++++++++++++-------------- 2 files changed, 19 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index c0e9b36..0f055a4 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,8 @@ let v2 = get_velocity(10.0 * meters, 1.0 * seconds); assert_eq!(v1, v2); ``` -Let's try to assign add quantities with incompatible dimensions: -```rust compile_fail -# use diman::si::units::{seconds, meters}; +Let's try to add quantities with incompatible dimensions: +```rust let time = 1.0 * seconds; let length = 10.0 * meters; let sum = length + time; @@ -175,26 +174,9 @@ fn bar(l: Length, t: Time) -> Quotient, Time> { # Rational dimensions The `rational-dimensions` feature allows using quantities with rational exponents in their base dimensions, as opposed to just integer values. This allows expressing defining dimensions and units such as: -```rust ignore -# mod surround { -# use diman_unit_system::unit_system; -# unit_system!( -# quantity_type Quantity; -# dimension_type Dimension; -# dimension Length; -# dimension Time; -# #[base(Length)] -# #[symbol(m)] -# unit meters; -# #[base(Time)] -# #[symbol(s)] -# unit seconds; +```rust dimension Sorptivity = Length Time^(-1/2); unit meters_per_sqrt_second: Sorptivity = meters / seconds^(1/2); -# ); -# } -# use surround::dimensions::Sorptivity; -# use surround::units::{micrometers,milliseconds}; let l = 2.0 * micrometers; let t = 5.0 * milliseconds; let sorptivity: Sorptivity = l / t.sqrt(); @@ -205,10 +187,7 @@ Still, this feature should be enabled only when necessary, since the compiler er # `serde` Serialization and deserialization of the units is provided via `serde` if the `serde` feature gate is enabled: -```rust ignore -# use diman::si::dimensions::{Length, Velocity}; -# use diman::si::units::{meters, meters_per_second}; -# use serde::{Serialize, Deserialize}; +```rust #[derive(Serialize, Deserialize, Debug, PartialEq)] struct Parameters { my_length: Length, @@ -231,9 +210,7 @@ assert_eq!( # `rand` Diman allows generating random quantities via `rand` if the `rand` feature gate is enabled: -```rust ignore -# use rand::Rng; -# use diman::si::units::{meters, kilometers}; +```rust let mut rng = rand::thread_rng(); for _ in 0..100 { diff --git a/src/lib.rs b/src/lib.rs index be8c639..c3dbe14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,8 +15,8 @@ //! assert_eq!(v1, v2); //! ``` //! -//! Let's try to assign add quantities with incompatible dimensions: -//! ```rust compile_fail +//! Let's try to add quantities with incompatible dimensions: +//! ```compile_fail //! # use diman::si::units::{seconds, meters}; //! let time = 1.0 * seconds; //! let length = 10.0 * meters; @@ -54,7 +54,7 @@ //! //! # Design //! Diman aims to make it as easy as possible to add compile-time unit safety to Rust code. Physical quantities are represented by the `Quantity` struct, where `S` is the underlying storage type (`f32`, `f64`, ...) and `D` is the dimension of the quantity. For example, in order to represent the [SI system of units](https://www.nist.gov/pml/owm/metric-si/si-units), the quantity type would be defined using the `unit_system!` macro as follows: -//! ```rust +//! ``` //! # #![allow(incomplete_features)] //! # #![feature(generic_const_exprs, adt_const_params)] //! # mod surround { @@ -77,7 +77,7 @@ //! //! The `unit_system!` macro also allows defining derived dimensions and units: //! -//! ```rust +//! ``` //! # #![allow(incomplete_features)] //! # #![feature(generic_const_exprs, adt_const_params)] //! # mod surround { @@ -152,7 +152,7 @@ //! # Prefixes //! Unit prefixes can automatically be generated with the `#[prefix(...)]` attribute for unit statements. //! For example -//! ```rust +//! ``` //! # #![allow(incomplete_features)] //! # #![feature(generic_const_exprs, adt_const_params)] //! # mod surround { @@ -172,7 +172,7 @@ //! //! # Aliases //! Unit aliases can automatically be generated with the `#[alias(...)]` macro. For example -//! ```rust +//! ``` //! # #![allow(incomplete_features)] //! # #![feature(generic_const_exprs, adt_const_params)] //! # mod surround { @@ -193,7 +193,7 @@ //! Sometimes, intermediate types in computations are quantities that don't really have a nice name and are also //! not needed too many times. Having to add a definition to the unit system for this case can be cumbersome. //! This is why the `Product` and `Quotient` types are provided: -//! ```rust +//! ``` //! use diman::si::dimensions::{Length, Time}; //! use diman::{Product, Quotient}; //! fn foo(l: Length, t: Time) -> Product, Time> { @@ -207,7 +207,7 @@ //! //! # Rational dimensions //! The `rational-dimensions` feature allows using quantities with rational exponents in their base dimensions, as opposed to just integer values. This allows expressing defining dimensions and units such as: -//! ```rust ignore +//! ```ignore //! # mod surround { //! # use diman_unit_system::unit_system; //! # unit_system!( @@ -237,7 +237,7 @@ //! //! # `serde` //! Serialization and deserialization of the units is provided via `serde` if the `serde` feature gate is enabled: -//! ```rust ignore +//! ```ignore //! # use diman::si::dimensions::{Length, Velocity}; //! # use diman::si::units::{meters, meters_per_second}; //! # use serde::{Serialize, Deserialize}; @@ -263,7 +263,7 @@ //! //! # `rand` //! Diman allows generating random quantities via `rand` if the `rand` feature gate is enabled: -//! ```rust ignore +//! ```ignore //! # use rand::Rng; //! # use diman::si::units::{meters, kilometers}; //! @@ -309,7 +309,7 @@ pub mod si; /// 3. `dimension`: Define a new dimension. If no expression is given (as in `dimension Length;`), this will define a new base dimension. If an expression is given, it will define a type alias for a derived dimension (as in `dimension Velocity = Length / Time`). /// 4. `constant`: Define a new constant. Example: `constant ELECTRON_CHARGE = 1.602176634e-19 volts`. /// 5. `unit`: Define a new unit. If no expression is given and the `#[base(...)]` attribute is set, it will be the base unit for the given dimension. Example: -/// ```rust +/// ``` /// # #![allow(incomplete_features)] /// # #![feature(generic_const_exprs, adt_const_params)] /// # mod surround { @@ -325,7 +325,7 @@ pub mod si; /// # } /// ``` /// Derived units can be defined via expressions, such as -/// ```rust +/// ``` /// # #![allow(incomplete_features)] /// # #![feature(generic_const_exprs, adt_const_params)] /// # mod surround { @@ -342,7 +342,7 @@ pub mod si; /// # } /// ``` /// Unit statements may optionally be annotated with their resulting dimension to prevent bugs: -/// ```rust +/// ``` /// # #![allow(incomplete_features)] /// # #![feature(generic_const_exprs, adt_const_params)] /// # mod surround { @@ -364,7 +364,7 @@ pub mod si; /// The symbol of the unit can be defined using the `#[symbol(...)]` attribute. /// /// Example usage: -/// ```rust +/// ``` /// # #![allow(incomplete_features)] /// # #![feature(generic_const_exprs, adt_const_params)] /// # mod surround { From 9a6ed4d4bfe0c3a44c29e5bf8efe4cf561821ea8 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 25 Feb 2024 10:16:55 +0100 Subject: [PATCH 27/40] update readme --- README.md | 50 +++++++++++++++++++++++++------------------ src/lib.rs | 62 ++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 68 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 0f055a4..db1ca0c 100644 --- a/README.md +++ b/README.md @@ -52,8 +52,36 @@ If you cannot use unstable Rust for your project or require a stable library, co * Quantities implement the `Equivalence` trait so that they can be sent via MPI using [`mpi`](https://crates.io/crates/mpi) (behind the `mpi` feature gate). * Random quantities can be generated via [`rand`](https://crates.io/crates/rand) (behind the `rand` feature gate, see the official documentation for more info). +# The `Quantity` type +Physical quantities are represented by the `Quantity` struct, where `S` is the underlying storage type (`f32`, `f64`, ...) and `D` is the dimension of the quantity. +`Quantity` should behave like its underlying storage type whenever allowed by the dimensions. +For example: +* Addition and subtraction of two quantities with the same storage type is allowed if the dimensions match. +* Multiplication and division of two quantities with the same storage type produces a new quantity: +```rust +let l: Length = 5.0 * meters; +let t: Time = 2.0 * seconds; +let v: Velocity = l / t; +``` +* Addition of `Quantity` and `Float` is possible if and only if `D` is dimensionless. +* `Quantity` implements the dimensionless methods of `S`, such as `abs` for dimensionless quantities. +* It implements `Deref` to `S` if and only if `D` is dimensionless. +* `Debug` is implemented and will print the quantity in its representation of the "closest" unit. For example `Length::meters(100.0)` would be debug printed as `0.1 km`. If printing in a specific unit is required, conversion methods are available for each unit (such as `Length::in_meters`). +* `.value()` provides access to the underlying storage type of a dimensionless quantity. +* `.value_unchecked()` provides access to the underlying storage type for all quantities if absolutely required. This is not unit-safe since the value will depend on the unit system! +* Similarly, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This is also not unit-safe. + +Some other, more complex operations are also allowed: +```rust +let x = 3.0f64 * meters; +let vol = x.cubed(); +assert_eq!(vol, 27.0 * cubic_meters) +``` +This includes `squared`, `cubed`, `sqrt`, `cbrt` as well as `powi`. + + # Design -Diman aims to make it as easy as possible to add compile-time unit safety to Rust code. Physical quantities are represented by the `Quantity` struct, where `S` is the underlying storage type (`f32`, `f64`, ...) and `D` is the dimension of the quantity. For example, in order to represent the [SI system of units](https://www.nist.gov/pml/owm/metric-si/si-units), the quantity type would be defined using the `unit_system!` macro as follows: + For example, in order to represent the [SI system of units](https://www.nist.gov/pml/owm/metric-si/si-units), the quantity type would be defined using the `unit_system!` macro as follows: ```rust diman::unit_system!( quantity_type Quantity; @@ -116,26 +144,6 @@ Other than this, there are no differences between base dimensions and dimensions The macro also accepts more complex expressions such as `dimension Energy = Mass (Length / Time)^2`. The definitions do not have to be in any specific order. -# The Quantity type -The macro will automatically implement numerical traits such as `Add`, `Sub`, `Mul`, and various other methods of the underlying storage type for `Quantity`. -`Quantity` should behave just like its underlying storage type whenever possible and allowed by the dimensions. -For example: -* Addition of `Quantity` and `Float` is possible if and only if `D` is dimensionless. -* `Quantity` implements the dimensionless methods of `S`, such as `abs` for dimensionless quantities. -* It implements `Deref` to `S` if and only if `D` is dimensionless. -* `Debug` is implemented and will print the quantity in its representation of the "closest" unit. For example `Length::meters(100.0)` would be debug printed as `0.1 km`. If printing in a specific unit is required, conversion methods are available for each unit (such as `Length::in_meters`). -* `.value()` provides access to the underlying storage type of a dimensionless quantity. -* `.value_unchecked()` provides access to the underlying storage type for all quantities if absolutely required. This is not unit-safe since the value will depend on the unit system! -* Similarly, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This is also not unit-safe. - -Some other, more complex operations are also allowed: -```rust -let x = 3.0f64 * meters; -let vol = x.cubed(); -assert_eq!(vol, 27.0 * cubic_meters) -``` -This includes `squared`, `cubed`, `sqrt`, `cbrt` as well as `powi`. - # Prefixes Unit prefixes can automatically be generated with the `#[prefix(...)]` attribute for unit statements. For example diff --git a/src/lib.rs b/src/lib.rs index c3dbe14..af16be0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,8 +52,46 @@ //! * Quantities implement the `Equivalence` trait so that they can be sent via MPI using [`mpi`](https://crates.io/crates/mpi) (behind the `mpi` feature gate). //! * Random quantities can be generated via [`rand`](https://crates.io/crates/rand) (behind the `rand` feature gate, see the official documentation for more info). //! +//! # The `Quantity` type +//! Physical quantities are represented by the `Quantity` struct, where `S` is the underlying storage type (`f32`, `f64`, ...) and `D` is the dimension of the quantity. +//! `Quantity` should behave like its underlying storage type whenever allowed by the dimensions. +//! ## Arithmetics +//! * Addition and subtraction of two quantities with the same storage type is allowed if the dimensions match. +//! ``` +//! # use diman::si::dimensions::{Length}; +//! # use diman::si::units::{kilometers, meters}; +//! let l: Length = 5.0 * meters + 10.0 * kilometers; +//! ``` +//! * Multiplication and division of two quantities with the same storage type produces a new quantity: +//! ``` +//! # #![feature(generic_const_exprs)] +//! # use diman::si::dimensions::{Length, Time, Velocity}; +//! # use diman::si::units::{meters, seconds}; +//! let l: Length = 5.0 * meters; +//! let t: Time = 2.0 * seconds; +//! let v: Velocity = l / t; +//! ``` +//! * Addition of `Quantity` and `Float` is possible if and only if `D` is dimensionless. +//! * `Quantity` implements the dimensionless methods of `S`, such as `abs` for dimensionless quantities. +//! * It implements `Deref` to `S` if and only if `D` is dimensionless. +//! * `Debug` is implemented and will print the quantity in its representation of the "closest" unit. For example `Length::meters(100.0)` would be debug printed as `0.1 km`. If printing in a specific unit is required, conversion methods are available for each unit (such as `Length::in_meters`). +//! * `.value()` provides access to the underlying storage type of a dimensionless quantity. +//! * `.value_unchecked()` provides access to the underlying storage type for all quantities if absolutely required. This is not unit-safe since the value will depend on the unit system! +//! * Similarly, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This is also not unit-safe. +//! +//! Some other, more complex operations are also allowed: +//! ``` +//! # use diman::si::dimensions::{Length, Volume}; +//! # use diman::si::units::{meters,cubic_meters}; +//! let x = 3.0f64 * meters; +//! let vol = x.cubed(); +//! assert_eq!(vol, 27.0 * cubic_meters) +//! ``` +//! This includes `squared`, `cubed`, `sqrt`, `cbrt` as well as `powi`. +//! +//! //! # Design -//! Diman aims to make it as easy as possible to add compile-time unit safety to Rust code. Physical quantities are represented by the `Quantity` struct, where `S` is the underlying storage type (`f32`, `f64`, ...) and `D` is the dimension of the quantity. For example, in order to represent the [SI system of units](https://www.nist.gov/pml/owm/metric-si/si-units), the quantity type would be defined using the `unit_system!` macro as follows: +//! For example, in order to represent the [SI system of units](https://www.nist.gov/pml/owm/metric-si/si-units), the quantity type would be defined using the `unit_system!` macro as follows: //! ``` //! # #![allow(incomplete_features)] //! # #![feature(generic_const_exprs, adt_const_params)] @@ -127,28 +165,6 @@ //! The macro also accepts more complex expressions such as `dimension Energy = Mass (Length / Time)^2`. //! The definitions do not have to be in any specific order. //! -//! # The Quantity type -//! The macro will automatically implement numerical traits such as `Add`, `Sub`, `Mul`, and various other methods of the underlying storage type for `Quantity`. -//! `Quantity` should behave just like its underlying storage type whenever possible and allowed by the dimensions. -//! For example: -//! * Addition of `Quantity` and `Float` is possible if and only if `D` is dimensionless. -//! * `Quantity` implements the dimensionless methods of `S`, such as `abs` for dimensionless quantities. -//! * It implements `Deref` to `S` if and only if `D` is dimensionless. -//! * `Debug` is implemented and will print the quantity in its representation of the "closest" unit. For example `Length::meters(100.0)` would be debug printed as `0.1 km`. If printing in a specific unit is required, conversion methods are available for each unit (such as `Length::in_meters`). -//! * `.value()` provides access to the underlying storage type of a dimensionless quantity. -//! * `.value_unchecked()` provides access to the underlying storage type for all quantities if absolutely required. This is not unit-safe since the value will depend on the unit system! -//! * Similarly, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This is also not unit-safe. -//! -//! Some other, more complex operations are also allowed: -//! ``` -//! # use diman::si::dimensions::{Length, Volume}; -//! # use diman::si::units::{meters,cubic_meters}; -//! let x = 3.0f64 * meters; -//! let vol = x.cubed(); -//! assert_eq!(vol, 27.0 * cubic_meters) -//! ``` -//! This includes `squared`, `cubed`, `sqrt`, `cbrt` as well as `powi`. -//! //! # Prefixes //! Unit prefixes can automatically be generated with the `#[prefix(...)]` attribute for unit statements. //! For example From 23724e1bc1753a5a8a603cf4835cc2989c92f50d Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 25 Feb 2024 10:22:54 +0100 Subject: [PATCH 28/40] add example for dimensionless add --- README.md | 11 ++++++++++- src/lib.rs | 9 +++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index db1ca0c..91987ca 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,11 @@ If you cannot use unstable Rust for your project or require a stable library, co # The `Quantity` type Physical quantities are represented by the `Quantity` struct, where `S` is the underlying storage type (`f32`, `f64`, ...) and `D` is the dimension of the quantity. `Quantity` should behave like its underlying storage type whenever allowed by the dimensions. -For example: +## Arithmetics * Addition and subtraction of two quantities with the same storage type is allowed if the dimensions match. +```rust +let l: Length = 5.0 * meters + 10.0 * kilometers; +``` * Multiplication and division of two quantities with the same storage type produces a new quantity: ```rust let l: Length = 5.0 * meters; @@ -64,6 +67,12 @@ let t: Time = 2.0 * seconds; let v: Velocity = l / t; ``` * Addition of `Quantity` and `Float` is possible if and only if `D` is dimensionless. +```rust +let l1: Length = 5.0 * meters; +let l2: Length = 10.0 * kilometers; +let x = l1 / l2 - 0.5; +let y = 0.5 + l1 / l2; +``` * `Quantity` implements the dimensionless methods of `S`, such as `abs` for dimensionless quantities. * It implements `Deref` to `S` if and only if `D` is dimensionless. * `Debug` is implemented and will print the quantity in its representation of the "closest" unit. For example `Length::meters(100.0)` would be debug printed as `0.1 km`. If printing in a specific unit is required, conversion methods are available for each unit (such as `Length::in_meters`). diff --git a/src/lib.rs b/src/lib.rs index af16be0..8be9330 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,6 +72,15 @@ //! let v: Velocity = l / t; //! ``` //! * Addition of `Quantity` and `Float` is possible if and only if `D` is dimensionless. +//! ``` +//! # #![feature(generic_const_exprs)] +//! # use diman::si::dimensions::{Dimensionless, Length}; +//! # use diman::si::units::{kilometers, meters}; +//! let l1: Length = 5.0 * meters; +//! let l2: Length = 10.0 * kilometers; +//! let x = l1 / l2 - 0.5; +//! let y = 0.5 + l1 / l2; +//! ``` //! * `Quantity` implements the dimensionless methods of `S`, such as `abs` for dimensionless quantities. //! * It implements `Deref` to `S` if and only if `D` is dimensionless. //! * `Debug` is implemented and will print the quantity in its representation of the "closest" unit. For example `Length::meters(100.0)` would be debug printed as `0.1 km`. If printing in a specific unit is required, conversion methods are available for each unit (such as `Length::in_meters`). From 7452d0e41c1936cc2b8cd2ddefbaaf21226db36c Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 25 Feb 2024 10:35:17 +0100 Subject: [PATCH 29/40] add doc for .value, Deref and .value_unchecked() --- README.md | 33 ++++++++++++++++++++++++--------- src/lib.rs | 44 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 91987ca..a91bf88 100644 --- a/README.md +++ b/README.md @@ -56,28 +56,43 @@ If you cannot use unstable Rust for your project or require a stable library, co Physical quantities are represented by the `Quantity` struct, where `S` is the underlying storage type (`f32`, `f64`, ...) and `D` is the dimension of the quantity. `Quantity` should behave like its underlying storage type whenever allowed by the dimensions. ## Arithmetics -* Addition and subtraction of two quantities with the same storage type is allowed if the dimensions match. +Addition and subtraction of two quantities is allowed if the dimensions match. ```rust let l: Length = 5.0 * meters + 10.0 * kilometers; ``` -* Multiplication and division of two quantities with the same storage type produces a new quantity: +Multiplication and division of two quantities produces a new quantity: ```rust let l: Length = 5.0 * meters; let t: Time = 2.0 * seconds; let v: Velocity = l / t; ``` -* Addition of `Quantity` and `Float` is possible if and only if `D` is dimensionless. +Addition and subtraction of a `Quantity` and a storage type is possible if and only if `D` is dimensionless. ```rust let l1: Length = 5.0 * meters; let l2: Length = 10.0 * kilometers; let x = l1 / l2 - 0.5; -let y = 0.5 + l1 / l2; +let y = 0.5 - l1 / l2; ``` -* `Quantity` implements the dimensionless methods of `S`, such as `abs` for dimensionless quantities. -* It implements `Deref` to `S` if and only if `D` is dimensionless. -* `Debug` is implemented and will print the quantity in its representation of the "closest" unit. For example `Length::meters(100.0)` would be debug printed as `0.1 km`. If printing in a specific unit is required, conversion methods are available for each unit (such as `Length::in_meters`). -* `.value()` provides access to the underlying storage type of a dimensionless quantity. -* `.value_unchecked()` provides access to the underlying storage type for all quantities if absolutely required. This is not unit-safe since the value will depend on the unit system! +`Quantity` implements the dimensionless methods of `S`, such as `sin`, `cos`, etc. for dimensionless quantities. +```rust +let l1: Length = 5.0 * meters; +let l2: Length = 10.0 * kilometers; +let angle_radians = (l1 / l2).asin(); +``` +For dimensionless quantities, `.value()` provides access to the underlying storage types. Alternatively, dimensionless quantities also implement `Deref` for the same operation. +```rust +let l1: Length = 5.0 * meters; +let l2: Length = 10.0 * kilometers; +let ratio_value: f64 = (l1 / l2).value(); +let ratio_deref: f64 = *(l1 / l2); +assert_eq!(ratio_value, ratio_deref); +``` +If absolutely required, `.value_unchecked()` provides access to the underlying storage type for all quantities. This is not unit-safe since the return value will depend on the unit system! +```rust +let length: Length = 5.0 * kilometers; +let value: f64 = length.value_unchecked(); // In this case, returns 5000.0 +``` +`Debug` is implemented and will print the quantity in its representation of the "closest" unit. For example `Length::meters(100.0)` would be debug printed as `0.1 km`. If printing in a specific unit is required, conversion methods are available for each unit (such as `Length::in_meters`). * Similarly, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This is also not unit-safe. Some other, more complex operations are also allowed: diff --git a/src/lib.rs b/src/lib.rs index 8be9330..a220e23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,13 +56,13 @@ //! Physical quantities are represented by the `Quantity` struct, where `S` is the underlying storage type (`f32`, `f64`, ...) and `D` is the dimension of the quantity. //! `Quantity` should behave like its underlying storage type whenever allowed by the dimensions. //! ## Arithmetics -//! * Addition and subtraction of two quantities with the same storage type is allowed if the dimensions match. +//! Addition and subtraction of two quantities is allowed if the dimensions match. //! ``` //! # use diman::si::dimensions::{Length}; //! # use diman::si::units::{kilometers, meters}; //! let l: Length = 5.0 * meters + 10.0 * kilometers; //! ``` -//! * Multiplication and division of two quantities with the same storage type produces a new quantity: +//! Multiplication and division of two quantities produces a new quantity: //! ``` //! # #![feature(generic_const_exprs)] //! # use diman::si::dimensions::{Length, Time, Velocity}; @@ -71,21 +71,45 @@ //! let t: Time = 2.0 * seconds; //! let v: Velocity = l / t; //! ``` -//! * Addition of `Quantity` and `Float` is possible if and only if `D` is dimensionless. +//! Addition and subtraction of a `Quantity` and a storage type is possible if and only if `D` is dimensionless. //! ``` //! # #![feature(generic_const_exprs)] -//! # use diman::si::dimensions::{Dimensionless, Length}; +//! # use diman::si::dimensions::{Length}; //! # use diman::si::units::{kilometers, meters}; //! let l1: Length = 5.0 * meters; //! let l2: Length = 10.0 * kilometers; //! let x = l1 / l2 - 0.5; -//! let y = 0.5 + l1 / l2; +//! let y = 0.5 - l1 / l2; +//! ``` +//! `Quantity` implements the dimensionless methods of `S`, such as `sin`, `cos`, etc. for dimensionless quantities. +//! ``` +//! # #![feature(generic_const_exprs)] +//! # use diman::si::dimensions::{Length}; +//! # use diman::si::units::{kilometers, meters}; +//! let l1: Length = 5.0 * meters; +//! let l2: Length = 10.0 * kilometers; +//! let angle_radians = (l1 / l2).asin(); +//! ``` +//! For dimensionless quantities, `.value()` provides access to the underlying storage types. Alternatively, dimensionless quantities also implement `Deref` for the same operation. +//! ``` +//! # #![feature(generic_const_exprs)] +//! # use diman::si::dimensions::{Length}; +//! # use diman::si::units::{kilometers, meters}; +//! let l1: Length = 5.0 * meters; +//! let l2: Length = 10.0 * kilometers; +//! let ratio_value: f64 = (l1 / l2).value(); +//! let ratio_deref: f64 = *(l1 / l2); +//! assert_eq!(ratio_value, ratio_deref); +//! ``` +//! If absolutely required, `.value_unchecked()` provides access to the underlying storage type for all quantities. This is not unit-safe since the return value will depend on the unit system! +//! ``` +//! # #![feature(generic_const_exprs)] +//! # use diman::si::dimensions::{Dimensionless, Length}; +//! # use diman::si::units::{kilometers, meters}; +//! let length: Length = 5.0 * kilometers; +//! let value: f64 = length.value_unchecked(); // In this case, returns 5000.0 //! ``` -//! * `Quantity` implements the dimensionless methods of `S`, such as `abs` for dimensionless quantities. -//! * It implements `Deref` to `S` if and only if `D` is dimensionless. -//! * `Debug` is implemented and will print the quantity in its representation of the "closest" unit. For example `Length::meters(100.0)` would be debug printed as `0.1 km`. If printing in a specific unit is required, conversion methods are available for each unit (such as `Length::in_meters`). -//! * `.value()` provides access to the underlying storage type of a dimensionless quantity. -//! * `.value_unchecked()` provides access to the underlying storage type for all quantities if absolutely required. This is not unit-safe since the value will depend on the unit system! +//! `Debug` is implemented and will print the quantity in its representation of the "closest" unit. For example `Length::meters(100.0)` would be debug printed as `0.1 km`. If printing in a specific unit is required, conversion methods are available for each unit (such as `Length::in_meters`). //! * Similarly, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This is also not unit-safe. //! //! Some other, more complex operations are also allowed: From 5ab310fd1c3b576c880e1f4116e46a5380c1081f Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 25 Feb 2024 10:53:50 +0100 Subject: [PATCH 30/40] document debug, powi/sqrt/cbrt/powf --- README.md | 38 ++++++++++++++++++++++++++++---------- src/lib.rs | 53 ++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 68 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index a91bf88..fd43718 100644 --- a/README.md +++ b/README.md @@ -90,19 +90,37 @@ assert_eq!(ratio_value, ratio_deref); If absolutely required, `.value_unchecked()` provides access to the underlying storage type for all quantities. This is not unit-safe since the return value will depend on the unit system! ```rust let length: Length = 5.0 * kilometers; -let value: f64 = length.value_unchecked(); // In this case, returns 5000.0 +let value: f64 = length.value_unchecked(); +assert_eq!(value, 5000.0); // This only holds in SI units! ``` -`Debug` is implemented and will print the quantity in its representation of the "closest" unit. For example `Length::meters(100.0)` would be debug printed as `0.1 km`. If printing in a specific unit is required, conversion methods are available for each unit (such as `Length::in_meters`). -* Similarly, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This is also not unit-safe. - -Some other, more complex operations are also allowed: +Similarly, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This operation is also not unit-safe! ```rust -let x = 3.0f64 * meters; -let vol = x.cubed(); -assert_eq!(vol, 27.0 * cubic_meters) +let length: Length = Length::new_unchecked(5000.0); +assert_eq!(length, 5.0 * kilometers); // This only holds in SI units! +``` +`Debug` is implemented and will print the quantity in its base representation. +```rust +let length: Length = 5.0 * kilometers; +let time: Time = 1.0 * seconds; +assert_eq!(format!("{:?}", length / time), "5000 m s^-1") +``` +Exponentiation and related operations are supported via `squared`, `cubed`, `powi`, `sqrt`, `cbrt`: +```rust +let length = 2.0f64 * meters; +let area = length.squared(); +assert_eq!(area, 4.0 * square_meters); +assert_eq!(area.sqrt(), length); +let vol = length.cubed(); +assert_eq!(vol, 8.0 * cubic_meters); +assert_eq!(vol.cbrt(), length); +let questionable = length.powi::<4>(); +``` +Note that unlike its float equivalent, `powi` receives its exponent as a generic instead of as a normal function argument. Exponentiation of dimensionful quantities with an non-constant integer is not supported, since the compiler cannot infer the dimension of the return type. However, dimensionless quantities can be raised to arbitrary powers using `powf`: +```rust +let l1 = 2.0f64 * meters; +let l2 = 5.0f64 * kilometers; +let x = (l1 / l2).powf(2.71); ``` -This includes `squared`, `cubed`, `sqrt`, `cbrt` as well as `powi`. - # Design For example, in order to represent the [SI system of units](https://www.nist.gov/pml/owm/metric-si/si-units), the quantity type would be defined using the `unit_system!` macro as follows: diff --git a/src/lib.rs b/src/lib.rs index a220e23..e8f7e85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,24 +104,51 @@ //! If absolutely required, `.value_unchecked()` provides access to the underlying storage type for all quantities. This is not unit-safe since the return value will depend on the unit system! //! ``` //! # #![feature(generic_const_exprs)] -//! # use diman::si::dimensions::{Dimensionless, Length}; -//! # use diman::si::units::{kilometers, meters}; +//! # use diman::si::dimensions::{Length}; +//! # use diman::si::units::{kilometers}; //! let length: Length = 5.0 * kilometers; -//! let value: f64 = length.value_unchecked(); // In this case, returns 5000.0 +//! let value: f64 = length.value_unchecked(); +//! assert_eq!(value, 5000.0); // This only holds in SI units! //! ``` -//! `Debug` is implemented and will print the quantity in its representation of the "closest" unit. For example `Length::meters(100.0)` would be debug printed as `0.1 km`. If printing in a specific unit is required, conversion methods are available for each unit (such as `Length::in_meters`). -//! * Similarly, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This is also not unit-safe. -//! -//! Some other, more complex operations are also allowed: +//! Similarly, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This operation is also not unit-safe! //! ``` +//! # #![feature(generic_const_exprs)] +//! # use diman::si::dimensions::{Length}; +//! # use diman::si::units::{kilometers}; +//! let length: Length = Length::new_unchecked(5000.0); +//! assert_eq!(length, 5.0 * kilometers); // This only holds in SI units! +//! ``` +//! `Debug` is implemented and will print the quantity in its base representation. +//! ``` +//! # #![feature(generic_const_exprs)] +//! # use diman::si::dimensions::{Length, Time}; +//! # use diman::si::units::{kilometers, seconds}; +//! let length: Length = 5.0 * kilometers; +//! let time: Time = 1.0 * seconds; +//! assert_eq!(format!("{:?}", length / time), "5000 m s^-1") +//! ``` +//! Exponentiation and related operations are supported via `squared`, `cubed`, `powi`, `sqrt`, `cbrt`: +//! ``` +//! # use diman::si::dimensions::{Length}; +//! # use diman::si::units::{meters, cubic_meters, square_meters}; +//! let length = 2.0f64 * meters; +//! let area = length.squared(); +//! assert_eq!(area, 4.0 * square_meters); +//! assert_eq!(area.sqrt(), length); +//! let vol = length.cubed(); +//! assert_eq!(vol, 8.0 * cubic_meters); +//! assert_eq!(vol.cbrt(), length); +//! let questionable = length.powi::<4>(); +//! ``` +//! Note that unlike its float equivalent, `powi` receives its exponent as a generic instead of as a normal function argument. Exponentiation of dimensionful quantities with an non-constant integer is not supported, since the compiler cannot infer the dimension of the return type. However, dimensionless quantities can be raised to arbitrary powers using `powf`: +//! ``` +//! # #![feature(generic_const_exprs)] //! # use diman::si::dimensions::{Length, Volume}; -//! # use diman::si::units::{meters,cubic_meters}; -//! let x = 3.0f64 * meters; -//! let vol = x.cubed(); -//! assert_eq!(vol, 27.0 * cubic_meters) +//! # use diman::si::units::{meters, kilometers}; +//! let l1 = 2.0f64 * meters; +//! let l2 = 5.0f64 * kilometers; +//! let x = (l1 / l2).powf(2.71); //! ``` -//! This includes `squared`, `cubed`, `sqrt`, `cbrt` as well as `powi`. -//! //! //! # Design //! For example, in order to represent the [SI system of units](https://www.nist.gov/pml/owm/metric-si/si-units), the quantity type would be defined using the `unit_system!` macro as follows: From d3a17b7cc0a8fc6f9a3475fbfffc04fa4ced28b2 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 25 Feb 2024 11:20:48 +0100 Subject: [PATCH 31/40] add doc on value_unchecked and creation --- README.md | 58 ++++++++++++++++++++++++++++-------------- src/lib.rs | 74 ++++++++++++++++++++++++++++++++++++------------------ 2 files changed, 89 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index fd43718..03af1ae 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,8 @@ If you cannot use unstable Rust for your project or require a stable library, co # The `Quantity` type Physical quantities are represented by the `Quantity` struct, where `S` is the underlying storage type (`f32`, `f64`, ...) and `D` is the dimension of the quantity. `Quantity` should behave like its underlying storage type whenever allowed by the dimensions. -## Arithmetics + +## Arithmetics and math Addition and subtraction of two quantities is allowed if the dimensions match. ```rust let l: Length = 5.0 * meters + 10.0 * kilometers; @@ -79,6 +80,32 @@ let l1: Length = 5.0 * meters; let l2: Length = 10.0 * kilometers; let angle_radians = (l1 / l2).asin(); ``` +Exponentiation and related operations are supported via `squared`, `cubed`, `powi`, `sqrt`, `cbrt`: +```rust +let length = 2.0f64 * meters; +let area = length.squared(); +assert_eq!(area, 4.0 * square_meters); +assert_eq!(area.sqrt(), length); +let vol = length.cubed(); +assert_eq!(vol, 8.0 * cubic_meters); +assert_eq!(vol.cbrt(), length); +let questionable = length.powi::<4>(); +``` +Note that unlike its float equivalent, `powi` receives its exponent as a generic instead of as a normal function argument. Exponentiation of dimensionful quantities with an non-constant integer is not supported, since the compiler cannot infer the dimension of the return type. However, dimensionless quantities can be raised to arbitrary powers using `powf`: +```rust +let l1 = 2.0f64 * meters; +let l2 = 5.0f64 * kilometers; +let x = (l1 / l2).powf(2.71); +``` +## Creation and conversion +New quantities can be created either by multiplying with a unit, or by calling the `.new` function on the unit: +```rust +let l1 = 2.0 * meters; +let l2 = meters.new(2.0); +assert_eq!(l1, l2); +``` +For a full list of the units supported by dimans `SI` module, see [the definitions](src/si.rs). + For dimensionless quantities, `.value()` provides access to the underlying storage types. Alternatively, dimensionless quantities also implement `Deref` for the same operation. ```rust let l1: Length = 5.0 * meters; @@ -93,34 +120,27 @@ let length: Length = 5.0 * kilometers; let value: f64 = length.value_unchecked(); assert_eq!(value, 5000.0); // This only holds in SI units! ``` -Similarly, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This operation is also not unit-safe! +Similarly, if absolutely required, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This operation is also not unit-safe! ```rust let length: Length = Length::new_unchecked(5000.0); assert_eq!(length, 5.0 * kilometers); // This only holds in SI units! ``` +The combination of `value_unchecked` and `new_unchecked` comes in handy when using third party libraries that only takes the raw storage type as argument. As an example, suppose we have a function `foo` that takes a `Vec` and returns a `Vec`, and suppose it sorts the numbers or does some other unit safe operation. Then we could reasonably write: +```rust +let lengths: Vec> = vec![1.0 * meters, 2.0 * kilometers, 3.0 * meters, 4.0 * kilometers]; +let unchecked = lengths + .into_iter() + .map(|x| x.value_unchecked()) + .collect(); +let fooed = foo(unchecked); +let result: Vec<_> = fooed.into_iter().map(|x| Length::new_unchecked(x)).collect(); +``` `Debug` is implemented and will print the quantity in its base representation. ```rust let length: Length = 5.0 * kilometers; let time: Time = 1.0 * seconds; assert_eq!(format!("{:?}", length / time), "5000 m s^-1") ``` -Exponentiation and related operations are supported via `squared`, `cubed`, `powi`, `sqrt`, `cbrt`: -```rust -let length = 2.0f64 * meters; -let area = length.squared(); -assert_eq!(area, 4.0 * square_meters); -assert_eq!(area.sqrt(), length); -let vol = length.cubed(); -assert_eq!(vol, 8.0 * cubic_meters); -assert_eq!(vol.cbrt(), length); -let questionable = length.powi::<4>(); -``` -Note that unlike its float equivalent, `powi` receives its exponent as a generic instead of as a normal function argument. Exponentiation of dimensionful quantities with an non-constant integer is not supported, since the compiler cannot infer the dimension of the return type. However, dimensionless quantities can be raised to arbitrary powers using `powf`: -```rust -let l1 = 2.0f64 * meters; -let l2 = 5.0f64 * kilometers; -let x = (l1 / l2).powf(2.71); -``` # Design For example, in order to represent the [SI system of units](https://www.nist.gov/pml/owm/metric-si/si-units), the quantity type would be defined using the `unit_system!` macro as follows: diff --git a/src/lib.rs b/src/lib.rs index e8f7e85..8c5682c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,8 @@ //! # The `Quantity` type //! Physical quantities are represented by the `Quantity` struct, where `S` is the underlying storage type (`f32`, `f64`, ...) and `D` is the dimension of the quantity. //! `Quantity` should behave like its underlying storage type whenever allowed by the dimensions. -//! ## Arithmetics +//! +//! ## Arithmetics and math //! Addition and subtraction of two quantities is allowed if the dimensions match. //! ``` //! # use diman::si::dimensions::{Length}; @@ -90,6 +91,38 @@ //! let l2: Length = 10.0 * kilometers; //! let angle_radians = (l1 / l2).asin(); //! ``` +//! Exponentiation and related operations are supported via `squared`, `cubed`, `powi`, `sqrt`, `cbrt`: +//! ``` +//! # use diman::si::dimensions::{Length}; +//! # use diman::si::units::{meters, cubic_meters, square_meters}; +//! let length = 2.0f64 * meters; +//! let area = length.squared(); +//! assert_eq!(area, 4.0 * square_meters); +//! assert_eq!(area.sqrt(), length); +//! let vol = length.cubed(); +//! assert_eq!(vol, 8.0 * cubic_meters); +//! assert_eq!(vol.cbrt(), length); +//! let questionable = length.powi::<4>(); +//! ``` +//! Note that unlike its float equivalent, `powi` receives its exponent as a generic instead of as a normal function argument. Exponentiation of dimensionful quantities with an non-constant integer is not supported, since the compiler cannot infer the dimension of the return type. However, dimensionless quantities can be raised to arbitrary powers using `powf`: +//! ``` +//! # #![feature(generic_const_exprs)] +//! # use diman::si::dimensions::{Length, Volume}; +//! # use diman::si::units::{meters, kilometers}; +//! let l1 = 2.0f64 * meters; +//! let l2 = 5.0f64 * kilometers; +//! let x = (l1 / l2).powf(2.71); +//! ``` +//! ## Creation and conversion +//! New quantities can be created either by multiplying with a unit, or by calling the `.new` function on the unit: +//! ``` +//! # use diman::si::units::meters; +//! let l1 = 2.0 * meters; +//! let l2 = meters.new(2.0); +//! assert_eq!(l1, l2); +//! ``` +//! For a full list of the units supported by dimans `SI` module, see [the definitions](src/si.rs). +//! //! For dimensionless quantities, `.value()` provides access to the underlying storage types. Alternatively, dimensionless quantities also implement `Deref` for the same operation. //! ``` //! # #![feature(generic_const_exprs)] @@ -110,7 +143,7 @@ //! let value: f64 = length.value_unchecked(); //! assert_eq!(value, 5000.0); // This only holds in SI units! //! ``` -//! Similarly, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This operation is also not unit-safe! +//! Similarly, if absolutely required, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This operation is also not unit-safe! //! ``` //! # #![feature(generic_const_exprs)] //! # use diman::si::dimensions::{Length}; @@ -118,6 +151,21 @@ //! let length: Length = Length::new_unchecked(5000.0); //! assert_eq!(length, 5.0 * kilometers); // This only holds in SI units! //! ``` +//! The combination of `value_unchecked` and `new_unchecked` comes in handy when using third party libraries that only takes the raw storage type as argument. As an example, suppose we have a function `foo` that takes a `Vec` and returns a `Vec`, and suppose it sorts the numbers or does some other unit safe operation. Then we could reasonably write: +//! ``` +//! # use diman::si::dimensions::{Length}; +//! # use diman::si::units::{meters, kilometers}; +//! # fn foo(x: Vec) -> Vec { +//! # x +//! # } +//! let lengths: Vec> = vec![1.0 * meters, 2.0 * kilometers, 3.0 * meters, 4.0 * kilometers]; +//! let unchecked = lengths +//! .into_iter() +//! .map(|x| x.value_unchecked()) +//! .collect(); +//! let fooed = foo(unchecked); +//! let result: Vec<_> = fooed.into_iter().map(|x| Length::new_unchecked(x)).collect(); +//! ``` //! `Debug` is implemented and will print the quantity in its base representation. //! ``` //! # #![feature(generic_const_exprs)] @@ -127,28 +175,6 @@ //! let time: Time = 1.0 * seconds; //! assert_eq!(format!("{:?}", length / time), "5000 m s^-1") //! ``` -//! Exponentiation and related operations are supported via `squared`, `cubed`, `powi`, `sqrt`, `cbrt`: -//! ``` -//! # use diman::si::dimensions::{Length}; -//! # use diman::si::units::{meters, cubic_meters, square_meters}; -//! let length = 2.0f64 * meters; -//! let area = length.squared(); -//! assert_eq!(area, 4.0 * square_meters); -//! assert_eq!(area.sqrt(), length); -//! let vol = length.cubed(); -//! assert_eq!(vol, 8.0 * cubic_meters); -//! assert_eq!(vol.cbrt(), length); -//! let questionable = length.powi::<4>(); -//! ``` -//! Note that unlike its float equivalent, `powi` receives its exponent as a generic instead of as a normal function argument. Exponentiation of dimensionful quantities with an non-constant integer is not supported, since the compiler cannot infer the dimension of the return type. However, dimensionless quantities can be raised to arbitrary powers using `powf`: -//! ``` -//! # #![feature(generic_const_exprs)] -//! # use diman::si::dimensions::{Length, Volume}; -//! # use diman::si::units::{meters, kilometers}; -//! let l1 = 2.0f64 * meters; -//! let l2 = 5.0f64 * kilometers; -//! let x = (l1 / l2).powf(2.71); -//! ``` //! //! # Design //! For example, in order to represent the [SI system of units](https://www.nist.gov/pml/owm/metric-si/si-units), the quantity type would be defined using the `unit_system!` macro as follows: From 5951a3cb228de40724426572e0814b25f739b476 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 25 Feb 2024 11:26:44 +0100 Subject: [PATCH 32/40] add doc on value_in --- README.md | 7 +++++++ src/lib.rs | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/README.md b/README.md index 03af1ae..940ffef 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,11 @@ assert_eq!(l1, l2); ``` For a full list of the units supported by dimans `SI` module, see [the definitions](src/si.rs). +Conversion into the underlying storage type can be done using the `value_in` function: +```rust +let length = 2.0f64 * kilometers; +assert_eq!(format!("{} m", length.value_in(meters)), "2000 m"); +``` For dimensionless quantities, `.value()` provides access to the underlying storage types. Alternatively, dimensionless quantities also implement `Deref` for the same operation. ```rust let l1: Length = 5.0 * meters; @@ -114,6 +119,7 @@ let ratio_value: f64 = (l1 / l2).value(); let ratio_deref: f64 = *(l1 / l2); assert_eq!(ratio_value, ratio_deref); ``` +## Unchecked creation and conversion If absolutely required, `.value_unchecked()` provides access to the underlying storage type for all quantities. This is not unit-safe since the return value will depend on the unit system! ```rust let length: Length = 5.0 * kilometers; @@ -135,6 +141,7 @@ let unchecked = lengths let fooed = foo(unchecked); let result: Vec<_> = fooed.into_iter().map(|x| Length::new_unchecked(x)).collect(); ``` +## Debug `Debug` is implemented and will print the quantity in its base representation. ```rust let length: Length = 5.0 * kilometers; diff --git a/src/lib.rs b/src/lib.rs index 8c5682c..bf6a930 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,6 +123,12 @@ //! ``` //! For a full list of the units supported by dimans `SI` module, see [the definitions](src/si.rs). //! +//! Conversion into the underlying storage type can be done using the `value_in` function: +//! ``` +//! # use diman::si::units::{kilometers, meters}; +//! let length = 2.0f64 * kilometers; +//! assert_eq!(format!("{} m", length.value_in(meters)), "2000 m"); +//! ``` //! For dimensionless quantities, `.value()` provides access to the underlying storage types. Alternatively, dimensionless quantities also implement `Deref` for the same operation. //! ``` //! # #![feature(generic_const_exprs)] @@ -134,6 +140,7 @@ //! let ratio_deref: f64 = *(l1 / l2); //! assert_eq!(ratio_value, ratio_deref); //! ``` +//! ## Unchecked creation and conversion //! If absolutely required, `.value_unchecked()` provides access to the underlying storage type for all quantities. This is not unit-safe since the return value will depend on the unit system! //! ``` //! # #![feature(generic_const_exprs)] @@ -166,6 +173,7 @@ //! let fooed = foo(unchecked); //! let result: Vec<_> = fooed.into_iter().map(|x| Length::new_unchecked(x)).collect(); //! ``` +//! ## Debug //! `Debug` is implemented and will print the quantity in its base representation. //! ``` //! # #![feature(generic_const_exprs)] From 85e4eb190c98daae2dc96d79c9d390d7bac4d48f Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 25 Feb 2024 14:13:36 +0100 Subject: [PATCH 33/40] add RuntimeUnit to allow multiplication / division of units --- .../src/codegen/unit_type.rs | 140 ++++++++++++++---- src/lib.rs | 13 +- 2 files changed, 127 insertions(+), 26 deletions(-) diff --git a/crates/diman_unit_system/src/codegen/unit_type.rs b/crates/diman_unit_system/src/codegen/unit_type.rs index b4250de..1a3e20d 100644 --- a/crates/diman_unit_system/src/codegen/unit_type.rs +++ b/crates/diman_unit_system/src/codegen/unit_type.rs @@ -11,6 +11,7 @@ impl Codegen { let storage_type_impls = self.gen_unit_trait_impls_for_storage_types(); quote! { pub struct Unit; + pub struct RuntimeUnit(Magnitude); #trait_impls #storage_type_impls } @@ -19,31 +20,27 @@ impl Codegen { fn gen_unit_trait_impls(&self) -> TokenStream { quote! { use core::ops::{Mul, Div}; - // The following would be possible if - // Unit::mul / Unit::div could be made const. - // // Unit * Unit - // impl - // Mul> for Unit - // where - // Unit<{ DL.add(DR) }, { FL.mul(FR) }>:, - // { - // type Output = Unit<{ DL.add(DR) }, { FL.mul(FR) }>; - // fn mul(self, _: Unit) -> Self::Output { - // Unit - // } - // } - - // // Unit / Unit - // impl - // Div> for Unit - // where - // Unit<{ DL.sub(DR) }, { FL.div(FR) }>:, - // { - // type Output = Unit<{ DL.sub(DR) }, { FL.div(FR) }>; - // fn div(self, _: Unit) -> Self::Output { - // Unit - // } - // } + // Unit * Unit = RuntimeUnit + impl + Mul> for Unit + where RuntimeUnit<{ DL.add(DR) }>: + { + type Output = RuntimeUnit<{ DL.add(DR) }>; + fn mul(self, _: Unit) -> Self::Output { + RuntimeUnit( FL.mul(FR) ) + } + } + + // Unit / Unit = RuntimeUnit + impl + Div> for Unit + where RuntimeUnit<{ DL.sub(DR) }>: + { + type Output = RuntimeUnit<{ DL.sub(DR) }>; + fn div(self, _: Unit) -> Self::Output { + RuntimeUnit( FL.div(FR) ) + } + } // Unit * Quantity impl Mul> @@ -105,6 +102,67 @@ impl Codegen { Quantity(val * F) } } + + // RuntimeUnit * Quantity + impl Mul> + for RuntimeUnit
+ where + S: Mul, + Quantity<(), { DL.add(DR) }>:, + { + type Output = Quantity; + fn mul(self, x: Quantity) -> Self::Output { + Quantity(x.value_unchecked() * self.0) + } + } + + // Quantity * RuntimeUnit + impl Mul> + for Quantity + where + S: Mul, + Quantity<(), { DL.add(DR) }>:, + { + type Output = Quantity; + fn mul(self, unit: RuntimeUnit) -> Self::Output { + Quantity(self.value_unchecked() * unit.0) + } + } + + // RuntimeUnit / Quantity + impl Div> + for RuntimeUnit
+ where + S: Div, + Quantity<(), { DL.sub(DR) }>:, + { + type Output = Quantity; + fn div(self, x: Quantity) -> Self::Output { + Quantity(x.value_unchecked() / self.0) + } + } + + // Quantity / RuntimeUnit + impl Div> + for Quantity + where + S: Div, + Quantity<(), { DL.sub(DR) }>:, + { + type Output = Quantity; + fn div(self, unit: RuntimeUnit) -> Self::Output { + Quantity(self.value_unchecked() / unit.0) + } + } + + impl RuntimeUnit { + pub fn new(self, val: S) -> Quantity + where + S: Mul, + { + Quantity(val * self.0) + } + } } } @@ -158,6 +216,38 @@ impl Codegen { Quantity(#into / f) } } + + // X * RuntimeUnit + impl Mul> for #name { + type Output = Quantity<#name, D>; + fn mul(self, unit: RuntimeUnit) -> Self::Output { + Quantity(self * unit.0.#conversion_to_float()) + } + } + + // X / RuntimeUnit + impl Div> for #name { + type Output = Quantity<#name, D>; + fn div(self, unit: RuntimeUnit) -> Self::Output { + Quantity(self / unit.0.#conversion_to_float()) + } + } + + // RuntimeUnit * X + impl Mul<#name> for RuntimeUnit { + type Output = Quantity<#name, D>; + fn mul(self, f: #name) -> Self::Output { + Quantity(self.0.#conversion_to_float() * f) + } + } + + // RuntimeUnit / X + impl Div<#name> for RuntimeUnit { + type Output = Quantity<#name, D>; + fn div(self, f: #name) -> Self::Output { + Quantity(self.0.#conversion_to_float() / f) + } + } }; res } diff --git a/src/lib.rs b/src/lib.rs index bf6a930..37fa48a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,12 +116,23 @@ //! ## Creation and conversion //! New quantities can be created either by multiplying with a unit, or by calling the `.new` function on the unit: //! ``` -//! # use diman::si::units::meters; +//! # use diman::si::units::{kilometers, meters, hour}; //! let l1 = 2.0 * meters; //! let l2 = meters.new(2.0); //! assert_eq!(l1, l2); //! ``` //! For a full list of the units supported by dimans `SI` module, see [the definitions](src/si.rs). +//! Composite units can be defined on the spot via multiplication/division of units: +//! ``` +//! # #![feature(generic_const_exprs)] +//! # use diman::si::units::{kilometers, meters, hour, meters_per_second}; +//! let v1 = (kilometers / hour).new(3.6); +//! let v2 = 3.6 * kilometers / hour; +//! assert_eq!(v1, 1.0 * meters_per_second); +//! assert_eq!(v2, 1.0 * meters_per_second); +//! ``` +//! Note that at the moment, the creation of quantities via units defined in this composite way incurs +//! a small performance overhead compared to creation from just a single unit (which is just a single multiplication). This will be fixed once [const_fn_floating_point_arithmetic](https://github.com/rust-lang/rust/issues/57241) or a similar feature is stabilized. //! //! Conversion into the underlying storage type can be done using the `value_in` function: //! ``` From b474fe038da0545ae9ea1038ec1a38c133ced0df Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 25 Feb 2024 14:24:30 +0100 Subject: [PATCH 34/40] take both Unit and RuntimeUnit in value_in --- README.md | 22 +++++++++++++++---- .../src/codegen/quantity_type.rs | 4 ++-- .../src/codegen/unit_type.rs | 12 ++++++++++ src/lib.rs | 15 +++++++++---- 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 940ffef..648f369 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Physical quantities are represented by the `Quantity` struct, where `S` is `Quantity` should behave like its underlying storage type whenever allowed by the dimensions. ## Arithmetics and math -Addition and subtraction of two quantities is allowed if the dimensions match. +Addition and subtraction of two quantities is allowed if the dimensions match: ```rust let l: Length = 5.0 * meters + 10.0 * kilometers; ``` @@ -67,14 +67,14 @@ let l: Length = 5.0 * meters; let t: Time = 2.0 * seconds; let v: Velocity = l / t; ``` -Addition and subtraction of a `Quantity` and a storage type is possible if and only if `D` is dimensionless. +Addition and subtraction of a `Quantity` and a storage type is possible if and only if `D` is dimensionless: ```rust let l1: Length = 5.0 * meters; let l2: Length = 10.0 * kilometers; let x = l1 / l2 - 0.5; let y = 0.5 - l1 / l2; ``` -`Quantity` implements the dimensionless methods of `S`, such as `sin`, `cos`, etc. for dimensionless quantities. +`Quantity` implements the dimensionless methods of `S`, such as `sin`, `cos`, etc. for dimensionless quantities: ```rust let l1: Length = 5.0 * meters; let l2: Length = 10.0 * kilometers; @@ -89,7 +89,7 @@ assert_eq!(area.sqrt(), length); let vol = length.cubed(); assert_eq!(vol, 8.0 * cubic_meters); assert_eq!(vol.cbrt(), length); -let questionable = length.powi::<4>(); +let foo = length.powi::<4>(); ``` Note that unlike its float equivalent, `powi` receives its exponent as a generic instead of as a normal function argument. Exponentiation of dimensionful quantities with an non-constant integer is not supported, since the compiler cannot infer the dimension of the return type. However, dimensionless quantities can be raised to arbitrary powers using `powf`: ```rust @@ -105,12 +105,26 @@ let l2 = meters.new(2.0); assert_eq!(l1, l2); ``` For a full list of the units supported by dimans `SI` module, see [the definitions](src/si.rs). +Composite units can be defined on the spot via multiplication/division of units: +```rust +let v1 = (kilometers / hour).new(3.6); +let v2 = 3.6 * kilometers / hour; +assert_eq!(v1, 1.0 * meters_per_second); +assert_eq!(v2, 1.0 * meters_per_second); +``` +Note that at the moment, the creation of quantities via units defined in this composite way incurs +a small performance overhead compared to creation from just a single unit (which is just a single multiplication). This will be fixed once [const_fn_floating_point_arithmetic](https://github.com/rust-lang/rust/issues/57241) or a similar feature is stabilized. Conversion into the underlying storage type can be done using the `value_in` function: ```rust let length = 2.0f64 * kilometers; assert_eq!(format!("{} m", length.value_in(meters)), "2000 m"); ``` +This also works for composite units: +```rust +let vel = 10.0f64 * meters_per_second; +assert_eq!(format!("{} km/h", vel.value_in(kilometers / hour)), "36 km/h"); +``` For dimensionless quantities, `.value()` provides access to the underlying storage types. Alternatively, dimensionless quantities also implement `Deref` for the same operation. ```rust let l1: Length = 5.0 * meters; diff --git a/crates/diman_unit_system/src/codegen/quantity_type.rs b/crates/diman_unit_system/src/codegen/quantity_type.rs index 4ee24d4..e7a8d8e 100644 --- a/crates/diman_unit_system/src/codegen/quantity_type.rs +++ b/crates/diman_unit_system/src/codegen/quantity_type.rs @@ -61,8 +61,8 @@ impl Codegen { where S: core::ops::Div + core::fmt::Debug, { - pub fn value_in(self, _: Unit) -> S { - self.value_unchecked() / R + pub fn value_in>(self, a: A) -> S { + self.value_unchecked() / a.into() } } diff --git a/crates/diman_unit_system/src/codegen/unit_type.rs b/crates/diman_unit_system/src/codegen/unit_type.rs index 1a3e20d..3475966 100644 --- a/crates/diman_unit_system/src/codegen/unit_type.rs +++ b/crates/diman_unit_system/src/codegen/unit_type.rs @@ -163,6 +163,18 @@ impl Codegen { Quantity(val * self.0) } } + + impl From> for Magnitude { + fn from(_: Unit) -> Magnitude { + F + } + } + + impl From> for Magnitude { + fn from(unit: RuntimeUnit) -> Magnitude { + unit.0 + } + } } } diff --git a/src/lib.rs b/src/lib.rs index 37fa48a..ec843d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,7 +57,7 @@ //! `Quantity` should behave like its underlying storage type whenever allowed by the dimensions. //! //! ## Arithmetics and math -//! Addition and subtraction of two quantities is allowed if the dimensions match. +//! Addition and subtraction of two quantities is allowed if the dimensions match: //! ``` //! # use diman::si::dimensions::{Length}; //! # use diman::si::units::{kilometers, meters}; @@ -72,7 +72,7 @@ //! let t: Time = 2.0 * seconds; //! let v: Velocity = l / t; //! ``` -//! Addition and subtraction of a `Quantity` and a storage type is possible if and only if `D` is dimensionless. +//! Addition and subtraction of a `Quantity` and a storage type is possible if and only if `D` is dimensionless: //! ``` //! # #![feature(generic_const_exprs)] //! # use diman::si::dimensions::{Length}; @@ -82,7 +82,7 @@ //! let x = l1 / l2 - 0.5; //! let y = 0.5 - l1 / l2; //! ``` -//! `Quantity` implements the dimensionless methods of `S`, such as `sin`, `cos`, etc. for dimensionless quantities. +//! `Quantity` implements the dimensionless methods of `S`, such as `sin`, `cos`, etc. for dimensionless quantities: //! ``` //! # #![feature(generic_const_exprs)] //! # use diman::si::dimensions::{Length}; @@ -102,7 +102,7 @@ //! let vol = length.cubed(); //! assert_eq!(vol, 8.0 * cubic_meters); //! assert_eq!(vol.cbrt(), length); -//! let questionable = length.powi::<4>(); +//! let foo = length.powi::<4>(); //! ``` //! Note that unlike its float equivalent, `powi` receives its exponent as a generic instead of as a normal function argument. Exponentiation of dimensionful quantities with an non-constant integer is not supported, since the compiler cannot infer the dimension of the return type. However, dimensionless quantities can be raised to arbitrary powers using `powf`: //! ``` @@ -140,6 +140,13 @@ //! let length = 2.0f64 * kilometers; //! assert_eq!(format!("{} m", length.value_in(meters)), "2000 m"); //! ``` +//! This also works for composite units: +//! ``` +//! # #![feature(generic_const_exprs)] +//! # use diman::si::units::{kilometers, meters_per_second, hour}; +//! let vel = 10.0f64 * meters_per_second; +//! assert_eq!(format!("{} km/h", vel.value_in(kilometers / hour)), "36 km/h"); +//! ``` //! For dimensionless quantities, `.value()` provides access to the underlying storage types. Alternatively, dimensionless quantities also implement `Deref` for the same operation. //! ``` //! # #![feature(generic_const_exprs)] From 07a1f0de0a7828582e0c08ffba8590d8e7ceb89a Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 25 Feb 2024 14:29:24 +0100 Subject: [PATCH 35/40] minor fixes in readme --- README.md | 79 +++++++++++++++++++++-------------------------- src/lib.rs | 90 +++++++++++++++++++++++------------------------------- 2 files changed, 74 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 648f369..381c4aa 100644 --- a/README.md +++ b/README.md @@ -134,26 +134,31 @@ let ratio_deref: f64 = *(l1 / l2); assert_eq!(ratio_value, ratio_deref); ``` ## Unchecked creation and conversion -If absolutely required, `.value_unchecked()` provides access to the underlying storage type for all quantities. This is not unit-safe since the return value will depend on the unit system! +If absolutely required, `.value_unchecked()` provides access to the underlying storage type for all quantities. This is **not unit-safe** since the return value will depend on the unit system! ```rust let length: Length = 5.0 * kilometers; let value: f64 = length.value_unchecked(); assert_eq!(value, 5000.0); // This only holds in SI units! ``` -Similarly, if absolutely required, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This operation is also not unit-safe! +Similarly, if absolutely required, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This operation is also **not unit-safe**! ```rust let length: Length = Length::new_unchecked(5000.0); assert_eq!(length, 5.0 * kilometers); // This only holds in SI units! ``` The combination of `value_unchecked` and `new_unchecked` comes in handy when using third party libraries that only takes the raw storage type as argument. As an example, suppose we have a function `foo` that takes a `Vec` and returns a `Vec`, and suppose it sorts the numbers or does some other unit safe operation. Then we could reasonably write: ```rust -let lengths: Vec> = vec![1.0 * meters, 2.0 * kilometers, 3.0 * meters, 4.0 * kilometers]; -let unchecked = lengths - .into_iter() - .map(|x| x.value_unchecked()) - .collect(); -let fooed = foo(unchecked); -let result: Vec<_> = fooed.into_iter().map(|x| Length::new_unchecked(x)).collect(); + let lengths: Vec> = vec![ + 1.0 * meters, + 2.0 * kilometers, + 3.0 * meters, + 4.0 * kilometers, + ]; + let unchecked = lengths.into_iter().map(|x| x.value_unchecked()).collect(); + let fooed = foo(unchecked); + let result: Vec<_> = fooed + .into_iter() + .map(|x| Length::new_unchecked(x)) + .collect(); ``` ## Debug `Debug` is implemented and will print the quantity in its base representation. @@ -163,8 +168,13 @@ let time: Time = 1.0 * seconds; assert_eq!(format!("{:?}", length / time), "5000 m s^-1") ``` -# Design - For example, in order to represent the [SI system of units](https://www.nist.gov/pml/owm/metric-si/si-units), the quantity type would be defined using the `unit_system!` macro as follows: +# Custom unit systems +## The `unit_system` macro +Diman also provides the `unit_system` macro for defining custom +unit systems for everything that is not covered by SI alone. The +macro will add a new quantity type and implement all the required +methods and traits to make it usable. +As an example, consider the following macro call: ```rust diman::unit_system!( quantity_type Quantity; @@ -173,26 +183,10 @@ diman::unit_system!( dimension Length; dimension Time; dimension Mass; - dimension Temperature; - dimension Current; - dimension AmountOfSubstance; - dimension LuminousIntensity; -); -``` -The first two statements imply that the macro should define a `Quantity` type, which is user-facing, and a `Dimension` type, which is used only internally and will surface in compiler error messages. -The macro will automatically implement all the required traits and methods for the `Quantity` type, such that addition and subtraction of two quantities is only allowed for quantities with the same `Dimension` type. During multiplication of two quantities, all the entries of the two dimensions are added. See below for a more comprehensive list of the implemented methods on `Quantity`. - -The `unit_system!` macro also allows defining derived dimensions and units: - -```rust -diman::unit_system!( - quantity_type Quantity; - dimension_type Dimension; - - dimension Length; - dimension Time; dimension Velocity = Length / Time; + dimension Frequency = 1 / Time; + dimension Energy = Mass * Velocity^2; #[prefix(kilo, milli)] #[base(Length)] @@ -206,28 +200,25 @@ diman::unit_system!( unit hours: Time = 3600 * seconds; unit meters_per_second: Velocity = meters / seconds; unit kilometers_per_hour: Velocity = kilometers / hours; - constant MY_FAVORITE_VELOCITY = 1000 * kilometers_per_hour; + constant SPEED_OF_LIGHT = 299792458 * meters_per_second; ); -fn fast_enough(x: Length, t: Time) { - let vel = x / t; - if vel > 1.0 * MY_FAVORITE_VELOCITY { - println!("{} m/s is definitely fast enough!", vel.value_in(meters_per_second)); - } +fn too_fast(x: Length, t: Time) -> bool { + x / t > 0.1f64 * SPEED_OF_LIGHT } -fast_enough(100.0 * kilometers, 0.3 * hours); +too_fast(100.0 * kilometers, 0.3 * hours); ``` -Here, `dimension` defines Quantities, which are concrete types, `unit` defines units, which are methods on the corresponding quantities and `constant` defines constants. -Dimensions without a right hand side are base dimensions (such as length, time, mass, temperature, ... in the SI system of units), whereas dimensions with a right hand side are derived dimensions. -The same thing holds for units - every unit is either a base unit for a given base dimension (denoted by the `#[base(...)]` attribute), or derived from base units and other derived units. Base units have the special property that the internal representation of the quantity will be in terms of the base unit (for example, a stored value `1.0` for a quantity with a `Length` dimension corresponds to `meter` in the above definitions). -Other than this, there are no differences between base dimensions and dimensions or base units and units and they can be treated equally in user code. -The macro also accepts more complex expressions such as `dimension Energy = Mass (Length / Time)^2`. -The definitions do not have to be in any specific order. +The macro accepts five different keywords: +1. `quantity_type` specifies the name of the quantity type. Required for compiler error messages to have something to point to. +2. `dimension_type` specifies the name of the dimension type. Required for compiler error messages to have something to point to. +3. `dimension` defines a new dimension which is a type. Dimensions without a right hand side are base dimensions (such as `Length` and `Time` in this example), whereas dimensions with a right hand side are derived dimensions (such as `Velocity` in this example). +4. `unit` defines a new units, which are methods on the corresponding quantities and `constant` defines constants. Units without a right-hand side are the base units to one specific base dimension, meaning that they are the unit that will internally be represented with a conversion factor of 1. Base units require the `#[base(...)]` attribute in order to specify which dimension they are the base unit of. Units with a right hand side are derived from other units. +5. `constant` defines a new constant. -# Prefixes +## SI Prefixes Unit prefixes can automatically be generated with the `#[prefix(...)]` attribute for unit statements. For example ```rust @@ -239,7 +230,7 @@ unit meters; will automatically generate the unit `meters` with symbol `m`, as well as `kilometers` and `millimeters` with symbols `km` and `mm` corresponding to `1e3 m` and `1e-3 m`. For simplicity, the attribute `#[metric_prefixes]` is provided, which will generate all metric prefixes from `atto-` to `exa-` automatically. -# Aliases +## Aliases Unit aliases can automatically be generated with the `#[alias(...)]` macro. For example ```rust #[alias(metres)] diff --git a/src/lib.rs b/src/lib.rs index ec843d0..ce4e236 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ //! ``` //! # #![feature(generic_const_exprs)] //! use diman::si::dimensions::{Length, Time, Velocity}; -//! use diman::si::units::{seconds, meters, kilometers, hours}; +//! use diman::si::units::{seconds, meters, kilometers, hours, hour}; //! //! fn get_velocity(x: Length, t: Time) -> Velocity { //! x / t @@ -13,9 +13,10 @@ //! let v2 = get_velocity(10.0 * meters, 1.0 * seconds); //! //! assert_eq!(v1, v2); +//! assert_eq!(format!("{} km/h", v1.value_in(kilometers / hour)), "36 km/h"); //! ``` //! -//! Let's try to add quantities with incompatible dimensions: +//! Diman prevents unit errors at compile time: //! ```compile_fail //! # use diman::si::units::{seconds, meters}; //! let time = 1.0 * seconds; @@ -159,7 +160,7 @@ //! assert_eq!(ratio_value, ratio_deref); //! ``` //! ## Unchecked creation and conversion -//! If absolutely required, `.value_unchecked()` provides access to the underlying storage type for all quantities. This is not unit-safe since the return value will depend on the unit system! +//! If absolutely required, `.value_unchecked()` provides access to the underlying storage type for all quantities. This is **not unit-safe** since the return value will depend on the unit system! //! ``` //! # #![feature(generic_const_exprs)] //! # use diman::si::dimensions::{Length}; @@ -168,7 +169,7 @@ //! let value: f64 = length.value_unchecked(); //! assert_eq!(value, 5000.0); // This only holds in SI units! //! ``` -//! Similarly, if absolutely required, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This operation is also not unit-safe! +//! Similarly, if absolutely required, new quantities can be constructed from storage types using `Quantity::new_unchecked`. This operation is also **not unit-safe**! //! ``` //! # #![feature(generic_const_exprs)] //! # use diman::si::dimensions::{Length}; @@ -183,13 +184,18 @@ //! # fn foo(x: Vec) -> Vec { //! # x //! # } -//! let lengths: Vec> = vec![1.0 * meters, 2.0 * kilometers, 3.0 * meters, 4.0 * kilometers]; -//! let unchecked = lengths -//! .into_iter() -//! .map(|x| x.value_unchecked()) -//! .collect(); -//! let fooed = foo(unchecked); -//! let result: Vec<_> = fooed.into_iter().map(|x| Length::new_unchecked(x)).collect(); +//! let lengths: Vec> = vec![ +//! 1.0 * meters, +//! 2.0 * kilometers, +//! 3.0 * meters, +//! 4.0 * kilometers, +//! ]; +//! let unchecked = lengths.into_iter().map(|x| x.value_unchecked()).collect(); +//! let fooed = foo(unchecked); +//! let result: Vec<_> = fooed +//! .into_iter() +//! .map(|x| Length::new_unchecked(x)) +//! .collect(); //! ``` //! ## Debug //! `Debug` is implemented and will print the quantity in its base representation. @@ -202,8 +208,13 @@ //! assert_eq!(format!("{:?}", length / time), "5000 m s^-1") //! ``` //! -//! # Design -//! For example, in order to represent the [SI system of units](https://www.nist.gov/pml/owm/metric-si/si-units), the quantity type would be defined using the `unit_system!` macro as follows: +//! # Custom unit systems +//! ## The `unit_system` macro +//! Diman also provides the `unit_system` macro for defining custom +//! unit systems for everything that is not covered by SI alone. The +//! macro will add a new quantity type and implement all the required +//! methods and traits to make it usable. +//! As an example, consider the following macro call: //! ``` //! # #![allow(incomplete_features)] //! # #![feature(generic_const_exprs, adt_const_params)] @@ -215,30 +226,10 @@ //! dimension Length; //! dimension Time; //! dimension Mass; -//! dimension Temperature; -//! dimension Current; -//! dimension AmountOfSubstance; -//! dimension LuminousIntensity; -//! ); -//! # } -//! ``` -//! The first two statements imply that the macro should define a `Quantity` type, which is user-facing, and a `Dimension` type, which is used only internally and will surface in compiler error messages. -//! The macro will automatically implement all the required traits and methods for the `Quantity` type, such that addition and subtraction of two quantities is only allowed for quantities with the same `Dimension` type. During multiplication of two quantities, all the entries of the two dimensions are added. See below for a more comprehensive list of the implemented methods on `Quantity`. -//! -//! The `unit_system!` macro also allows defining derived dimensions and units: -//! -//! ``` -//! # #![allow(incomplete_features)] -//! # #![feature(generic_const_exprs, adt_const_params)] -//! # mod surround { -//! diman::unit_system!( -//! quantity_type Quantity; -//! dimension_type Dimension; -//! -//! dimension Length; -//! dimension Time; //! //! dimension Velocity = Length / Time; +//! dimension Frequency = 1 / Time; +//! dimension Energy = Mass * Velocity^2; //! //! #[prefix(kilo, milli)] //! #[base(Length)] @@ -252,32 +243,29 @@ //! unit hours: Time = 3600 * seconds; //! unit meters_per_second: Velocity = meters / seconds; //! unit kilometers_per_hour: Velocity = kilometers / hours; -//! constant MY_FAVORITE_VELOCITY = 1000 * kilometers_per_hour; +//! constant SPEED_OF_LIGHT = 299792458 * meters_per_second; //! ); //! # } //! //! # use surround::dimensions::{Length, Time, Velocity}; //! # use surround::units::{meters_per_second,kilometers,hours}; -//! # use surround::constants::MY_FAVORITE_VELOCITY; +//! # use surround::constants::SPEED_OF_LIGHT; //! -//! fn fast_enough(x: Length, t: Time) { -//! let vel = x / t; -//! if vel > 1.0 * MY_FAVORITE_VELOCITY { -//! println!("{} m/s is definitely fast enough!", vel.value_in(meters_per_second)); -//! } +//! fn too_fast(x: Length, t: Time) -> bool { +//! x / t > 0.1f64 * SPEED_OF_LIGHT //! } //! -//! fast_enough(100.0 * kilometers, 0.3 * hours); +//! too_fast(100.0 * kilometers, 0.3 * hours); //! ``` //! -//! Here, `dimension` defines Quantities, which are concrete types, `unit` defines units, which are methods on the corresponding quantities and `constant` defines constants. -//! Dimensions without a right hand side are base dimensions (such as length, time, mass, temperature, ... in the SI system of units), whereas dimensions with a right hand side are derived dimensions. -//! The same thing holds for units - every unit is either a base unit for a given base dimension (denoted by the `#[base(...)]` attribute), or derived from base units and other derived units. Base units have the special property that the internal representation of the quantity will be in terms of the base unit (for example, a stored value `1.0` for a quantity with a `Length` dimension corresponds to `meter` in the above definitions). -//! Other than this, there are no differences between base dimensions and dimensions or base units and units and they can be treated equally in user code. -//! The macro also accepts more complex expressions such as `dimension Energy = Mass (Length / Time)^2`. -//! The definitions do not have to be in any specific order. +//! The macro accepts five different keywords: +//! 1. `quantity_type` specifies the name of the quantity type. Required for compiler error messages to have something to point to. +//! 2. `dimension_type` specifies the name of the dimension type. Required for compiler error messages to have something to point to. +//! 3. `dimension` defines a new dimension which is a type. Dimensions without a right hand side are base dimensions (such as `Length` and `Time` in this example), whereas dimensions with a right hand side are derived dimensions (such as `Velocity` in this example). +//! 4. `unit` defines a new units, which are methods on the corresponding quantities and `constant` defines constants. Units without a right-hand side are the base units to one specific base dimension, meaning that they are the unit that will internally be represented with a conversion factor of 1. Base units require the `#[base(...)]` attribute in order to specify which dimension they are the base unit of. Units with a right hand side are derived from other units. +//! 5. `constant` defines a new constant. //! -//! # Prefixes +//! ## SI Prefixes //! Unit prefixes can automatically be generated with the `#[prefix(...)]` attribute for unit statements. //! For example //! ``` @@ -298,7 +286,7 @@ //! will automatically generate the unit `meters` with symbol `m`, as well as `kilometers` and `millimeters` with symbols `km` and `mm` corresponding to `1e3 m` and `1e-3 m`. //! For simplicity, the attribute `#[metric_prefixes]` is provided, which will generate all metric prefixes from `atto-` to `exa-` automatically. //! -//! # Aliases +//! ## Aliases //! Unit aliases can automatically be generated with the `#[alias(...)]` macro. For example //! ``` //! # #![allow(incomplete_features)] From 011fbb522e76ab31b36b46aaf0b84df055d378d0 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 25 Feb 2024 14:55:36 +0100 Subject: [PATCH 36/40] add links to crates --- README.md | 9 +++++---- src/lib.rs | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 381c4aa..8da0a97 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Diman is a library for zero-cost compile time unit checking. ```rust use diman::si::dimensions::{Length, Time, Velocity}; -use diman::si::units::{seconds, meters, kilometers, hours}; +use diman::si::units::{seconds, meters, kilometers, hours, hour}; fn get_velocity(x: Length, t: Time) -> Velocity { x / t @@ -14,9 +14,10 @@ let v1 = get_velocity(36.0 * kilometers, 1.0 * hours); let v2 = get_velocity(10.0 * meters, 1.0 * seconds); assert_eq!(v1, v2); +assert_eq!(format!("{} km/h", v1.value_in(kilometers / hour)), "36 km/h"); ``` -Let's try to add quantities with incompatible dimensions: +Diman prevents unit errors at compile time: ```rust let time = 1.0 * seconds; let length = 10.0 * meters; @@ -268,7 +269,7 @@ The unit system generated with `rational-dimensions` supports a superset of feat Still, this feature should be enabled only when necessary, since the compiler errors in case of dimension mismatches will be harder to read. # `serde` -Serialization and deserialization of the units is provided via `serde` if the `serde` feature gate is enabled: +Serialization and deserialization of the units is provided via [`serde`](https://crates.io/crates/serde) if the `serde` feature gate is enabled: ```rust #[derive(Serialize, Deserialize, Debug, PartialEq)] struct Parameters { @@ -291,7 +292,7 @@ assert_eq!( ``` # `rand` -Diman allows generating random quantities via `rand` if the `rand` feature gate is enabled: +Diman allows generating random quantities via [`rand`](https://crates.io/crates/rand) if the `rand` feature gate is enabled: ```rust let mut rng = rand::thread_rng(); diff --git a/src/lib.rs b/src/lib.rs index ce4e236..4c190dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -352,7 +352,7 @@ //! Still, this feature should be enabled only when necessary, since the compiler errors in case of dimension mismatches will be harder to read. //! //! # `serde` -//! Serialization and deserialization of the units is provided via `serde` if the `serde` feature gate is enabled: +//! Serialization and deserialization of the units is provided via [`serde`](https://crates.io/crates/serde) if the `serde` feature gate is enabled: //! ```ignore //! # use diman::si::dimensions::{Length, Velocity}; //! # use diman::si::units::{meters, meters_per_second}; @@ -378,7 +378,7 @@ //! ``` //! //! # `rand` -//! Diman allows generating random quantities via `rand` if the `rand` feature gate is enabled: +//! Diman allows generating random quantities via [`rand`](https://crates.io/crates/rand) if the `rand` feature gate is enabled: //! ```ignore //! # use rand::Rng; //! # use diman::si::units::{meters, kilometers}; From 9546634e50a861f8bfdaa5c1a9d7ed9679b270c2 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 25 Feb 2024 15:00:16 +0100 Subject: [PATCH 37/40] fix warnings in new rust version --- .../src/codegen/storage_types.rs | 19 ------------------- crates/diman_unit_system/src/resolve/mod.rs | 2 +- .../resolver_partial_resolution.rs | 2 +- 3 files changed, 2 insertions(+), 21 deletions(-) diff --git a/crates/diman_unit_system/src/codegen/storage_types.rs b/crates/diman_unit_system/src/codegen/storage_types.rs index 5a7b426..89c4b00 100644 --- a/crates/diman_unit_system/src/codegen/storage_types.rs +++ b/crates/diman_unit_system/src/codegen/storage_types.rs @@ -31,9 +31,6 @@ pub trait StorageType { /// For vector types, this represents the underlying storage of a /// single entry in the vector. fn base_storage(&self) -> &FloatType; - - fn module_name(&self) -> &TokenStream; - fn generate_constants(&self) -> bool; } impl StorageType for VectorType { @@ -44,14 +41,6 @@ impl StorageType for VectorType { fn base_storage(&self) -> &FloatType { &self.float_type } - - fn module_name(&self) -> &TokenStream { - &self.module_name - } - - fn generate_constants(&self) -> bool { - false - } } impl StorageType for FloatType { @@ -62,14 +51,6 @@ impl StorageType for FloatType { fn base_storage(&self) -> &FloatType { self } - - fn module_name(&self) -> &TokenStream { - &self.module_name - } - - fn generate_constants(&self) -> bool { - true - } } impl Codegen { diff --git a/crates/diman_unit_system/src/resolve/mod.rs b/crates/diman_unit_system/src/resolve/mod.rs index 9c97d7c..ace7061 100644 --- a/crates/diman_unit_system/src/resolve/mod.rs +++ b/crates/diman_unit_system/src/resolve/mod.rs @@ -1,7 +1,7 @@ mod error; mod ident_storage; -use std::{collections::HashMap, result::Result}; +use std::collections::HashMap; use proc_macro2::Span; use syn::Ident; diff --git a/crates/diman_unit_system/tests/compile_fail/resolver_partial_resolution.rs b/crates/diman_unit_system/tests/compile_fail/resolver_partial_resolution.rs index b8ee0f4..1122b2c 100644 --- a/crates/diman_unit_system/tests/compile_fail/resolver_partial_resolution.rs +++ b/crates/diman_unit_system/tests/compile_fail/resolver_partial_resolution.rs @@ -17,5 +17,5 @@ unit_system_internal!( fn main() { use crate::units::meters; - let l = 1.0 * meters; + let _ = 1.0 * meters; } From 6f27a78711e55384afe0273c862971089f79c6b3 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 25 Feb 2024 15:02:25 +0100 Subject: [PATCH 38/40] remove allow attrs that were needed due to a previous clippy bug --- src/lib.rs | 2 -- tests/mod.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4c190dc..67c22db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -396,8 +396,6 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(incomplete_features)] #![feature(generic_const_exprs, adt_const_params)] -// clippy bug: https://github.com/rust-lang/rust-clippy/issues/12133 -#![allow(clippy::unconditional_recursion)] // This ensures we don't have to differentiate between // imports via `crate::` and `diman::` in the proc macro. diff --git a/tests/mod.rs b/tests/mod.rs index a9442e7..603527d 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -1,8 +1,6 @@ #![allow(incomplete_features)] #![feature(generic_const_exprs, adt_const_params)] #![feature(const_fn_floating_point_arithmetic)] -// clippy bug: https://github.com/rust-lang/rust-clippy/issues/12133 -#![allow(clippy::unconditional_recursion)] pub mod example_system; pub mod utils; From a6da0c1ad97d4ca8fcd4a1eb13c89b423fab7419 Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 25 Feb 2024 15:28:08 +0100 Subject: [PATCH 39/40] remove some unused type annotations --- README.md | 14 +++++++------- src/lib.rs | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8da0a97..28a6132 100644 --- a/README.md +++ b/README.md @@ -60,25 +60,25 @@ Physical quantities are represented by the `Quantity` struct, where `S` is ## Arithmetics and math Addition and subtraction of two quantities is allowed if the dimensions match: ```rust -let l: Length = 5.0 * meters + 10.0 * kilometers; +let l = 5.0 * meters + 10.0 * kilometers; ``` Multiplication and division of two quantities produces a new quantity: ```rust -let l: Length = 5.0 * meters; -let t: Time = 2.0 * seconds; +let l = 5.0 * meters; +let t = 2.0 * seconds; let v: Velocity = l / t; ``` Addition and subtraction of a `Quantity` and a storage type is possible if and only if `D` is dimensionless: ```rust -let l1: Length = 5.0 * meters; -let l2: Length = 10.0 * kilometers; +let l1 = 5.0 * meters; +let l2 = 10.0 * kilometers; let x = l1 / l2 - 0.5; let y = 0.5 - l1 / l2; ``` `Quantity` implements the dimensionless methods of `S`, such as `sin`, `cos`, etc. for dimensionless quantities: ```rust -let l1: Length = 5.0 * meters; -let l2: Length = 10.0 * kilometers; +let l1 = 5.0f64 * meters; +let l2 = 10.0f64 * kilometers; let angle_radians = (l1 / l2).asin(); ``` Exponentiation and related operations are supported via `squared`, `cubed`, `powi`, `sqrt`, `cbrt`: diff --git a/src/lib.rs b/src/lib.rs index 67c22db..ea01566 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,15 +62,15 @@ //! ``` //! # use diman::si::dimensions::{Length}; //! # use diman::si::units::{kilometers, meters}; -//! let l: Length = 5.0 * meters + 10.0 * kilometers; +//! let l = 5.0 * meters + 10.0 * kilometers; //! ``` //! Multiplication and division of two quantities produces a new quantity: //! ``` //! # #![feature(generic_const_exprs)] //! # use diman::si::dimensions::{Length, Time, Velocity}; //! # use diman::si::units::{meters, seconds}; -//! let l: Length = 5.0 * meters; -//! let t: Time = 2.0 * seconds; +//! let l = 5.0 * meters; +//! let t = 2.0 * seconds; //! let v: Velocity = l / t; //! ``` //! Addition and subtraction of a `Quantity` and a storage type is possible if and only if `D` is dimensionless: @@ -78,8 +78,8 @@ //! # #![feature(generic_const_exprs)] //! # use diman::si::dimensions::{Length}; //! # use diman::si::units::{kilometers, meters}; -//! let l1: Length = 5.0 * meters; -//! let l2: Length = 10.0 * kilometers; +//! let l1 = 5.0 * meters; +//! let l2 = 10.0 * kilometers; //! let x = l1 / l2 - 0.5; //! let y = 0.5 - l1 / l2; //! ``` @@ -88,8 +88,8 @@ //! # #![feature(generic_const_exprs)] //! # use diman::si::dimensions::{Length}; //! # use diman::si::units::{kilometers, meters}; -//! let l1: Length = 5.0 * meters; -//! let l2: Length = 10.0 * kilometers; +//! let l1 = 5.0f64 * meters; +//! let l2 = 10.0f64 * kilometers; //! let angle_radians = (l1 / l2).asin(); //! ``` //! Exponentiation and related operations are supported via `squared`, `cubed`, `powi`, `sqrt`, `cbrt`: From a8e304371eeb40da1bcf5156f41e6bfa0a9efe5d Mon Sep 17 00:00:00 2001 From: tehforsch Date: Sun, 25 Feb 2024 15:35:56 +0100 Subject: [PATCH 40/40] minor formatting tweaks --- README.md | 9 +++++++-- src/lib.rs | 11 +++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 28a6132..da84894 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,7 @@ This is why the `Product` and `Quotient` types are provided: ```rust use diman::si::dimensions::{Length, Time}; use diman::{Product, Quotient}; + fn foo(l: Length, t: Time) -> Product, Time> { l * t } @@ -258,8 +259,12 @@ fn bar(l: Length, t: Time) -> Quotient, Time> { # Rational dimensions The `rational-dimensions` feature allows using quantities with rational exponents in their base dimensions, as opposed to just integer values. This allows expressing defining dimensions and units such as: ```rust -dimension Sorptivity = Length Time^(-1/2); -unit meters_per_sqrt_second: Sorptivity = meters / seconds^(1/2); +unit_system!( + // ... + dimension Sorptivity = Length Time^(-1/2); + unit meters_per_sqrt_second: Sorptivity = meters / seconds^(1/2); + // ... +); let l = 2.0 * micrometers; let t = 5.0 * milliseconds; let sorptivity: Sorptivity = l / t.sqrt(); diff --git a/src/lib.rs b/src/lib.rs index ea01566..e1947b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -312,6 +312,7 @@ //! ``` //! use diman::si::dimensions::{Length, Time}; //! use diman::{Product, Quotient}; +//! //! fn foo(l: Length, t: Time) -> Product, Time> { //! l * t //! } @@ -326,7 +327,7 @@ //! ```ignore //! # mod surround { //! # use diman_unit_system::unit_system; -//! # unit_system!( +//! unit_system!( //! # quantity_type Quantity; //! # dimension_type Dimension; //! # dimension Length; @@ -337,9 +338,11 @@ //! # #[base(Time)] //! # #[symbol(s)] //! # unit seconds; -//! dimension Sorptivity = Length Time^(-1/2); -//! unit meters_per_sqrt_second: Sorptivity = meters / seconds^(1/2); -//! # ); +//! // ... +//! dimension Sorptivity = Length Time^(-1/2); +//! unit meters_per_sqrt_second: Sorptivity = meters / seconds^(1/2); +//! // ... +//! ); //! # } //! # use surround::dimensions::Sorptivity; //! # use surround::units::{micrometers,milliseconds};