7 Commits
Author SHA1 Message Date
swaits c3b3ff1e4d feat: f32/f64 cargo features added
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-08 17:01:19 -07:00
swaits 2addbc68b5 doc: minor updates re: type aliases, crate to 0.5.1
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-08 15:59:15 -07:00
swaits 6294d1e520 doc: udpate crate description 2023-02-08 15:48:46 -07:00
swaits 2f8769012b bump crate to 0.5.0
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-08 15:40:47 -07:00
swaits 57891ed230 feat: type aliases for 2D/3D NetworkCoordinates
This just adds a few type aliases for convenience, and slightly better ergonomics for the most common usage (2D or 3D).

Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-08 15:40:23 -07:00
swaits fce4946369 feat: improve ergonomics for serialization format
Goal: To make the serialization format more sensible instead of it being a reflection of our internal (ie private) data model.

- mostly handled with #[serde()] macros (renaming, flattening)
- renamed NetworkCoordinate::position to heightvec which is a better name, even if not seen in the published interface
- changed HeightVector from a tuple struct to a named struct so we can control serde on a field-by-field basis
- updated README, tests, and doc tests to match

Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-08 15:32:27 -07:00
swaits 7f41676795 fix: exclude examples from crate
crates.io is limited to 10MB crates

Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-07 22:09:28 -07:00
6 changed files with 222 additions and 99 deletions

No files matched your search

