feat: f32/f64 cargo features added

Signed-off-by: Stephen Waits <steve@waits.net>
This commit is contained in:
swaits committed 2023-02-08 17:01:19 -07:00
1 parent 1bfdce1c21
commit 9d0a318248
5 files changed
+82 -35

No files matched your search

+6 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "vivaldi-nc" name = "vivaldi-nc"
version = "0.5.1" version = "0.5.2"
edition = "2021" edition = "2021"
authors = ["Stephen Waits <steve@waits.net>"] authors = ["Stephen Waits <steve@waits.net>"]
description = "Vivaldi network coordinates for fast, distributed latency estimates in multinode networks, with a clean and simple interface." description = "Vivaldi network coordinates for fast, distributed latency estimates in multinode networks, with a clean and simple interface."
@@ -15,6 +15,11 @@ exclude = [
"examples/*", "examples/*",
] ]
[features]
default = ["f32"]
f32 = []
f64 = []
[dev-dependencies] [dev-dependencies]
assert_approx_eq = "1.1.0" assert_approx_eq = "1.1.0"
proptest = "1.0.0" proptest = "1.0.0"
+9
View File
@@ -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 See the [module documentation](https://docs.rs/vivaldi-nc) for more detailed
usage examples. 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 ### Examples
The repository includes an example which loads a n-to-n latency sample from The repository includes an example which loads a n-to-n latency sample from
+31 -22
View File
@@ -49,6 +49,15 @@ use serde::{Deserialize, Serialize};
use crate::vector::Vector; use crate::vector::Vector;
//
// **** Features ****
//
#[cfg(feature = "f32")]
type FloatType = f32;
#[cfg(feature = "f64")]
type FloatType = f64;
// //
// **** Constants **** // **** Constants ****
// //
@@ -65,8 +74,8 @@ use crate::vector::Vector;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub(crate) struct HeightVector<const N: usize> { pub(crate) struct HeightVector<const N: usize> {
#[serde(flatten)] #[serde(flatten)]
position: Vector<f32, N>, position: Vector<FloatType, N>,
height: f32, height: FloatType,
} }
// //
@@ -84,11 +93,11 @@ impl<const N: usize> HeightVector<N> {
let mut rng = WyRand::new(); let mut rng = WyRand::new();
let mut vec = [0.0; N]; let mut vec = [0.0; N];
for i in vec.iter_mut().take(N) { for i in vec.iter_mut().take(N) {
*i = rng.generate::<f32>(); *i = rng.generate::<FloatType>();
} }
let height = rng.generate::<f32>().abs(); let height = rng.generate::<FloatType>().abs();
Self { Self {
position: Vector::<f32, N>::from(vec), position: Vector::<FloatType, N>::from(vec),
height, height,
} }
.normalized() .normalized()
@@ -96,7 +105,7 @@ impl<const N: usize> HeightVector<N> {
/// The magnitude of a Vivaldi height vector is defined as the magnitude of the vector plus the /// The magnitude of a Vivaldi height vector is defined as the magnitude of the vector plus the
/// height value. /// height value.
pub(crate) fn len(&self) -> f32 { pub(crate) fn len(&self) -> FloatType {
self.position.len() + self.height self.position.len() + self.height
} }
@@ -104,7 +113,7 @@ impl<const N: usize> HeightVector<N> {
/// by the inveerse of its length. /// by the inveerse of its length.
pub(crate) fn normalized(&self) -> Self { pub(crate) fn normalized(&self) -> Self {
let len = self.len(); let len = self.len();
if len < f32::EPSILON { if len < FloatType::EPSILON {
// if we have a bad vector, generate a new random vector // if we have a bad vector, generate a new random vector
Self::new() Self::new()
} else { } else {
@@ -150,11 +159,11 @@ impl<const N: usize> Default for HeightVector<N> {
} }
} }
impl<const N: usize> From<([f32; N], f32)> for HeightVector<N> { impl<const N: usize> From<([FloatType; N], FloatType)> for HeightVector<N> {
/// Convert from a `([f32; N], f32)` (vector, height) to a Vivaldi height vector type. /// Convert from a `([FloatType; N], FloatType)` (vector, height) to a Vivaldi height vector type.
fn from(value: ([f32; N], f32)) -> Self { fn from(value: ([FloatType; N], FloatType)) -> Self {
let ret = Self { let ret = Self {
position: Vector::<f32, N>::from(value.0), position: Vector::<FloatType, N>::from(value.0),
height: value.1, height: value.1,
}; };
if ret.is_valid() { if ret.is_valid() {
@@ -200,11 +209,11 @@ impl<const N: usize> Sub for HeightVector<N> {
} }
} }
impl<const N: usize> Mul<f32> for HeightVector<N> { impl<const N: usize> Mul<FloatType> for HeightVector<N> {
type Output = Self; type Output = Self;
/// Multiply a Vivaldi height vector by a scalar. Works the same as normal vector scaling. /// 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 { let ret = Self {
position: self.position * rhs, position: self.position * rhs,
height: self.height * rhs, height: self.height * rhs,
@@ -228,7 +237,7 @@ mod tests {
proptest! { proptest! {
#[test] #[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 len = ((x*x) + (y*y)).sqrt() + h.abs();
let a = HeightVector::<2>::from(([x,y],h)); 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 { 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] #[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) { 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 // convert our integer range inputs to FloatType
let (fx0,fy0,fh0) = (x0 as f32 / 1_000.0, y0 as f32 / 1_000.0, h0 as f32 / 1_000.0); 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 f32 / 1_000.0, y1 as f32 / 1_000.0, h1 as f32 / 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 a = HeightVector::<2>::from(([fx0 ,fy0 ],fh0 ));
let b = HeightVector::<2>::from(([fx1 ,fy1 ],fh1 )); let b = HeightVector::<2>::from(([fx1 ,fy1 ],fh1 ));
@@ -256,9 +265,9 @@ mod tests {
#[test] #[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) { 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 // convert our integer range inputs to FloatType
let (fx0,fy0,fh0) = (x0 as f32 / 1_000.0, y0 as f32 / 1_000.0, h0 as f32 / 1_000.0); 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 f32 / 1_000.0, y1 as f32 / 1_000.0, h1 as f32 / 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 a = HeightVector::<2>::from(([fx0 ,fy0 ],fh0 ));
let b = HeightVector::<2>::from(([fx1 ,fy1 ],fh1 )); let b = HeightVector::<2>::from(([fx1 ,fy1 ],fh1 ));
@@ -270,8 +279,8 @@ mod tests {
#[test] #[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) { 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 // convert our integer range inputs to FloatType
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); 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 a = HeightVector::<2>::from(([fx, fy], fh));
let b = a * fm; let b = a * fm;
+4
View File
@@ -96,6 +96,10 @@
mod height_vector; mod height_vector;
mod 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 // publish our interface
pub mod network_coordinate; pub mod network_coordinate;
pub use network_coordinate::NetworkCoordinate; pub use network_coordinate::NetworkCoordinate;
+32 -12
View File
@@ -31,19 +31,28 @@ use serde::{Deserialize, Serialize};
use crate::height_vector::HeightVector; use crate::height_vector::HeightVector;
//
// **** Features ****
//
#[cfg(feature = "f32")]
type FloatType = f32;
#[cfg(feature = "f64")]
type FloatType = f64;
// //
// **** Constants **** // **** Constants ****
// //
// Vivaldi tuning parameters // Vivaldi tuning parameters
const C_ERROR: f32 = 0.25; const C_ERROR: FloatType = 0.25;
const C_DELTA: f32 = 0.25; const C_DELTA: FloatType = 0.25;
// initial error value // initial error value
const DEFAULT_ERROR: f32 = 200.0; const DEFAULT_ERROR: FloatType = 200.0;
// error should always be greater than zero // error should always be greater than zero
const MIN_ERROR: f32 = f32::EPSILON; const MIN_ERROR: FloatType = FloatType::EPSILON;
// //
// **** Structs **** // **** Structs ****
@@ -53,7 +62,7 @@ const MIN_ERROR: f32 = f32::EPSILON;
pub struct NetworkCoordinate<const N: usize> { pub struct NetworkCoordinate<const N: usize> {
#[serde(flatten)] #[serde(flatten)]
heightvec: HeightVector<N>, heightvec: HeightVector<N>,
error: f32, error: FloatType,
} }
// type aliases for convenience // type aliases for convenience
@@ -121,7 +130,11 @@ impl<const N: usize> NetworkCoordinate<N> {
/// ///
pub fn estimated_rtt(&self, rhs: &Self) -> Duration { pub fn estimated_rtt(&self, rhs: &Self) -> Duration {
// estimated rss is euclidean distance between the two plus the sum of the heights // 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 /// Given another Vivaldi [`NetworkCoordinate`], adjust our coordinateto better represent the actual round
@@ -155,15 +168,22 @@ impl<const N: usize> NetworkCoordinate<N> {
/// local.update(&remote, rtt); /// local.update(&remote, rtt);
/// ``` /// ```
pub fn update(&mut self, rhs: &Self, rtt: Duration) -> &Self { 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 // rtt needs to be positive
if rtt.as_secs_f32() < 0.0 { if rtt_ms < 0.0 {
return self; 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) // Sample weight balances local and remote error. (1)
// w = ei /(ei + ej ) // w = ei /(ei + ej )
let w = self.error / (self.error + rhs.error); let w = self.error / (self.error + rhs.error);
@@ -196,7 +216,7 @@ impl<const N: usize> NetworkCoordinate<N> {
/// getter for error value - useful for consumers to understand the estimated accuracty of this /// getter for error value - useful for consumers to understand the estimated accuracty of this
/// `NetworkCoordinate` /// `NetworkCoordinate`
pub fn error(&self) -> f32 { pub fn error(&self) -> FloatType {
self.error self.error
} }
} }