From 9d0a318248196e734b8b060ab6a7d21a19b3feb5 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Wed, 8 Feb 2023 17:01:19 -0700 Subject: [PATCH] feat: f32/f64 cargo features added Signed-off-by: Stephen Waits --- Cargo.toml | 7 +++++- README.md | 9 +++++++ src/height_vector.rs | 53 +++++++++++++++++++++++---------------- src/lib.rs | 4 +++ src/network_coordinate.rs | 44 +++++++++++++++++++++++--------- 5 files changed, 82 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 526e939..3303d60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vivaldi-nc" -version = "0.5.1" +version = "0.5.2" edition = "2021" authors = ["Stephen Waits "] description = "Vivaldi network coordinates for fast, distributed latency estimates in multinode networks, with a clean and simple interface." @@ -15,6 +15,11 @@ exclude = [ "examples/*", ] +[features] +default = ["f32"] +f32 = [] +f64 = [] + [dev-dependencies] assert_approx_eq = "1.1.0" proptest = "1.0.0" diff --git a/README.md b/README.md index a01339c..dce10f8 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,15 @@ and compact binary formats like [bincode](https://crates.io/crates/bincode) and See the [module documentation](https://docs.rs/vivaldi-nc) for more detailed usage examples. +### Cargo Features + +By default, the internal data structures and operations all use `f32`. If you want to use `f64`, you can enable it as a cargo feature: + +```toml +[dependencies] +vivaldi-nc = { version = "0.5.2", features = ["f64"] } +``` + ### Examples The repository includes an example which loads a n-to-n latency sample from diff --git a/src/height_vector.rs b/src/height_vector.rs index db44c4c..c5f1556 100644 --- a/src/height_vector.rs +++ b/src/height_vector.rs @@ -49,6 +49,15 @@ use serde::{Deserialize, Serialize}; use crate::vector::Vector; +// +// **** Features **** +// +#[cfg(feature = "f32")] +type FloatType = f32; + +#[cfg(feature = "f64")] +type FloatType = f64; + // // **** Constants **** // @@ -65,8 +74,8 @@ use crate::vector::Vector; #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub(crate) struct HeightVector { #[serde(flatten)] - position: Vector, - height: f32, + position: Vector, + height: FloatType, } // @@ -84,11 +93,11 @@ impl HeightVector { let mut rng = WyRand::new(); let mut vec = [0.0; N]; for i in vec.iter_mut().take(N) { - *i = rng.generate::(); + *i = rng.generate::(); } - let height = rng.generate::().abs(); + let height = rng.generate::().abs(); Self { - position: Vector::::from(vec), + position: Vector::::from(vec), height, } .normalized() @@ -96,7 +105,7 @@ impl HeightVector { /// The magnitude of a Vivaldi height vector is defined as the magnitude of the vector plus the /// height value. - pub(crate) fn len(&self) -> f32 { + pub(crate) fn len(&self) -> FloatType { self.position.len() + self.height } @@ -104,7 +113,7 @@ impl HeightVector { /// by the inveerse of its length. pub(crate) fn normalized(&self) -> Self { let len = self.len(); - if len < f32::EPSILON { + if len < FloatType::EPSILON { // if we have a bad vector, generate a new random vector Self::new() } else { @@ -150,11 +159,11 @@ impl Default for HeightVector { } } -impl From<([f32; N], f32)> for HeightVector { - /// Convert from a `([f32; N], f32)` (vector, height) to a Vivaldi height vector type. - fn from(value: ([f32; N], f32)) -> Self { +impl From<([FloatType; N], FloatType)> for HeightVector { + /// Convert from a `([FloatType; N], FloatType)` (vector, height) to a Vivaldi height vector type. + fn from(value: ([FloatType; N], FloatType)) -> Self { let ret = Self { - position: Vector::::from(value.0), + position: Vector::::from(value.0), height: value.1, }; if ret.is_valid() { @@ -200,11 +209,11 @@ impl Sub for HeightVector { } } -impl Mul for HeightVector { +impl Mul for HeightVector { type Output = Self; /// Multiply a Vivaldi height vector by a scalar. Works the same as normal vector scaling. - fn mul(self, rhs: f32) -> Self::Output { + fn mul(self, rhs: FloatType) -> Self::Output { let ret = Self { position: self.position * rhs, height: self.height * rhs, @@ -228,7 +237,7 @@ mod tests { proptest! { #[test] - fn proptest_len(x: f32, y: f32, h: f32) { + fn proptest_len(x: FloatType, y: FloatType, h: FloatType) { let len = ((x*x) + (y*y)).sqrt() + h.abs(); let a = HeightVector::<2>::from(([x,y],h)); if x.is_nan() || x.is_infinite() || y.is_nan() || y.is_infinite() || h.is_nan() || h.is_infinite() || h < 0.0 { @@ -242,9 +251,9 @@ mod tests { #[test] fn proptest_add(x0 in -1_000_000_000..1_000_000_000i32, y0 in -1_000_000_000..1_000_000_000i32, h0 in 0..1_000_000_000i32, x1 in -1_000_000_000..1_000_000_000i32, y1 in -1_000_000_000..1_000_000_000i32, h1 in 0..1_000_000i32) { - // convert our integer range inputs to f32s - let (fx0,fy0,fh0) = (x0 as f32 / 1_000.0, y0 as f32 / 1_000.0, h0 as f32 / 1_000.0); - let (fx1,fy1,fh1) = (x1 as f32 / 1_000.0, y1 as f32 / 1_000.0, h1 as f32 / 1_000.0); + // convert our integer range inputs to FloatType + let (fx0,fy0,fh0) = (x0 as FloatType / 1_000.0, y0 as FloatType / 1_000.0, h0 as FloatType / 1_000.0); + let (fx1,fy1,fh1) = (x1 as FloatType / 1_000.0, y1 as FloatType / 1_000.0, h1 as FloatType / 1_000.0); let a = HeightVector::<2>::from(([fx0 ,fy0 ],fh0 )); let b = HeightVector::<2>::from(([fx1 ,fy1 ],fh1 )); @@ -256,9 +265,9 @@ mod tests { #[test] fn proptest_sub(x0 in -1_000_000_000..1_000_000_000i32, y0 in -1_000_000_000..1_000_000_000i32, h0 in 0..1_000_000_000i32, x1 in -1_000_000_000..1_000_000_000i32, y1 in -1_000_000_000..1_000_000_000i32, h1 in 0..1_000_000i32) { - // convert our integer range inputs to f32s - let (fx0,fy0,fh0) = (x0 as f32 / 1_000.0, y0 as f32 / 1_000.0, h0 as f32 / 1_000.0); - let (fx1,fy1,fh1) = (x1 as f32 / 1_000.0, y1 as f32 / 1_000.0, h1 as f32 / 1_000.0); + // convert our integer range inputs to FloatType + let (fx0,fy0,fh0) = (x0 as FloatType / 1_000.0, y0 as FloatType / 1_000.0, h0 as FloatType / 1_000.0); + let (fx1,fy1,fh1) = (x1 as FloatType / 1_000.0, y1 as FloatType / 1_000.0, h1 as FloatType / 1_000.0); let a = HeightVector::<2>::from(([fx0 ,fy0 ],fh0 )); let b = HeightVector::<2>::from(([fx1 ,fy1 ],fh1 )); @@ -270,8 +279,8 @@ mod tests { #[test] fn proptest_mul(x in -1_000_000_000..1_000_000_000i32, y in -1_000_000_000..1_000_000_000i32, h in 0..1_000_000_000i32, m in -1_000_000_000..1_000_000_000i32) { - // convert our integer range inputs to f32s - let (fx,fy,fh,fm) = (x as f32 / 1_000.0, y as f32 / 1_000.0, h as f32 / 1_000.0, m as f32 / 1_000.0); + // convert our integer range inputs to FloatType + let (fx,fy,fh,fm) = (x as FloatType / 1_000.0, y as FloatType / 1_000.0, h as FloatType / 1_000.0, m as FloatType / 1_000.0); let a = HeightVector::<2>::from(([fx, fy], fh)); let b = a * fm; diff --git a/src/lib.rs b/src/lib.rs index 81db139..ee3f743 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,6 +96,10 @@ mod height_vector; mod vector; +// make sure we don't have conflicting features +#[cfg(all(feature = "f32", feature = "f64"))] +compile_error!("feature \"f32\" and feature \"f64\" cannot be enabled at the same time"); + // publish our interface pub mod network_coordinate; pub use network_coordinate::NetworkCoordinate; diff --git a/src/network_coordinate.rs b/src/network_coordinate.rs index f242b4f..e867ac2 100644 --- a/src/network_coordinate.rs +++ b/src/network_coordinate.rs @@ -31,19 +31,28 @@ use serde::{Deserialize, Serialize}; use crate::height_vector::HeightVector; +// +// **** Features **** +// +#[cfg(feature = "f32")] +type FloatType = f32; + +#[cfg(feature = "f64")] +type FloatType = f64; + // // **** Constants **** // // Vivaldi tuning parameters -const C_ERROR: f32 = 0.25; -const C_DELTA: f32 = 0.25; +const C_ERROR: FloatType = 0.25; +const C_DELTA: FloatType = 0.25; // initial error value -const DEFAULT_ERROR: f32 = 200.0; +const DEFAULT_ERROR: FloatType = 200.0; // error should always be greater than zero -const MIN_ERROR: f32 = f32::EPSILON; +const MIN_ERROR: FloatType = FloatType::EPSILON; // // **** Structs **** @@ -53,7 +62,7 @@ const MIN_ERROR: f32 = f32::EPSILON; pub struct NetworkCoordinate { #[serde(flatten)] heightvec: HeightVector, - error: f32, + error: FloatType, } // type aliases for convenience @@ -121,7 +130,11 @@ impl NetworkCoordinate { /// pub fn estimated_rtt(&self, rhs: &Self) -> Duration { // estimated rss is euclidean distance between the two plus the sum of the heights - Duration::from_secs_f32((self.heightvec - rhs.heightvec).len() / 1000.0) + #[cfg(feature = "f32")] + return Duration::from_secs_f32((self.heightvec - rhs.heightvec).len() / 1000.0); + + #[cfg(feature = "f64")] + return Duration::from_secs_f64((self.heightvec - rhs.heightvec).len() / 1000.0); } /// Given another Vivaldi [`NetworkCoordinate`], adjust our coordinateto better represent the actual round @@ -155,15 +168,22 @@ impl NetworkCoordinate { /// local.update(&remote, rtt); /// ``` pub fn update(&mut self, rhs: &Self, rtt: Duration) -> &Self { + // convert Durations into FloatType as fractional milliseconds for convenience + #[cfg(feature = "f32")] + let rtt_ms = rtt.as_secs_f32() * 1000.0; + #[cfg(feature = "f32")] + let rtt_estimated_ms = self.estimated_rtt(rhs).as_secs_f32() * 1000.0; + + #[cfg(feature = "f64")] + let rtt_ms = rtt.as_secs_f64() * 1000.0; + #[cfg(feature = "f64")] + let rtt_estimated_ms = self.estimated_rtt(rhs).as_secs_f64() * 1000.0; + // rtt needs to be positive - if rtt.as_secs_f32() < 0.0 { + if rtt_ms < 0.0 { return self; } - // convert Durations into f32s as fractional milliseconds for convenience - let rtt_ms = rtt.as_secs_f32() * 1000.0; - let rtt_estimated_ms = self.estimated_rtt(rhs).as_secs_f32() * 1000.0; - // Sample weight balances local and remote error. (1) // w = ei /(ei + ej ) let w = self.error / (self.error + rhs.error); @@ -196,7 +216,7 @@ impl NetworkCoordinate { /// getter for error value - useful for consumers to understand the estimated accuracty of this /// `NetworkCoordinate` - pub fn error(&self) -> f32 { + pub fn error(&self) -> FloatType { self.error } }