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]
|
||||
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"
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user