+10 -2
View File
@@ -1,9 +1,9 @@
[package]
name = "vivaldi-nc"
version = "0.4.0"
version = "0.5.2"
edition = "2021"
authors = ["Stephen Waits <steve@waits.net>"]
description = "Vivaldi network coordinates with a simple interface."
description = "Vivaldi network coordinates for fast, distributed latency estimates in multinode networks, with a clean and simple interface."
license = "MIT"
keywords = ["vivaldi", "network", "coordinates", "distributed", "p2p"]
categories = ["algorithms", "network-programming"]
@@ -11,6 +11,14 @@ documentation = "https://docs.rs/vivaldi-nc/"
readme = "README.md"
homepage = "https://sr.ht/~swaits/vivaldi-nc/"
repository = "https://git.sr.ht/~swaits/vivaldi-nc"
exclude = [
"examples/*",
]
[features]
default = ["f32"]
f32 = []
f64 = []
[dev-dependencies]
assert_approx_eq = "1.1.0"
+46 -17
View File
@@ -110,30 +110,59 @@ 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
Run an example which loads a n-to-n latency sample from PlanetLab and iterates
on `NetworkCoordinate`s until some low-enough mean error is reached. The output is a JSON array of elements that look like this:
The repository includes an example which loads a n-to-n latency sample from
PlanetLab and iterates on `NetworkCoordinate`s until some low-enough mean error
is reached. The output is a JSON array of elements which contain the NC's
position, its height (or stem latency estimate), and its estimation of error
(lower is better) that look like this:
```json
{
"position": [
{
"inner": [ # Euclidean position (3D in this case):
9.249476, # first dimension; "x"
23.747225, # second dimension; "x"
-6.473008 # third dimension; "x"
]
},
72.10599 # Height component (estimate of stem latency in milliseconds)
],
"error": 2.9166403 # Estimated error (lower is better)
},
{
"position": [
5.563593,
-2.332495,
7.3957834
],
"height": 55.56651,
"error": 3.241348
}
```
To run the example:
Adding some detail to these three fields:
- `position`: Estimated position of this node (or `NetworkCoordinate`) on the
network core. Think of this like the node's endpoint into the Internet
backbone. This is a cartesian coordinate in a multi-dimensional latency space
(in this case 3D), measured in milliseconds.
- `height`: Estimated stem time, which is the time from the node itself to the
backbone. So, for example this might represent the latency between your
node's actual location, like your home, and the point at which it gets into
the highest-speed, core part of the Internet. Height helps adjust for
[triangle inequality violations](https://en.wikipedia.org/wiki/Triangle_inequality),
which are common on the Internet.
- `error`: Estimated error of the current `position`, and `height`. Lower is
better.
To run the example, first clone the repository locally:
```bash
git clone https://git.sr.ht/~swaits/vivaldi-nc
```
Then `cd` into the local clone and run the example:
```bash
cd vivaldi-nc
cargo run --example planetlab
```
+88 -50
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 ****
//
@@ -63,7 +72,11 @@ use crate::vector::Vector;
///
/// - `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<const N: usize>(Vector<f32, N>, f32);
pub(crate) struct HeightVector<const N: usize> {
#[serde(flatten)]
position: Vector<FloatType, N>,
height: FloatType,
}
//
// **** Implementations ****
@@ -80,28 +93,35 @@ 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();
Self(Vector::<f32, N>::from(vec), height).normalized()
let height = rng.generate::<FloatType>().abs();
Self {
position: Vector::<FloatType, N>::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) -> f32 {
self.0.len() + self.1
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();
if len < f32::EPSILON {
if len < FloatType::EPSILON {
// if we have a bad vector, generate a new random vector
Self::new()
} else {
// otherwise scale it by the inverse of its magnitude like any normal vector
Self(self.0 / len, self.1 / len)
Self {
position: self.position / len,
height: self.height / len,
}
}
}
@@ -118,7 +138,10 @@ impl<const N: usize> HeightVector<N> {
/// 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.0.is_invalid() || self.1.is_nan() || self.1.is_infinite() || self.1 < 0.0
self.position.is_invalid()
|| self.height.is_nan()
|| self.height.is_infinite()
|| self.height < 0.0
}
}
@@ -129,14 +152,20 @@ impl<const N: usize> HeightVector<N> {
impl<const N: usize> Default for HeightVector<N> {
/// Default value for a Vivaldi height vector is just the defaults of its children types.
fn default() -> Self {
Self(Default::default(), Default::default())
Self {
position: Default::default(),
height: Default::default(),
}
}
}
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 {
let ret = Self(Vector::<f32, N>::from(value.0), value.1);
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::<FloatType, N>::from(value.0),
height: value.1,
};
if ret.is_valid() {
ret
} else {
@@ -150,7 +179,10 @@ impl<const N: usize> Add for HeightVector<N> {
/// Add two Vivaldi height vectors.
fn add(self, rhs: Self) -> Self::Output {
let ret = Self(self.0 + rhs.0, self.1 + rhs.1);
let ret = Self {
position: self.position + rhs.position,
height: self.height + rhs.height,
};
if ret.is_valid() {
ret
} else {
@@ -165,7 +197,10 @@ impl<const N: usize> Sub for HeightVector<N> {
/// 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(self.0 - rhs.0, self.1 + rhs.1);
let ret = Self {
position: self.position - rhs.position,
height: self.height + rhs.height,
};
if ret.is_valid() {
ret
} else {
@@ -174,12 +209,15 @@ 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 {
let ret = Self(self.0 * rhs, self.1 * rhs);
fn mul(self, rhs: FloatType) -> Self::Output {
let ret = Self {
position: self.position * rhs,
height: self.height * rhs,
};
if ret.is_valid() {
ret
} else {
@@ -199,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 {
@@ -213,45 +251,45 @@ 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 ));
let c = a+b;
assert_eq!(c.0[0], fx0 + fx1);
assert_eq!(c.0[1], fy0 + fy1);
assert_eq!(c.1, fh0 + fh1);
assert_eq!(c.position[0], fx0 + fx1);
assert_eq!(c.position[1], fy0 + fy1);
assert_eq!(c.height, fh0 + fh1);
}
#[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 ));
let c = a-b;
assert_eq!(c.0[0], fx0 - fx1);
assert_eq!(c.0[1], fy0 - fy1);
assert_eq!(c.1, fh0 + fh1);
assert_eq!(c.position[0], fx0 - fx1);
assert_eq!(c.position[1], fy0 - fy1);
assert_eq!(c.height, fh0 + fh1);
}
#[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;
if fm < 0.0 {
assert_approx_eq!(b.len(), 1.0)
} else {
assert_eq!(b.0[0], fx * fm);
assert_eq!(b.0[1], fy * fm);
assert_eq!(b.1, fh * fm);
assert_eq!(b.position[0], fx * fm);
assert_eq!(b.position[1], fy * fm);
assert_eq!(b.height, fh * fm);
}
}
}
@@ -279,9 +317,9 @@ mod tests {
#[test]
fn test_from() {
let a = HeightVector::<2>::from(([1.0, 2.0], 3.0));
assert_eq!(a.0[0], 1.0);
assert_eq!(a.0[1], 2.0);
assert_eq!(a.1, 3.0);
assert_eq!(a.position[0], 1.0);
assert_eq!(a.position[1], 2.0);
assert_eq!(a.height, 3.0);
}
#[test]
@@ -289,9 +327,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.0[0], 4.0);
assert_eq!(c.0[1], 4.0);
assert_eq!(c.1, 4.0);
assert_eq!(c.position[0], 4.0);
assert_eq!(c.position[1], 4.0);
assert_eq!(c.height, 4.0);
}
#[test]
@@ -299,23 +337,23 @@ 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.0[0], -2.0);
assert_eq!(c.0[1], 0.0);
assert_eq!(c.1, 4.0);
assert_eq!(c.position[0], -2.0);
assert_eq!(c.position[1], 0.0);
assert_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.0[0], 10.0);
assert_eq!(a.0[1], 20.0);
assert_eq!(a.1, 30.0);
assert_eq!(a.position[0], 10.0);
assert_eq!(a.position[1], 20.0);
assert_eq!(a.height, 30.0);
}
#[test]
fn test_serde() {
// start with JSON, deserialize it
let s = "[{\"inner\":[1.0,2.0,3.0]},4.0]";
let s = "{\"position\":[1.0,2.0,3.0],\"height\":4.0}";
let a: HeightVector<3> = serde_json::from_str(s).unwrap();
// make sure it's the right length and works like we expect a normal NC
+18 -7
View File
@@ -1,4 +1,5 @@
//! Vivaldi Network Coordinates with a simple interface
//! Vivaldi network coordinates for fast, distributed latency estimates in multinode networks, with
//! a clean and simple interface.
//!
//! Network Coordinates (NC) are a way to represent a node's position in a network's
//! latency space. Nodes with low latency (ping or round trip time) between each
@@ -19,7 +20,8 @@
//!
//! # Usage
//!
//! This crate exports a single struct, [`NetworkCoordinate`]. Typical use of Vivaldi NCs for a
//! This crate exports a single struct, [`NetworkCoordinate`] and two type aliases
//! ([`NetworkCoordinate2D`] and [`NetworkCoordinate3D`]). Typical use of Vivaldi NCs for a
//! distributed network works like this:
//!
//! 1. Each node in the network has its own instance of [`NetworkCoordinate`]. *See "Note on
@@ -51,7 +53,7 @@
//!
//! // mock up a remote NC for the sake of this example (normally the node would actually receive
//! // this from a remote node)
//! let received_msg = "{\"position\":[{\"inner\":[1.5,0.5,2.0]},0.1],\"error\":1.0}";
//! let received_msg = "{\"position\":[1.5,0.5,2.0],\"height\":0.1,\"error\":1.0}";
//! let remote_nc: NetworkCoordinate<3> = serde_json::from_str(received_msg).unwrap();
//!
//! // mock up a measured RTT between us and that node (normally the nodes would coordinate
@@ -69,16 +71,19 @@
//! forwarded to us by someone else. And in this case, we aren't measuring the actual RTT. Instead,
//! we're using the NCs to estimate RTT.
//!
//! This example also uses the type alias [`NetworkCoordinate3D`] instead of its equivalent
//! [`NetworkCoordinate<3>`].
//!
//! ```
//! use vivaldi_nc::NetworkCoordinate;
//! use vivaldi_nc::NetworkCoordinate3D;
//!
//! // create our local NC as a 3-dimensional Vivaldi coordinate
//! let mut my_nc = NetworkCoordinate::<3>::new();
//! let mut my_nc = NetworkCoordinate3D::new();
//!
//! // mock up a remote NC for the sake of this example (normally the node would actually receive
//! // this from a remote node)
//! let received_msg = "{\"position\":[{\"inner\":[1.5,0.5,2.0]},0.1],\"error\":1.0}";
//! let remote_nc: NetworkCoordinate<3> = serde_json::from_str(received_msg).unwrap();
//! let received_msg = "{\"position\":[1.5,0.5,2.0],\"height\":0.1,\"error\":1.0}";
//! let remote_nc: NetworkCoordinate3D = serde_json::from_str(received_msg).unwrap();
//!
//! // estimate the RTT to this remote node without measuring it directly
//! let rtt_estimated = my_nc.estimated_rtt(&remote_nc);
@@ -91,6 +96,12 @@
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;
pub use network_coordinate::NetworkCoordinate2D;
pub use network_coordinate::NetworkCoordinate3D;
+58 -22
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 ****
@@ -51,10 +60,25 @@ const MIN_ERROR: f32 = f32::EPSILON;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NetworkCoordinate<const N: usize> {
position: HeightVector<N>,
error: f32,
#[serde(flatten)]
heightvec: HeightVector<N>,
error: FloatType,
}
// type aliases for convenience
/// A 2D [`NetworkCoordinate`]. Includes a 2D Euclidean position and a height.
///
/// This type alias is just for convenience. It's functionally equivalent to
/// `NetworkCoordinate<2>`.
pub type NetworkCoordinate2D = NetworkCoordinate<2>;
/// A 3D [`NetworkCoordinate`]. Includes a 3D Euclidean position and a height.
///
/// This type alias is just for convenience. It's functionally equivalent to
/// `NetworkCoordinate<3>`.
pub type NetworkCoordinate3D = NetworkCoordinate<3>;
//
// **** Implementations ****
//
@@ -106,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.position - rhs.position).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
@@ -140,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);
@@ -166,11 +201,12 @@ impl<const N: usize> NetworkCoordinate<N> {
// δ = cc × w
let delta = C_DELTA * w;
// xi = xi + δ × (rtt − ‖xi − xj ‖) × u(xi − xj)
self.position = self.position + (self.position - rhs.position).normalized() * delta * error;
self.heightvec =
self.heightvec + (self.heightvec - rhs.heightvec).normalized() * delta * error;
// if we ended up with an invalid coordinate, return a new random coordinate with default
// error
if self.position.is_invalid() {
if self.heightvec.is_invalid() {
*self = Self::new()
}
@@ -180,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
}
}
@@ -193,7 +229,7 @@ impl<const N: usize> Default for NetworkCoordinate<N> {
/// A default `NetworkCoordinate` has a random position and DEFAULT_ERROR
fn default() -> Self {
Self {
position: HeightVector::<N>::random(),
heightvec: HeightVector::<N>::random(),
error: DEFAULT_ERROR,
}
}
@@ -258,8 +294,8 @@ mod tests {
// 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();
(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);
// iterate plenty of times to converge and minimize error
@@ -292,11 +328,11 @@ mod tests {
#[test]
fn test_serde() {
// start with JSON, deserialize it
let s = "{\"position\":[{\"inner\":[1.5,0.5,2.0]},0.1],\"error\":1.0}";
let s = "{\"position\":[1.5,0.5,2.0],\"height\":0.1,\"error\":1.0}";
let a: NetworkCoordinate<3> = serde_json::from_str(s).unwrap();
// make sure it's the right length and works like we expect a normal NC
assert_approx_eq!(a.position.len(), 2.649_509, 0.001);
assert_approx_eq!(a.heightvec.len(), 2.649_509, 0.001);
assert_eq!(a.error, 1.0);
assert_eq!(a.estimated_rtt(&a).as_millis(), 0);
@@ -308,9 +344,9 @@ mod tests {
#[test]
fn test_estimated_rtt() {
// start with JSON, deserialize it
let s = "{\"position\":[{\"inner\":[1.5,0.5,2.0]},25.0],\"error\":1.0}";
let s = "{\"position\":[1.5,0.5,2.0],\"height\":25.0,\"error\":1.0}";
let a: NetworkCoordinate<3> = serde_json::from_str(s).unwrap();
let s = "{\"position\":[{\"inner\":[-1.5,-0.5,-2.0]},50.0],\"error\":1.0}";
let s = "{\"position\":[-1.5,-0.5,-2.0],\"height\":50.0,\"error\":1.0}";
let b: NetworkCoordinate<3> = serde_json::from_str(s).unwrap();
let estimate = a.estimated_rtt(&b);
+2 -1
View File
@@ -24,6 +24,7 @@ pub(crate) struct Vector<T, const N: usize>
where
T: Float + Serialize + for<'d> Deserialize<'d>,
{
#[serde(rename = "position")]
#[serde_as(as = "[_; N]")]
inner: [T; N],
}
@@ -279,7 +280,7 @@ mod tests {
fn test_serde() {
let a = Vector::<f32, 3>::from([1.0, 2.0, 3.0]);
let s = serde_json::to_string(&a);
assert_eq!(s.as_ref().unwrap(), "{\"inner\":[1.0,2.0,3.0]}");
assert_eq!(s.as_ref().unwrap(), "{\"position\":[1.0,2.0,3.0]}");
assert!(s.is_ok());
}
}