//! Implementation of a Vivaldi height vector or coordinate. //! //! A Vivaldi height vector consists of an N-dimensional Euclidean/Cartesian space vector and a //! greater-than-zero height. This vector is intented to represent latency; hence the distance //! between two Vivaldi coordinates should be close to the actual round-trip-time (i.e. ping time). //! //! The height component is included to help solve for the triangle inequality; the fact that three //! points in the real world won't necessarily follow the triangle equality principle. In other //! words, ping times aren't perfectly correlated to geographic distance. Thus, the height can be //! considered a representation of a sort of "stem time" which is the time from a node to the core //! internet; and the vectors can be considered locations on the core internet backbone where ping //! times are more consistently correlated with geographic distance. //! //! Quoting the paper: //! //! > A height vector consists of a Euclidean coordinate augmented //! > with a height. The Euclidean portion models a high-speed Internet //! > core with latencies proportional to geographic distance, while the //! > height models the time it takes packets to travel the access link //! > from the node to the core. The cause of the access link latency may //! > be queuing delay (as in the case of an oversubscribed cable line), //! > low bandwidth (as in the case of DSL, cable modems, or telephone //! > modems), or even the sheer length of the link (as in the case of //! > long-distance fiber-optic cables). //! > A packet sent from one node to another must travel the source //! > node’s height, then travel in the Euclidean space, then travel the //! > destination node’s height. Even if the two nodes have the same //! > height, the distance between them is their Euclidean distance plus //! > the two heights. This is the fundamental difference between height //! > vectors and adding a dimension to the Euclidean space. Intuitively, //! > packet transmission can only be done in the core, not above it. //! > The height vector model is implemented by redefining the usual //! > vector operations (note the + on the right hand side of the subtrac- //! > tion equation): //! //! ```text //! [x, xh] − [y, y] = [(x − y), xh + yh] //! ∥∥∥[x, xh]∥∥∥ = ∥∥∥x∥∥∥ + xh //! α × [x, xh] = [αx, αxh ] //! ``` //! //! > Each node has a positive height element in its coordinates, so that //! > its height can always be scaled up or down. use core::ops::{Add, Mul, Sub}; use rand::prelude::*; use serde::{Deserialize, Serialize}; use crate::vector::{self, Vector}; // // **** Features **** // cfg_if::cfg_if! { if #[cfg(feature = "f32")] { /// `FloatType` is a type alias for either `f32` or `f64` depending on cargo features type FloatType = f32; } else { /// `FloatType` is a type alias for either `f32` or `f64` depending on cargo features type FloatType = f64; } } // // **** Constants **** // // // **** Structs **** // /// The `HeightVector` is a tuple containing an `N`-dimensional Cartesian vector and a `> 0` height. /// /// ## Generic Parameters /// /// - `N`: the dimensionality of the vector portion (i.e. non-height) of the Vivaldi height vector #[derive(Copy, Clone, Debug, Serialize, Deserialize)] 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, } // // **** Implementations **** // impl HeightVector { /// A new height vector is a random unit vector pub(crate) fn random() -> Self { let mut rng = thread_rng(); let mut vec = [0.0; N]; for i in vec.iter_mut().take(N) { *i = rng.gen::() - 0.5; } let height = rng.gen::().abs(); Self { position: Vector::::from(vec), height, } .normalized() } /// The magnitude of a Vivaldi height vector is defined as the magnitude of the vector plus the /// height value. pub(crate) fn len(&self) -> FloatType { self.position.len() + self.height } /// A normalized Vivaldi height vector is just like a normalized vector: the vector scaled /// by the inveerse of its length. pub(crate) fn normalized(&self) -> Self { let len = self.len(); // scale it by the inverse of its magnitude like any normal vector let ret = Self { position: self.position / len, height: self.height / len, }; if ret.is_valid() { ret } else { // if we have a bad vector, generate a new random vector Self::random() } } /// Checks whether the `HeightVector` is valid. /// /// In this case, valid means the height is positive, and none of the components are NaN or /// Inf. pub(crate) fn is_valid(&self) -> bool { !self.is_invalid() } /// Checks whether the `HeightVector` is invalid. /// /// In this case, valid means the height is positive, and none of the components are NaN or /// Inf. pub(crate) fn is_invalid(&self) -> bool { self.position.is_invalid() || self.height.is_nan() || self.height.is_infinite() || self.height < 0.0 } } // // **** Trait Implementations **** // impl Default for HeightVector { /// Default value for a Vivaldi height vector is just the defaults of its children types. fn default() -> Self { Self { position: vector::Vector::default(), height: Default::default(), } } } impl From<([FloatType; N], FloatType)> for HeightVector { /// 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::::from(value.0), height: value.1, }; if ret.is_valid() { ret } else { Self::random() } } } impl Add for HeightVector { type Output = Self; /// Add two Vivaldi height vectors. fn add(self, rhs: Self) -> Self::Output { let ret = Self { position: self.position + rhs.position, height: self.height + rhs.height, }; if ret.is_valid() { ret } else { Self::random() } } } impl Sub for HeightVector { type Output = Self; /// Subtract two Vivaldi height vectors. Note that this is the difference in the vectors /// and the summation of the heights, as defined by Vivaldi's author. fn sub(self, rhs: Self) -> Self::Output { let ret = Self { position: self.position - rhs.position, height: self.height + rhs.height, }; if ret.is_valid() { ret } else { Self::random() } } } impl Mul for HeightVector { type Output = Self; /// Multiply a Vivaldi height vector by a scalar. Works the same as normal vector scaling. fn mul(self, rhs: FloatType) -> Self::Output { let ret = Self { position: self.position * rhs, height: self.height * rhs, }; if ret.is_valid() { ret } else { Self::random() } } } // // **** Tests **** // #[allow(clippy::ignored_unit_patterns)] #[cfg(test)] mod tests { use super::*; use assert_approx_eq::assert_approx_eq; use proptest::prelude::*; fn approx_eq(a: FloatType, b: FloatType, margin: FloatType) -> bool { (a - b).abs() <= margin } proptest! { #[test] fn proptest_len(x: FloatType, y: FloatType, h: FloatType) { let len = x.hypot(y) + 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 { // we should've gottne a random univ vector here prop_assert!(approx_eq(a.len(), 1.0, 0.0001)); } else { // `HeightVector` we created from proptest values should be valid and have a length if len.is_nan() || len.is_infinite() { prop_assert!(a.len().is_nan() || a.len().is_infinite()); } else { prop_assert!(approx_eq(a.len(), len, 0.0001)); } } } #[test] #[allow(clippy::cast_lossless, clippy::cast_precision_loss)] 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 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 )); let c = a+b; prop_assert!(approx_eq(c.position[0], fx0 + fx1, 0.0001)); prop_assert!(approx_eq(c.position[1], fy0 + fy1, 0.0001)); prop_assert!(approx_eq(c.height, fh0 + fh1, 0.0001)); } #[test] #[allow(clippy::cast_lossless, clippy::cast_precision_loss)] 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 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 )); let c = a-b; prop_assert!(approx_eq(c.position[0], fx0 - fx1, 0.0001)); prop_assert!(approx_eq(c.position[1], fy0 - fy1, 0.0001)); prop_assert!(approx_eq(c.height, fh0 + fh1, 0.0001)); } #[test] #[allow(clippy::cast_lossless, clippy::cast_precision_loss)] 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 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; if fm < 0.0 { prop_assert!(approx_eq(b.len(), 1.0, 0.0001)); } else { prop_assert!(approx_eq(b.position[0], fx * fm, 0.0001)); prop_assert!(approx_eq(b.position[1], fy * fm, 0.0001)); prop_assert!(approx_eq(b.height, fh * fm, 0.0001)); } } } #[test] fn test_len() { let a = HeightVector::<3>::from(([1.0, 2.0, 3.0], 4.0)); assert_approx_eq!(a.len(), 7.741_657, 0.00001); } #[test] fn test_new() { // new gives us a random unit length vec let a = HeightVector::<3>::random(); assert_approx_eq!(a.len(), 1.0); } #[test] fn test_default() { // default() should give us a zero vec let a = HeightVector::<3>::default(); assert_approx_eq!(a.len(), 0.0); } #[test] fn test_from() { let a = HeightVector::<2>::from(([1.0, 2.0], 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] fn test_add() { 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_approx_eq!(c.position[0], 4.0); assert_approx_eq!(c.position[1], 4.0); assert_approx_eq!(c.height, 4.0); } #[test] fn test_sub() { 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_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_approx_eq!(a.position[0], 10.0); assert_approx_eq!(a.position[1], 20.0); assert_approx_eq!(a.height, 30.0); } #[test] fn test_serde() { // start with JSON, deserialize it let s = "{\"position\":[1.0,2.0,3.0],\"height\":4.0}"; let a: HeightVector<3> = serde_json::from_str(s).expect("deserialization failed during test"); // make sure it's the right length and works like we expect a normal NC assert_approx_eq!(a.len(), 7.741_657, 0.001); // serialize it into a new JSON string and make sure it matches the original let t = serde_json::to_string(&a); assert_eq!(t.as_ref().expect("serialization failed during test"), s); } #[test] fn test_sub_invalid() { let valid = HeightVector::<2>::from(([1.0, 2.0], 3.0)); let mut invalid = HeightVector::<2>::from(([1.0, 2.0], 3.0)); // force invalidity here - because `::from()` actually also catches this invalid.height = 1.0 / 0.0; let result = valid - invalid; assert_approx_eq!(result.len(), 1.0); } #[test] fn test_add_invalid() { let valid = HeightVector::<2>::from(([1.0, 2.0], 3.0)); let mut invalid = HeightVector::<2>::from(([1.0 / 0.0, 2.0], 3.0)); // make sure `from()` caught and replaced the invalid vec assert_approx_eq!(invalid.len(), 1.0); // force invalidity here - because `::from()` actually also catches this invalid.height = 1.0 / 0.0; let result = valid + invalid; assert_approx_eq!(result.len(), 1.0); } #[test] fn test_zero_norm() { let a = HeightVector::<2>::from(([0.0, 0.0], 0.0)); let b = a.normalized(); let len = b.len(); assert_approx_eq!(len, 1.0); } }