diff --git a/.build.yml b/.build.yml index 1032204..c675594 100644 --- a/.build.yml +++ b/.build.yml @@ -11,13 +11,15 @@ tasks: - stable: | rustup default stable cd vivaldi-nc/ + cargo clippy --verbose cargo build --verbose cargo test --verbose - nightly: | rustup default nightly cd vivaldi-nc/ - cargo build --verbose ||: - cargo test --verbose ||: + cargo clippy --verbose ||: + cargo build --verbose ||: + cargo test --verbose ||: - docs: | cd vivaldi-nc/ rustup run stable cargo doc --no-deps diff --git a/proptest-regressions/height_vector.txt b/proptest-regressions/height_vector.txt index 493a7d2..8abd7a4 100644 --- a/proptest-regressions/height_vector.txt +++ b/proptest-regressions/height_vector.txt @@ -8,3 +8,4 @@ cc 4a5cb6ca844d5306203ae00a99375c83a20ddf4ca427f0ae0b541697efa3d9b7 # shrinks to cc 38ed1e2c313317809f5469d856d2c1c61e4851c64a156cd1bdd0757fa7c77128 # shrinks to x = 0.0, y = -0.0, h = 0.0, m = -0.0 cc b7d1b5e9fe3e33066b583c4532865373179972e9b5786ecc62438f23bc939e87 # shrinks to x = -0.0, y = 0.0, h = -3.448118e-33, m = -0.0 cc 3c4fc4f21c82ffe5bcbc740ca9ab598e0894226acb86a3046a3a24cbbcdbf8e6 # shrinks to x = 0.0, y = 0.0, h = -0.0 +cc d1a551d7eb92bf69e3d0cd0b677baac5aaa5a95d6a016fccf67cf57af71c8b0c # shrinks to x = -5.098216e26, y = 0.0, h = 0.0 diff --git a/src/height_vector.rs b/src/height_vector.rs index c5f1556..bfdc434 100644 --- a/src/height_vector.rs +++ b/src/height_vector.rs @@ -42,19 +42,22 @@ //! > Each node has a positive height element in its coordinates, so that //! > its height can always be scaled up or down. -use std::ops::{Add, Mul, Sub}; +use core::ops::{Add, Mul, Sub}; use nanorand::{Rng, WyRand}; use serde::{Deserialize, Serialize}; -use crate::vector::Vector; +use crate::vector::{self, Vector}; // // **** Features **** // + +/// `FloatType` is a type alias for either `f32` or `f64` depending on cargo features #[cfg(feature = "f32")] type FloatType = f32; +/// `FloatType` is a type alias for either `f32` or `f64` depending on cargo features #[cfg(feature = "f64")] type FloatType = f64; @@ -72,9 +75,15 @@ type FloatType = f64; /// /// - `N`: the dimensionality of the vector portion (i.e. non-height) of the Vivaldi height vector #[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub(crate) struct HeightVector { +pub struct HeightVector { + /// `position` is the Euclidean coordinate part of the `HeightVector`, representing a position + /// in the network's latency space #[serde(flatten)] position: Vector, + + /// `height` is a representation of a node's stemp time, or its latency to get into the network + /// core (or backbone). It's a way to account for triangle inequality violations. + /// in the network's latency space height: FloatType, } @@ -153,7 +162,7 @@ impl Default for HeightVector { /// Default value for a Vivaldi height vector is just the defaults of its children types. fn default() -> Self { Self { - position: Default::default(), + position: vector::Vector::default(), height: Default::default(), } } @@ -238,14 +247,15 @@ mod tests { proptest! { #[test] fn proptest_len(x: FloatType, y: FloatType, h: FloatType) { - let len = ((x*x) + (y*y)).sqrt() + h.abs(); + let len = x.hypot(y) + h.abs(); let a = HeightVector::<2>::from(([x,y],h)); + println!("a = {a:#?}"); if x.is_nan() || x.is_infinite() || y.is_nan() || y.is_infinite() || h.is_nan() || h.is_infinite() || h < 0.0 { // we should've gottne a random univ vector here assert_approx_eq!(a.len(), 1.0); } else { // `HeightVector` we created from proptest values should be valid and have a length - assert_eq!(a.len(), len); + assert_approx_eq!(a.len(), len); } } @@ -258,9 +268,9 @@ mod tests { let a = HeightVector::<2>::from(([fx0 ,fy0 ],fh0 )); let b = HeightVector::<2>::from(([fx1 ,fy1 ],fh1 )); let c = a+b; - assert_eq!(c.position[0], fx0 + fx1); - assert_eq!(c.position[1], fy0 + fy1); - assert_eq!(c.height, fh0 + fh1); + assert_approx_eq!(c.position[0], fx0 + fx1); + assert_approx_eq!(c.position[1], fy0 + fy1); + assert_approx_eq!(c.height, fh0 + fh1); } #[test] @@ -272,9 +282,9 @@ mod tests { let a = HeightVector::<2>::from(([fx0 ,fy0 ],fh0 )); let b = HeightVector::<2>::from(([fx1 ,fy1 ],fh1 )); let c = a-b; - assert_eq!(c.position[0], fx0 - fx1); - assert_eq!(c.position[1], fy0 - fy1); - assert_eq!(c.height, fh0 + fh1); + assert_approx_eq!(c.position[0], fx0 - fx1); + assert_approx_eq!(c.position[1], fy0 - fy1); + assert_approx_eq!(c.height, fh0 + fh1); } #[test] @@ -285,11 +295,11 @@ mod tests { let a = HeightVector::<2>::from(([fx, fy], fh)); let b = a * fm; if fm < 0.0 { - assert_approx_eq!(b.len(), 1.0) + assert_approx_eq!(b.len(), 1.0); } else { - assert_eq!(b.position[0], fx * fm); - assert_eq!(b.position[1], fy * fm); - assert_eq!(b.height, fh * fm); + assert_approx_eq!(b.position[0], fx * fm); + assert_approx_eq!(b.position[1], fy * fm); + assert_approx_eq!(b.height, fh * fm); } } } @@ -317,9 +327,9 @@ mod tests { #[test] fn test_from() { let a = HeightVector::<2>::from(([1.0, 2.0], 3.0)); - assert_eq!(a.position[0], 1.0); - assert_eq!(a.position[1], 2.0); - assert_eq!(a.height, 3.0); + assert_approx_eq!(a.position[0], 1.0); + assert_approx_eq!(a.position[1], 2.0); + assert_approx_eq!(a.height, 3.0); } #[test] @@ -327,9 +337,9 @@ mod tests { let a = HeightVector::<2>::from(([1.0, 2.0], 3.0)); let b = HeightVector::<2>::from(([3.0, 2.0], 1.0)); let c = a + b; - assert_eq!(c.position[0], 4.0); - assert_eq!(c.position[1], 4.0); - assert_eq!(c.height, 4.0); + assert_approx_eq!(c.position[0], 4.0); + assert_approx_eq!(c.position[1], 4.0); + assert_approx_eq!(c.height, 4.0); } #[test] @@ -337,17 +347,17 @@ mod tests { let a = HeightVector::<2>::from(([1.0, 2.0], 3.0)); let b = HeightVector::<2>::from(([3.0, 2.0], 1.0)); let c = a - b; - assert_eq!(c.position[0], -2.0); - assert_eq!(c.position[1], 0.0); - assert_eq!(c.height, 4.0); + assert_approx_eq!(c.position[0], -2.0); + assert_approx_eq!(c.position[1], 0.0); + assert_approx_eq!(c.height, 4.0); } #[test] fn test_mul_scalar() { let a = HeightVector::<2>::from(([1.0, 2.0], 3.0)) * 10.0; - assert_eq!(a.position[0], 10.0); - assert_eq!(a.position[1], 20.0); - assert_eq!(a.height, 30.0); + assert_approx_eq!(a.position[0], 10.0); + assert_approx_eq!(a.position[1], 20.0); + assert_approx_eq!(a.height, 30.0); } #[test] diff --git a/src/lib.rs b/src/lib.rs index ee3f743..bdbd99c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,18 @@ //! ``` //! +#![deny(clippy::all)] +#![deny(clippy::correctness)] +#![deny(clippy::style)] +#![deny(clippy::suspicious)] +#![deny(clippy::complexity)] +#![deny(clippy::perf)] +#![deny(clippy::cargo)] +#![deny(clippy::pedantic)] +#![deny(clippy::nursery)] +#![allow(clippy::type_repetition_in_bounds)] +#![allow(clippy::module_name_repetitions)] + mod height_vector; mod vector; diff --git a/src/network_coordinate.rs b/src/network_coordinate.rs index e867ac2..096bc31 100644 --- a/src/network_coordinate.rs +++ b/src/network_coordinate.rs @@ -97,6 +97,7 @@ impl NetworkCoordinate { /// // print the NC /// println!("Our new NC is: {:#?}", a); /// ``` + #[must_use] pub fn new() -> Self { Self::default() } @@ -128,6 +129,7 @@ impl NetworkCoordinate { /// println!("Estimated RTT: {}", a.estimated_rtt(&b).as_millis()); /// ``` /// + #[must_use] pub fn estimated_rtt(&self, rhs: &Self) -> Duration { // estimated rss is euclidean distance between the two plus the sum of the heights #[cfg(feature = "f32")] @@ -195,7 +197,11 @@ impl NetworkCoordinate { // Update weighted moving average of local error. (3) // ei = es × ce × w + ei × (1 − ce × w) - self.error = (es * C_ERROR * w + self.error * (1.0 - C_ERROR * w)).max(MIN_ERROR); + // self.error = (es * C_ERROR * w + self.error * (1.0 - C_ERROR * w)).max(MIN_ERROR); + // NOTE: using `mul_add()` which is a little safer (avoid overflows) + self.error = (es * C_ERROR) + .mul_add(w, self.error * C_ERROR.mul_add(-w, 1.0)) + .max(MIN_ERROR); // Update local coordinates. (4) // δ = cc × w @@ -207,7 +213,7 @@ impl NetworkCoordinate { // if we ended up with an invalid coordinate, return a new random coordinate with default // error if self.heightvec.is_invalid() { - *self = Self::new() + *self = Self::new(); } // return reference to updated self @@ -216,7 +222,8 @@ impl NetworkCoordinate { /// getter for error value - useful for consumers to understand the estimated accuracty of this /// `NetworkCoordinate` - pub fn error(&self) -> FloatType { + #[must_use] + pub const fn error(&self) -> FloatType { self.error } } @@ -226,7 +233,7 @@ impl NetworkCoordinate { // impl Default for NetworkCoordinate { - /// A default `NetworkCoordinate` has a random position and DEFAULT_ERROR + /// A default `NetworkCoordinate` has a random position and `DEFAULT_ERROR` fn default() -> Self { Self { heightvec: HeightVector::::random(), @@ -293,10 +300,8 @@ mod tests { let mut mad = NetworkCoordinate::<2>::new(); // verify the initial error - let error = - (slc.error.powf(2.0) + nyc.error.powf(2.0) + lax.error.powf(2.0) + mad.error.powf(2.0)) - .sqrt(); - assert_eq!(error, 400.0); + let error = slc.error.hypot(nyc.error.hypot(lax.error.hypot(mad.error))); + assert_approx_eq!(error, 400.0); // iterate plenty of times to converge and minimize error (0..20).for_each(|_| { @@ -333,7 +338,7 @@ mod tests { // make sure it's the right length and works like we expect a normal NC assert_approx_eq!(a.heightvec.len(), 2.649_509, 0.001); - assert_eq!(a.error, 1.0); + assert_approx_eq!(a.error, 1.0); assert_eq!(a.estimated_rtt(&a).as_millis(), 0); // serialize it into a new JSON string and make sure it matches the original diff --git a/src/vector.rs b/src/vector.rs index 2ef4db6..5bc1ede 100644 --- a/src/vector.rs +++ b/src/vector.rs @@ -20,7 +20,7 @@ use std::ops::{Add, Div, Index, Mul, Sub}; /// - `N`: the number of dimensions (i.e. 2 for 2D vectors, 3 for 3D, etc) #[serde_as] #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub(crate) struct Vector +pub struct Vector where T: Float + Serialize + for<'d> Deserialize<'d>, { @@ -44,10 +44,8 @@ where /// Compute magnitude of vector. pub(crate) fn len(&self) -> T { - (0..N) - .map(|i| self.inner[i] * self.inner[i]) - .fold(T::zero(), |sum_sq, x| sum_sq + x) - .sqrt() + // use hypot() instead of sqrt() of sum of squares to avoid overflows + self.inner.iter().fold(T::zero(), |acc, x| acc.hypot(*x)) } /// Checks whether the `Vector` is invalid. @@ -175,9 +173,9 @@ mod tests { #[test] fn proptest_new(x: f32, y: f32, z: f32){ let v = Vector::::from([x,y,z]); - assert_eq!(v[0], x); - assert_eq!(v[1], y); - assert_eq!(v[2], z); + assert_approx_eq!(v[0], x); + assert_approx_eq!(v[1], y); + assert_approx_eq!(v[2], z); assert!(v[0].is_finite()); assert!(v[1].is_finite()); assert!(v[1].is_finite()); @@ -187,9 +185,25 @@ mod tests { fn proptest_mul(x: f32, y: f32, z: f32, m: f32) { let v = Vector::::from([x,y,z]); let w = v * m; - assert_eq!(v[0] * m, w[0]); - assert_eq!(v[1] * m, w[1]); - assert_eq!(v[2] * m, w[2]); + + let (x,y,z) = (v[0]*m, v[1]*m, v[2]*m); + if x.is_nan() || x.is_infinite() { + assert!(w[0].is_nan() || w[0].is_infinite()); + } else { + assert_approx_eq!(w[0], x); + } + + if y.is_nan() || y.is_infinite() { + assert!(w[1].is_nan() || w[1].is_infinite()); + } else { + assert_approx_eq!(w[1], y); + } + + if z.is_nan() || z.is_infinite() { + assert!(w[2].is_nan() || w[2].is_infinite()); + } else { + assert_approx_eq!(w[2], z); + } } #[test] @@ -198,23 +212,22 @@ mod tests { let w = v / d; let (x,y,z) = (v[0]/d, v[1]/d, v[2]/d); - - if x.is_nan() { - assert!(w[0].is_nan()); + if x.is_nan() || x.is_infinite() { + assert!(w[0].is_nan() ||w[0].is_infinite()); } else { - assert_eq!(v[0] / d, x); + assert_approx_eq!(w[0], x); } - if y.is_nan() { - assert!(w[1].is_nan()); + if y.is_nan() || y.is_infinite() { + assert!(w[1].is_nan() || w[1].is_infinite()); } else { - assert_eq!(v[1] / d, y); + assert_approx_eq!(w[1], y); } - if z.is_nan() { - assert!(w[2].is_nan()); + if z.is_nan() || z.is_infinite() { + assert!(w[2].is_nan() || w[2].is_infinite()); } else { - assert_eq!(v[2] / d, z); + assert_approx_eq!(w[2], z); } } } @@ -263,9 +276,9 @@ mod tests { #[test] fn test_index() { let a = Vector::::from([1.0, 2.0, 3.0]); - assert_eq!(a[0], 1.0); - assert_eq!(a[1], 2.0); - assert_eq!(a[2], 3.0); + assert_approx_eq!(a[0], 1.0); + assert_approx_eq!(a[1], 2.0); + assert_approx_eq!(a[2], 3.0); } #[test] fn test_length() {