feat: f32/f64 cargo features added
Signed-off-by: Stephen Waits <steve@waits.net>
This commit is contained in:
1 parent
1bfdce1c21
commit
9d0a318248
5 files changed
+82
-35
No files matched your search
+6
-1
@@ -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"
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user