10 Commits
Author SHA1 Message Date
swaits b1c37b7b8f bump crate to 0.6.2
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-13 10:58:18 -07:00
swaits bc043ad54b fix(test): allow clippy lints on specific tests 2023-02-13 10:51:28 -07:00
swaits 96f64f189c dev: add husky for pre-commit hook generation
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-13 10:48:19 -07:00
swaits 609cc3e98c refactor: clean up by using cfg-if where possible
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-13 10:41:49 -07:00
swaits d1bc523b67 misc cleanup
- move from std::ops to core::ops
- remove unnecessary println! calls in tests

Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-13 09:52:35 -07:00
swaits e6011b4548 refactor: change unwrap()s to expects() and deny in clippy
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-13 09:30:00 -07:00
swaits c267d4b9b2 refactor: change std::time::Duration to std::core::Duration
One step closer to `no_std`.

Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-13 09:29:12 -07:00
swaits b76c6c9775 test: use prop_assert* macros where appropriate
The prop_assert* macros clean-up the assertion output by eliminating the extra panics while it’s reducing inputs.

Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-13 09:28:09 -07:00
swaits 794d6dffb1 bump crate to 0.6.1 2023-02-10 23:02:46 -05:00
swaits c3d210cf16 refactor: switch to array-init
- Use array-init for the lowest level vector operations. Makes them nicer and less bug-prone, with zero performance impact.
- Remove unnecessary/unused ::new() functions on Vector and HeightVector.
- Update README with new dependency.
- Fix typo in README.

Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-10 22:51:38 -05:00
9 changed files with 142 additions and 106 deletions

No files matched your search

