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]
name = "vivaldi-nc"
version = "0.5.1"
version = "0.5.2"
edition = "2021"
authors = ["Stephen Waits <steve@waits.net>"]
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"
+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
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
+31 -22
View File
@@ -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<const N: usize> {
#[serde(flatten)]
position: Vector<f32, N>,
height: f32,
position: Vector<FloatType, N>,
height: FloatType,
}
//
@@ -84,11 +93,11 @@ impl<const N: usize> HeightVector<N> {
let mut rng = WyRand::new();
let mut vec = [0.0; 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 {
position: Vector::<f32, N>::from(vec),
position: Vector::<FloatType, N>::from(vec),
height,
}
.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
/// height value.
pub(crate) fn len(&self) -> f32 {
pub(crate) fn len(&self) -> FloatType {
self.position.len() + self.height
}
@@ -104,7 +113,7 @@ impl<const N: usize> HeightVector<N> {
/// 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<const N: usize> Default for HeightVector<N> {
}
}
impl<const N: usize> From<([f32; N], f32)> for HeightVector<N> {
/// Convert from a `([f32; N], f32)` (vector, height) to a Vivaldi height vector type.
fn from(value: ([f32; N], f32)) -> Self {
impl<const N: usize> From<([FloatType; N], FloatType)> for HeightVector<N> {
/// 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::<f32, N>::from(value.0),
position: Vector::<FloatType, N>::from(value.0),
height: value.1,
};
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;
/// 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;
+4
View File
@@ -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;
+32 -12
View File
@@ -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<const N: usize> {
#[serde(flatten)]
heightvec: HeightVector<N>,
error: f32,
error: FloatType,
}
// type aliases for convenience
@@ -121,7 +130,11 @@ impl<const N: usize> NetworkCoordinate<N> {
///
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<const N: usize> NetworkCoordinate<N> {
/// 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<const N: usize> NetworkCoordinate<N> {
/// 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
}
}