+7 -7
View File
@@ -14,11 +14,11 @@ tasks:
- format: | - format: |
rustup default stable rustup default stable
cd vivaldi-nc/ cd vivaldi-nc/
cargo fmt --verbose --check cargo fmt --verbose --check --all
- lint: | - lint: |
rustup default stable rustup default stable
cd vivaldi-nc/ cd vivaldi-nc/
cargo clippy --verbose cargo clippy --verbose --all-targets --all-features
- coverage: | - coverage: |
rustup default stable rustup default stable
cd vivaldi-nc/ cd vivaldi-nc/
@@ -26,18 +26,18 @@ tasks:
- stable: | - stable: |
rustup default stable rustup default stable
cd vivaldi-nc/ cd vivaldi-nc/
cargo build --verbose cargo build --verbose --all-targets --all-features
cargo test --verbose --all-features cargo test --verbose --all-features
- nightly: | - nightly: |
rustup default nightly rustup default nightly
cd vivaldi-nc/ cd vivaldi-nc/
cargo clippy --verbose ||: cargo clippy --verbose --all-targets --all-features ||:
cargo build --verbose ||: cargo build --verbose --all-targets --all-features ||:
cargo test --verbose --all-features ||: cargo test --verbose --all-targets --all-features ||:
- bench: | - bench: |
rustup default stable rustup default stable
cd vivaldi-nc/ cd vivaldi-nc/
cargo bench --verbose --all-features cargo bench --verbose --all-targets --all-features
- examples: | - examples: |
rustup default stable rustup default stable
cd vivaldi-nc/ cd vivaldi-nc/
+4 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "vivaldi-nc" name = "vivaldi-nc"
version = "0.6.0" version = "0.6.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."
@@ -28,6 +28,7 @@ debug = true
[dev-dependencies] [dev-dependencies]
assert_approx_eq = "1.1.0" assert_approx_eq = "1.1.0"
criterion = "0.4.0" criterion = "0.4.0"
cargo-husky = { version = "1.5.0", features = ["run-for-all", "precommit-hook", "run-cargo-test", "run-cargo-check", "run-cargo-clippy", "run-cargo-fmt"] }
proptest = "1.0.0" proptest = "1.0.0"
serde_json = "1.0.91" serde_json = "1.0.91"
@@ -36,6 +37,8 @@ name = "million-updates"
harness = false harness = false
[dependencies] [dependencies]
array-init = "2.1.0"
cfg-if = "1.0.0"
num-traits = "0.2.15" num-traits = "0.2.15"
rand = "0.8.5" rand = "0.8.5"
serde = {version = "1.0.152", features = ["serde_derive"]} serde = {version = "1.0.152", features = ["serde_derive"]}
+9 -3
View File
@@ -123,9 +123,9 @@ vivaldi-nc = { version = "(version)", features = ["f32"] }
### Examples ### Examples
The repository includes an example which loads a n-to-n latency sample from The repository includes an example which loads a 490 node N-to-N latency sample
PlanetLab and iterates on `NetworkCoordinate`s until some low-enough mean error from PlanetLab and iterates on `NetworkCoordinate`s until some low-enough mean
is reached. The output is a JSON array of elements which contain the NC's 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 position, its height (or stem latency estimate), and its estimation of error
(lower is better) that look like this: (lower is better) that look like this:
@@ -188,6 +188,12 @@ This crate depends on:
- [`serde_with`](https://crates.io/crates/serde_with): Because the inner - [`serde_with`](https://crates.io/crates/serde_with): Because the inner
`Vector<T,N>` uses a const generic length, we use this to help derive `Vector<T,N>` uses a const generic length, we use this to help derive
`Deserialize`. `Deserialize`.
- [`array-init`](https://crates.io/crates/array-init): Makes the array
operations for the internal `Vector` so much nicer. Once we have
`feature(array_zip)` stabilized, we could use that instead. Until then, this
works well with zero performance impact.
- [`cfg-if`](https://crates.io/crates/cfg-if): Compile-time only macro which
makes using `cfg` parameters much nicer in code.
## Design Goals & Alternatives ## Design Goals & Alternatives
+1 -1
View File
@@ -1,4 +1,4 @@
use std::time::Duration; use core::time::Duration;
use criterion::{criterion_group, criterion_main, Criterion}; use criterion::{criterion_group, criterion_main, Criterion};
use vivaldi_nc::{NetworkCoordinate2D, NetworkCoordinate3D}; use vivaldi_nc::{NetworkCoordinate2D, NetworkCoordinate3D};
+34 -30
View File
@@ -53,13 +53,15 @@ use crate::vector::{self, Vector};
// **** Features **** // **** Features ****
// //
/// `FloatType` is a type alias for either `f32` or `f64` depending on cargo features cfg_if::cfg_if! {
#[cfg(feature = "f32")] if #[cfg(feature = "f32")] {
type FloatType = f32; /// `FloatType` is a type alias for either `f32` or `f64` depending on cargo features
type FloatType = f32;
/// `FloatType` is a type alias for either `f32` or `f64` depending on cargo features } else {
#[cfg(not(feature = "f32"))] /// `FloatType` is a type alias for either `f32` or `f64` depending on cargo features
type FloatType = f64; type FloatType = f64;
}
}
// //
// **** Constants **** // **** Constants ****
@@ -92,11 +94,6 @@ pub struct HeightVector<const N: usize> {
// //
impl<const N: usize> HeightVector<N> { impl<const N: usize> HeightVector<N> {
/// A new height vector is a random unit vector
pub(crate) fn new() -> Self {
Self::random()
}
/// A new height vector is a random unit vector /// A new height vector is a random unit vector
pub(crate) fn random() -> Self { pub(crate) fn random() -> Self {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
@@ -131,7 +128,7 @@ impl<const N: usize> HeightVector<N> {
ret ret
} else { } else {
// 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::random()
} }
} }
@@ -245,26 +242,30 @@ mod tests {
use assert_approx_eq::assert_approx_eq; use assert_approx_eq::assert_approx_eq;
use proptest::prelude::*; use proptest::prelude::*;
fn approx_eq(a: FloatType, b: FloatType, margin: FloatType) -> bool {
(a - b).abs() <= margin
}
proptest! { proptest! {
#[test] #[test]
fn proptest_len(x: FloatType, y: FloatType, h: FloatType) { fn proptest_len(x: FloatType, y: FloatType, h: FloatType) {
let len = x.hypot(y) + h.abs(); let len = x.hypot(y) + h.abs();
let a = HeightVector::<2>::from(([x,y],h)); 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 { 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 // we should've gottne a random univ vector here
assert_approx_eq!(a.len(), 1.0); prop_assert!(approx_eq(a.len(), 1.0, 0.0001));
} else { } else {
// `HeightVector` we created from proptest values should be valid and have a length // `HeightVector` we created from proptest values should be valid and have a length
if len.is_nan() || len.is_infinite() { if len.is_nan() || len.is_infinite() {
assert!(a.len().is_nan() || a.len().is_infinite()); prop_assert!(a.len().is_nan() || a.len().is_infinite());
} else { } else {
assert_approx_eq!(a.len(), len); prop_assert!(approx_eq(a.len(), len, 0.0001));
} }
} }
} }
#[test] #[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) { 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 // 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 (fx0,fy0,fh0) = (x0 as FloatType / 1_000.0, y0 as FloatType / 1_000.0, h0 as FloatType / 1_000.0);
@@ -273,12 +274,13 @@ mod tests {
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 ));
let c = a+b; let c = a+b;
assert_approx_eq!(c.position[0], fx0 + fx1); prop_assert!(approx_eq(c.position[0], fx0 + fx1, 0.0001));
assert_approx_eq!(c.position[1], fy0 + fy1); prop_assert!(approx_eq(c.position[1], fy0 + fy1, 0.0001));
assert_approx_eq!(c.height, fh0 + fh1); prop_assert!(approx_eq(c.height, fh0 + fh1, 0.0001));
} }
#[test] #[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) { 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 // 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 (fx0,fy0,fh0) = (x0 as FloatType / 1_000.0, y0 as FloatType / 1_000.0, h0 as FloatType / 1_000.0);
@@ -287,12 +289,13 @@ mod tests {
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 ));
let c = a-b; let c = a-b;
assert_approx_eq!(c.position[0], fx0 - fx1); prop_assert!(approx_eq(c.position[0], fx0 - fx1, 0.0001));
assert_approx_eq!(c.position[1], fy0 - fy1); prop_assert!(approx_eq(c.position[1], fy0 - fy1, 0.0001));
assert_approx_eq!(c.height, fh0 + fh1); prop_assert!(approx_eq(c.height, fh0 + fh1, 0.0001));
} }
#[test] #[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) { 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 // 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 (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);
@@ -300,11 +303,11 @@ mod tests {
let a = HeightVector::<2>::from(([fx, fy], fh)); let a = HeightVector::<2>::from(([fx, fy], fh));
let b = a * fm; let b = a * fm;
if fm < 0.0 { if fm < 0.0 {
assert_approx_eq!(b.len(), 1.0); prop_assert!(approx_eq(b.len(), 1.0, 0.0001));
} else { } else {
assert_approx_eq!(b.position[0], fx * fm); prop_assert!(approx_eq(b.position[0], fx * fm, 0.0001));
assert_approx_eq!(b.position[1], fy * fm); prop_assert!(approx_eq(b.position[1], fy * fm, 0.0001));
assert_approx_eq!(b.height, fh * fm); prop_assert!(approx_eq(b.height, fh * fm, 0.0001));
} }
} }
} }
@@ -318,7 +321,7 @@ mod tests {
#[test] #[test]
fn test_new() { fn test_new() {
// new gives us a random unit length vec // new gives us a random unit length vec
let a = HeightVector::<3>::new(); let a = HeightVector::<3>::random();
assert_approx_eq!(a.len(), 1.0); assert_approx_eq!(a.len(), 1.0);
} }
@@ -369,14 +372,15 @@ mod tests {
fn test_serde() { fn test_serde() {
// start with JSON, deserialize it // start with JSON, deserialize it
let s = "{\"position\":[1.0,2.0,3.0],\"height\":4.0}"; let s = "{\"position\":[1.0,2.0,3.0],\"height\":4.0}";
let a: HeightVector<3> = serde_json::from_str(s).unwrap(); 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 // 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); assert_approx_eq!(a.len(), 7.741_657, 0.001);
// serialize it into a new JSON string and make sure it matches the original // serialize it into a new JSON string and make sure it matches the original
let t = serde_json::to_string(&a); let t = serde_json::to_string(&a);
assert_eq!(t.as_ref().unwrap(), s); assert_eq!(t.as_ref().expect("serialization failed during test"), s);
} }
#[test] #[test]
+1
View File
@@ -102,6 +102,7 @@
#![deny(clippy::cargo)] #![deny(clippy::cargo)]
#![deny(clippy::pedantic)] #![deny(clippy::pedantic)]
#![deny(clippy::nursery)] #![deny(clippy::nursery)]
#![deny(clippy::unwrap_used)]
#![allow(clippy::type_repetition_in_bounds)] #![allow(clippy::type_repetition_in_bounds)]
#![allow(clippy::module_name_repetitions)] #![allow(clippy::module_name_repetitions)]
+27 -22
View File
@@ -2,7 +2,7 @@
//! //!
//! For usage explanation and examples, please see the main [`crate`] documentation. //! For usage explanation and examples, please see the main [`crate`] documentation.
use std::time::Duration; use core::time::Duration;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -124,11 +124,13 @@ impl<const N: usize> NetworkCoordinate<N> {
#[must_use] #[must_use]
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
#[cfg(feature = "f32")] cfg_if::cfg_if! {
return Duration::from_secs_f32((self.heightvec - rhs.heightvec).len() / 1000.0); if #[cfg(feature = "f32")] {
Duration::from_secs_f32((self.heightvec - rhs.heightvec).len() / 1000.0)
#[cfg(not(feature = "f32"))] } else {
return Duration::from_secs_f64((self.heightvec - rhs.heightvec).len() / 1000.0); 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
@@ -146,7 +148,7 @@ impl<const N: usize> NetworkCoordinate<N> {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use std::time::Duration; /// use core::time::Duration;
/// use vivaldi_nc::NetworkCoordinate; /// use vivaldi_nc::NetworkCoordinate;
/// ///
/// // We always have our own NC: /// // We always have our own NC:
@@ -190,15 +192,15 @@ impl<const N: usize> NetworkCoordinate<N> {
/// ///
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 // convert Durations into FloatType as fractional milliseconds for convenience
#[cfg(feature = "f32")] cfg_if::cfg_if! {
let rtt_ms = rtt.as_secs_f32() * 1000.0; if #[cfg(feature = "f32")] {
#[cfg(feature = "f32")] let rtt_ms = rtt.as_secs_f32() * 1000.0;
let rtt_estimated_ms = self.estimated_rtt(rhs).as_secs_f32() * 1000.0; let rtt_estimated_ms = self.estimated_rtt(rhs).as_secs_f32() * 1000.0;
} else {
#[cfg(not(feature = "f32"))] let rtt_ms = rtt.as_secs_f64() * 1000.0;
let rtt_ms = rtt.as_secs_f64() * 1000.0; let rtt_estimated_ms = self.estimated_rtt(rhs).as_secs_f64() * 1000.0;
#[cfg(not(feature = "f32"))] }
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_ms < 0.0 { if rtt_ms < 0.0 {
@@ -352,7 +354,6 @@ mod tests {
// compute and test the root mean squared error // compute and test the root mean squared error
let error = slc.error + nyc.error + lax.error + mad.error; let error = slc.error + nyc.error + lax.error + mad.error;
println!("error = {error}");
assert!(error < 5.0); assert!(error < 5.0);
} }
@@ -360,7 +361,8 @@ mod tests {
fn test_serde() { fn test_serde() {
// start with JSON, deserialize it // start with JSON, deserialize it
let s = "{\"position\":[1.5,0.5,2.0],\"height\":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(); let a: NetworkCoordinate<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 // 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_approx_eq!(a.heightvec.len(), 2.649_509, 0.001);
@@ -369,16 +371,18 @@ mod tests {
// serialize it into a new JSON string and make sure it matches the original // serialize it into a new JSON string and make sure it matches the original
let t = serde_json::to_string(&a); let t = serde_json::to_string(&a);
assert_eq!(t.as_ref().unwrap(), s); assert_eq!(t.as_ref().expect("serialization failed during test"), s);
} }
#[test] #[test]
fn test_estimated_rtt() { fn test_estimated_rtt() {
// start with JSON, deserialize it // start with JSON, deserialize it
let s = "{\"position\":[1.5,0.5,2.0],\"height\":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 a: NetworkCoordinate<3> =
serde_json::from_str(s).expect("deserialization failed during test");
let s = "{\"position\":[-1.5,-0.5,-2.0],\"height\":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 b: NetworkCoordinate<3> =
serde_json::from_str(s).expect("deserialization failed during test");
let estimate = a.estimated_rtt(&b); let estimate = a.estimated_rtt(&b);
assert_approx_eq!(estimate.as_secs_f32(), 0.080_099); assert_approx_eq!(estimate.as_secs_f32(), 0.080_099);
@@ -387,7 +391,8 @@ mod tests {
#[test] #[test]
fn test_error_getter() { fn test_error_getter() {
let s = "{\"position\":[1.5,0.5,2.0],\"height\":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 a: NetworkCoordinate<3> =
serde_json::from_str(s).expect("deserialization failed during test");
assert_approx_eq!(a.error(), 1.0); assert_approx_eq!(a.error(), 1.0);
} }
} }
+37 -42
View File
@@ -3,11 +3,12 @@
//! This is the simplest possible implementation of a vector to support the needs of a //! This is the simplest possible implementation of a vector to support the needs of a
//! Vivaldi `HeightVector`. //! Vivaldi `HeightVector`.
use array_init::array_init;
use num_traits::Float; use num_traits::Float;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::serde_as; use serde_with::serde_as;
use std::ops::{Add, Div, Index, Mul, Sub}; use core::ops::{Add, Div, Index, Mul, Sub};
// //
// **** Structs **** // **** Structs ****
@@ -37,11 +38,6 @@ impl<T, const N: usize> Vector<T, N>
where where
T: Float + Serialize + for<'d> Deserialize<'d>, T: Float + Serialize + for<'d> Deserialize<'d>,
{ {
/// Create a new Zero vector.
pub(crate) fn new() -> Self {
Self::default()
}
/// Compute magnitude of vector. /// Compute magnitude of vector.
pub(crate) fn len(&self) -> T { pub(crate) fn len(&self) -> T {
// use hypot() instead of sqrt() of sum of squares to avoid overflows // use hypot() instead of sqrt() of sum of squares to avoid overflows
@@ -90,11 +86,9 @@ where
/// Vector+Vector addition. /// Vector+Vector addition.
fn add(self, rhs: Self) -> Self::Output { fn add(self, rhs: Self) -> Self::Output {
let mut ret = [T::zero(); N]; //Self::Output::new(); Self::Output {
for i in 0..N { inner: array_init(|i| self[i] + rhs[i]),
ret[i] = self[i] + rhs[i];
} }
Self::Output::from(ret)
} }
} }
@@ -106,11 +100,9 @@ where
/// Vector-Vector subtraction. /// Vector-Vector subtraction.
fn sub(self, rhs: Self) -> Self::Output { fn sub(self, rhs: Self) -> Self::Output {
let mut ret = [T::zero(); N]; //Self::Output::new(); Self::Output {
for i in 0..N { inner: array_init(|i| self[i] - rhs[i]),
ret[i] = self[i] - rhs[i];
} }
Self::Output::from(ret)
} }
} }
@@ -122,11 +114,9 @@ where
/// Vector*scalar multiplication. /// Vector*scalar multiplication.
fn mul(self, rhs: T) -> Self::Output { fn mul(self, rhs: T) -> Self::Output {
let mut ret = Self::Output::new(); Self::Output {
for i in 0..N { inner: array_init(|i| self[i] * rhs),
ret.inner[i] = self.inner[i] * rhs;
} }
ret
} }
} }
@@ -138,11 +128,9 @@ where
/// Vector/scalar division. /// Vector/scalar division.
fn div(self, rhs: T) -> Self::Output { fn div(self, rhs: T) -> Self::Output {
let mut ret = Self::Output::new(); Self::Output {
for i in 0..N { inner: array_init(|i| self[i] / rhs),
ret.inner[i] = self.inner[i] / rhs;
} }
ret
} }
} }
@@ -169,16 +157,20 @@ mod tests {
use assert_approx_eq::assert_approx_eq; use assert_approx_eq::assert_approx_eq;
use proptest::prelude::*; use proptest::prelude::*;
fn approx_eq(a: f32, b: f32, margin: f32) -> bool {
(a - b).abs() <= margin
}
proptest! { proptest! {
#[test] #[test]
fn proptest_new(x: f32, y: f32, z: f32){ fn proptest_new(x: f32, y: f32, z: f32){
let v = Vector::<f32, 3>::from([x,y,z]); let v = Vector::<f32, 3>::from([x,y,z]);
assert_approx_eq!(v[0], x); prop_assert!(approx_eq(v[0], x, 0.0001));
assert_approx_eq!(v[1], y); prop_assert!(approx_eq(v[1], y, 0.0001));
assert_approx_eq!(v[2], z); prop_assert!(approx_eq(v[2], z, 0.0001));
assert!(v[0].is_finite()); prop_assert!(v[0].is_finite());
assert!(v[1].is_finite()); prop_assert!(v[1].is_finite());
assert!(v[1].is_finite()); prop_assert!(v[1].is_finite());
} }
#[test] #[test]
@@ -188,21 +180,21 @@ mod tests {
let (x,y,z) = (v[0]*m, v[1]*m, v[2]*m); let (x,y,z) = (v[0]*m, v[1]*m, v[2]*m);
if x.is_nan() || x.is_infinite() { if x.is_nan() || x.is_infinite() {
assert!(w[0].is_nan() || w[0].is_infinite()); prop_assert!(w[0].is_nan() || w[0].is_infinite());
} else { } else {
assert_approx_eq!(w[0], x); prop_assert!(approx_eq(w[0], x, 0.0001));
} }
if y.is_nan() || y.is_infinite() { if y.is_nan() || y.is_infinite() {
assert!(w[1].is_nan() || w[1].is_infinite()); prop_assert!(w[1].is_nan() || w[1].is_infinite());
} else { } else {
assert_approx_eq!(w[1], y); prop_assert!(approx_eq(w[1], y, 0.0001));
} }
if z.is_nan() || z.is_infinite() { if z.is_nan() || z.is_infinite() {
assert!(w[2].is_nan() || w[2].is_infinite()); prop_assert!(w[2].is_nan() || w[2].is_infinite());
} else { } else {
assert_approx_eq!(w[2], z); prop_assert!(approx_eq(w[2], z, 0.0001));
} }
} }
@@ -213,21 +205,21 @@ mod tests {
let (x,y,z) = (v[0]/d, v[1]/d, v[2]/d); let (x,y,z) = (v[0]/d, v[1]/d, v[2]/d);
if x.is_nan() || x.is_infinite() { if x.is_nan() || x.is_infinite() {
assert!(w[0].is_nan() ||w[0].is_infinite()); prop_assert!(w[0].is_nan() ||w[0].is_infinite());
} else { } else {
assert_approx_eq!(w[0], x); prop_assert!(approx_eq(w[0], x, 0.0001));
} }
if y.is_nan() || y.is_infinite() { if y.is_nan() || y.is_infinite() {
assert!(w[1].is_nan() || w[1].is_infinite()); prop_assert!(w[1].is_nan() || w[1].is_infinite());
} else { } else {
assert_approx_eq!(w[1], y); prop_assert!(approx_eq(w[1], y, 0.0001));
} }
if z.is_nan() || z.is_infinite() { if z.is_nan() || z.is_infinite() {
assert!(w[2].is_nan() || w[2].is_infinite()); prop_assert!(w[2].is_nan() || w[2].is_infinite());
} else { } else {
assert_approx_eq!(w[2], z); prop_assert!(approx_eq(w[2], z, 0.0001));
} }
} }
} }
@@ -235,7 +227,7 @@ mod tests {
#[test] #[test]
fn test_default_new() { fn test_default_new() {
let a = Vector::<f32, 3>::default(); let a = Vector::<f32, 3>::default();
let b = Vector::<f32, 3>::new(); let b = Vector::<f32, 3>::from([1.0; 3]) * 0.0;
let c = Vector::<f32, 3>::from([0.0; 3]); let c = Vector::<f32, 3>::from([0.0; 3]);
assert_eq!(a, b); assert_eq!(a, b);
assert_eq!(b, c); assert_eq!(b, c);
@@ -293,7 +285,10 @@ mod tests {
fn test_serde() { fn test_serde() {
let a = Vector::<f32, 3>::from([1.0, 2.0, 3.0]); let a = Vector::<f32, 3>::from([1.0, 2.0, 3.0]);
let s = serde_json::to_string(&a); let s = serde_json::to_string(&a);
assert_eq!(s.as_ref().unwrap(), "{\"position\":[1.0,2.0,3.0]}"); assert_eq!(
s.as_ref().expect("serialization failed in test"),
"{\"position\":[1.0,2.0,3.0]}"
);
assert!(s.is_ok()); assert!(s.is_ok());
} }
} }
+22
View File
@@ -0,0 +1,22 @@
// This is just for basic interface and happy path testing. It doesn't do anything other than make
// sure the expected public interface exists and works.
use array_init::array_init;
use core::time::Duration;
use vivaldi_nc::NetworkCoordinate;
const NUM_NODES: usize = 100;
#[test]
fn basic_usage() {
let mut nc: [NetworkCoordinate<2>; NUM_NODES] = array_init(|_| NetworkCoordinate::<2>::new());
for i in 0..NUM_NODES {
for j in 0..NUM_NODES {
if i == j {
continue;
}
let other = nc[j].clone();
nc[i].update(&other, Duration::from_millis(100));
}
}
}