Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2d14bcc0d
|
||
|
|
25e0076c8d
|
||
|
|
aba483c9e1
|
||
|
|
e9febc6faa
|
||
|
|
c83a4e654b
|
||
|
|
2fad2e18b5
|
||
|
|
3415347072
|
||
|
|
5611618126
|
||
|
|
c2399c5b8f
|
||
|
|
f35680738c
|
No files matched your search
+24
@@ -0,0 +1,24 @@
|
||||
image: archlinux
|
||||
packages:
|
||||
- rustup
|
||||
sources:
|
||||
- https://git.sr.ht/~swaits/vivaldi-nc
|
||||
tasks:
|
||||
- setup: |
|
||||
rustup toolchain install nightly stable
|
||||
cd vivaldi-nc/
|
||||
rustup run stable cargo fetch
|
||||
- stable: |
|
||||
rustup default stable
|
||||
cd vivaldi-nc/
|
||||
cargo build --verbose
|
||||
cargo test --verbose
|
||||
- nightly: |
|
||||
rustup default nightly
|
||||
cd vivaldi-nc/
|
||||
cargo build --verbose ||:
|
||||
cargo test --verbose ||:
|
||||
- docs: |
|
||||
cd vivaldi-nc/
|
||||
rustup run stable cargo doc --no-deps
|
||||
rustup run nightly cargo doc --no-deps ||:
|
||||
@@ -1 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
Generated
-192
@@ -1,192 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "assert_approx_eq"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c07dab4369547dbe5114677b33fbbf724971019f3818172d59a97a61c774ffd"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.139"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanorand"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
||||
|
||||
[[package]]
|
||||
name = "vivaldi-nc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"assert_approx_eq",
|
||||
"nanorand",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
|
||||
+10
-3
@@ -1,16 +1,23 @@
|
||||
[package]
|
||||
name = "vivaldi-nc"
|
||||
version = "0.1.0"
|
||||
version = "0.3.1"
|
||||
edition = "2021"
|
||||
authors = ["Stephen Waits <steve@waits.net>"]
|
||||
description = "An implementation of Vivaldi network coordinates."
|
||||
description = "Vivaldi network coordinates with a simple interface."
|
||||
license = "MIT"
|
||||
keywords = ["vivaldi", "network coordinates", "distributed", "p2p"]
|
||||
keywords = ["vivaldi", "network", "coordinates", "distributed", "p2p"]
|
||||
categories = ["algorithms", "network-programming"]
|
||||
documentation = "https://docs.rs/vivaldi-nc/"
|
||||
readme = "README.md"
|
||||
homepage = "https://sr.ht/~swaits/vivaldi-nc/"
|
||||
repository = "https://git.sr.ht/~swaits/vivaldi-nc"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_approx_eq = "1.1.0"
|
||||
serde_json = "1.0.91"
|
||||
|
||||
[dependencies]
|
||||
nanorand = {version = "0.7.0", features = ["wyrand", "getrandom"]}
|
||||
num-traits = "0.2.15"
|
||||
serde = {version = "1.0.152", features = ["serde_derive"]}
|
||||
serde_with = "2.2.0"
|
||||
@@ -1,5 +1,7 @@
|
||||
# vivaldi-nc - Vivaldi Network Coordinates
|
||||
|
||||
[](https://docs.rs/vivaldi-nc/) [](https://builds.sr.ht/~swaits/vivaldi-nc?) [](https://crates.io/crates/vivaldi-nc)  
|
||||
|
||||
## Introduction
|
||||
|
||||
Network Coordinates (NC) are a way to represent a node's position in a network's
|
||||
@@ -8,11 +10,11 @@ other will be close to each other. Nodes with high latency between them will be
|
||||
far from each other.
|
||||
|
||||
This is an implementation of Vivaldi Network Coordinates, a specific NC
|
||||
algorithm, with a public simple interface and few dependencies. Vivaldi
|
||||
coordinates are typically used in distributed networking applications, like p2p
|
||||
networks. They allow each node in the network to estimate its position in a
|
||||
latency space in a distributed way, with no central authority. This enables
|
||||
nodes to understand estimated latency to any other node in the system.
|
||||
algorithm, with a simple interface and few dependencies. Vivaldi coordinates
|
||||
are typically used in distributed networking applications, like p2p networks.
|
||||
They allow each node in the network to estimate its position in a latency space
|
||||
in a distributed way, with no central authority. This enables nodes to
|
||||
understand estimated latency to any other node in the system.
|
||||
|
||||
According to the [Vivaldi coordinates article on Wikipedia](https://en.wikipedia.org/wiki/Vivaldi_coordinates):
|
||||
|
||||
@@ -60,9 +62,53 @@ Regarding the two drawbacks mentioned by the Wikiepedia article, quoted above:
|
||||
|
||||
### Getting Started
|
||||
|
||||
### Features
|
||||
Add the crate to your project:
|
||||
|
||||
### Examples
|
||||
```bash
|
||||
cargo add vivaldi-nc
|
||||
```
|
||||
|
||||
Each node in the network should create a `NetworkCoordinate` (NC). The node
|
||||
uses this structure to track its latency position in the entire network.
|
||||
|
||||
```rust
|
||||
use vivaldi_nc::NetworkCoordinate;
|
||||
|
||||
// create a 2-dimensional NetworkCoordinate to track my position
|
||||
let mut my_position = NetworkCoordinate::<2>::new();
|
||||
```
|
||||
Normally 2 or 3-dimensions is plenty. Higher dimensions may add some accuracy,
|
||||
but not enough to be worth the extra costs.
|
||||
|
||||
Each node occasionally sends its NC to other nodes. Upon receiving a NC from a
|
||||
remote node, update your local node's position with the actual measured ping
|
||||
time to that node.
|
||||
|
||||
```rust
|
||||
my_position.update(&remote_position, Duration::from_millis(measured_rtt));
|
||||
```
|
||||
|
||||
Over time your NC will get more and more accurate as it's updated against more
|
||||
nodes in the network.
|
||||
|
||||
You can estimate your ping time with any other NC you receive, even if it was
|
||||
forwarded to you indirectly.
|
||||
|
||||
```rust
|
||||
let rtt_estimate = my_position.estimate_rtt(&remote_position);
|
||||
```
|
||||
|
||||
That's the entire interface for creating and iteratively updating NCs.
|
||||
|
||||
If you want to save/restore NCs, or send/receive them over a network, you'll
|
||||
want to serialize/deserialize them. `NetworkCoordinate` supports
|
||||
[Serde](https://crates.io/crates/serde) by default. Many formats are supported
|
||||
by Serde, including text formats like [JSON](https://github.com/serde-rs/json)
|
||||
and compact binary formats like [bincode](https://crates.io/crates/bincode) and
|
||||
[MessagePack](https://github.com/3Hren/msgpack-rust).
|
||||
|
||||
See the [module documentation](https://docs.rs/vivaldi-nc) for more detailed
|
||||
usage examples.
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -70,21 +116,27 @@ One design goal of this crate is to minimize dependencies. When dependencies
|
||||
are required, I try to be very selective about them (re: bloat and licensing).
|
||||
This crate depends on:
|
||||
|
||||
- `nanorand`: A fast PRNG based on WyRand. It pulls in `getrandom` as a
|
||||
portable source of entropy. I chose this over the more commonly used, but
|
||||
heavyweight `rand` because it's significantly smaller and does just what I
|
||||
need, and not much more.
|
||||
- `num-traits`: A brilliant crate which makes it easier to operate on numbers
|
||||
in generics; like using `Float` as a constraint on a generic type. Its
|
||||
convenience outweighs its cost.
|
||||
- [`nanorand`](https://crates.io/crates/nanorand): A fast PRNG based on WyRand.
|
||||
It pulls in `getrandom` as a portable source of entropy. I chose this over
|
||||
the more commonly used, but heavyweight `rand` because it's significantly
|
||||
smaller and does just what I need, and not much more.
|
||||
- [`num-traits`](https://crates.io/crates/num-traits): A brilliant crate which
|
||||
makes it easier to operate on numbers in generics; like using `Float` as a
|
||||
constraint on a generic type. Its convenience outweighs its cost.
|
||||
- [`serde`](https://crates.io/crates/serde): I think NCs are usually meant to
|
||||
be shared across a network. That requires serialization/deserialization and
|
||||
serde is *the* choice for that. It might be big, but it's efficient.
|
||||
- [`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
|
||||
`Deserialize`.
|
||||
|
||||
## Alternative implementations
|
||||
## Design Goals & Alternatives
|
||||
|
||||
Several crates implemented Vivaldi NC before this one. So, why another?
|
||||
|
||||
I had several design goals which the existing crates didn't satisfy:
|
||||
|
||||
1. Provide the simplest interface possible. I just want to have some sort of
|
||||
1. Provide the *simplest interface possible*. I just want to have some sort of
|
||||
Coordinate struct and be able to update it, and then use it to estimate
|
||||
round trip times (ie ping times).
|
||||
2. Don't require the consumer to bring their own vector or linear algebra
|
||||
@@ -96,14 +148,16 @@ I had several design goals which the existing crates didn't satisfy:
|
||||
rigamarole. Support `serde` traits by default.
|
||||
4. Well documented. Well tested. This varies by the crates.
|
||||
|
||||
All of that said, those other implementations might work best for you. Here are
|
||||
the ones I know of today:
|
||||
### Other Vivaldi NC Implementations
|
||||
|
||||
- TODO: list crates
|
||||
- TODO: list crates
|
||||
- TODO: list crates
|
||||
All of that said, those other rust implementations might work best for you.
|
||||
Here are the ones I know of today:
|
||||
|
||||
## Other Algorithms
|
||||
- [netloc](https://github.com/dgtony/netloc)
|
||||
- [violin](https://crates.io/crates/violin)
|
||||
- [vivaldi](https://crates.io/crates/vivaldi)
|
||||
|
||||
### Other NC Algorithms
|
||||
|
||||
Vivaldi is about the simplest distributed NC algorithm out there. That
|
||||
simplicity combined with its reasonably good performance is a reason why it's
|
||||
@@ -111,6 +165,47 @@ popular.
|
||||
|
||||
But it's far from the only choice. Here are links to other NC algorithms:
|
||||
|
||||
- TODO: list algorithms
|
||||
- TODO: list algorithms
|
||||
- TODO: list algorithms
|
||||
- [Pharos](https://en.wikipedia.org/wiki/Pharos_Network_Coordinates)
|
||||
- [Phoenix](https://en.wikipedia.org/wiki/Phoenix_Network_Coordinates)
|
||||
|
||||
Search your favorite research paper index for "network coordinates" and you'll
|
||||
find many more.
|
||||
|
||||
## Getting Help or Contributing
|
||||
|
||||
To get help or discuss this crate, [submit a
|
||||
ticket](https://todo.sr.ht/~swaits/vivaldi-nc) or post on
|
||||
[`vivaldi-nc-discuss@`](https://lists.sr.ht/~swaits/vivaldi-nc-discuss).
|
||||
|
||||
Discussion related to development or patch submissions should go to
|
||||
[`vivaldi-nc-devel@`](https://lists.sr.ht/~swaits/vivaldi-nc-devel).
|
||||
|
||||
Patchsets should use [git-send-email](https://git-send-email.io/) or the [sr.ht
|
||||
UI](https://git.sr.ht/~swaits/vivaldi-nc/send-email) (easiest IMO).
|
||||
|
||||
Patch submitters implicitly agree that all contributions they submit fall under
|
||||
the MIT license.
|
||||
|
||||
## License
|
||||
|
||||
> MIT License
|
||||
>
|
||||
> Copyright (c) 2023 Stephen Waits
|
||||
>
|
||||
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
> of this software and associated documentation files (the "Software"), to deal
|
||||
> in the Software without restriction, including without limitation the rights
|
||||
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
> copies of the Software, and to permit persons to whom the Software is
|
||||
> furnished to do so, subject to the following conditions:
|
||||
>
|
||||
> The above copyright notice and this permission notice shall be included in all
|
||||
> copies or substantial portions of the Software.
|
||||
>
|
||||
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
> SOFTWARE.
|
||||
+66
-1
@@ -45,6 +45,7 @@
|
||||
use std::ops::{Add, Mul, Sub};
|
||||
|
||||
use nanorand::{Rng, WyRand};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::vector::Vector;
|
||||
|
||||
@@ -61,7 +62,7 @@ use crate::vector::Vector;
|
||||
/// ## Generic Parameters
|
||||
///
|
||||
/// - `N`: the dimensionality of the vector portion (i.e. non-height) of the Vivaldi height vector
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct HeightVector<const N: usize>(Vector<f32, N>, f32);
|
||||
|
||||
//
|
||||
@@ -160,4 +161,68 @@ mod tests {
|
||||
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>::new();
|
||||
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_eq!(a.0[0], 1.0);
|
||||
assert_eq!(a.0[1], 2.0);
|
||||
assert_eq!(a.1, 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_eq!(c.0[0], 4.0);
|
||||
assert_eq!(c.0[1], 4.0);
|
||||
assert_eq!(c.1, 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_eq!(c.0[0], -2.0);
|
||||
assert_eq!(c.0[1], 0.0);
|
||||
assert_eq!(c.1, 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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serde() {
|
||||
// start with JSON, deserialize it
|
||||
let s = "[{\"inner\":[1.0,2.0,3.0]},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
|
||||
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().unwrap(), s);
|
||||
}
|
||||
}
|
||||
+92
@@ -1,4 +1,96 @@
|
||||
//! Vivaldi Network Coordinates with a 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
|
||||
//! other will be close to each other in this latency space. Nodes with high latency between them
|
||||
//! will be far from each other.
|
||||
//!
|
||||
//! This is an implementation of Vivaldi Network Coordinates, a specific NC
|
||||
//! algorithm, with a simple interface and few dependencies. Vivaldi coordinates
|
||||
//! are typically used in distributed networking applications, like p2p networks.
|
||||
//! They allow each node in the network to estimate its position in a latency space
|
||||
//! in a distributed way, with no central authority. This enables nodes to
|
||||
//! understand estimated latency to any other node in the system.
|
||||
//!
|
||||
//! To learn more, see the [Vivaldi coordinates article on
|
||||
//! Wikipedia](https://en.wikipedia.org/wiki/Vivaldi_coordinates) or [the original
|
||||
//! paper](http://conferences.sigcomm.org/sigcomm/2004/papers/p426-dabek111111.pdf) (Frank Dabek,
|
||||
//! Russ Cox, Frans Kaashoek, Robert Morris (2004)).
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! This crate exports a single struct, [`NetworkCoordinate`]. 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
|
||||
//! dimensionality" below.*
|
||||
//! 2. Every node occasionally sends its [`NetworkCoordinate`] to some or all other nodes through
|
||||
//! the network overlay.
|
||||
//! 3. Whenever a node receives a remote coordinate, it updates its local coordinate with the
|
||||
//! actual measured round trip time (RTT or "ping time") between itself and that node to improve
|
||||
//! its own position and thus reduce its estimation error.
|
||||
//! 4. Any node can estimate its RTT to any other [`NetworkCoordinate`] it's aware of.
|
||||
//!
|
||||
//! Importantly, all updates in this system are performed locally. No central authority is needed
|
||||
//! to build the network latency maps. It's fully distributed.
|
||||
//!
|
||||
//! **Note on dimensionality:** Typically 2D or 3D Vivaldi coordinates (i.e. `NetworkCoordinate<2>` or
|
||||
//! `NetworkCoordinate<3>`) are sufficient. Higher dimensions don't add much accuracy and aren't
|
||||
//! generally worth it. That said, you're welcome to use any dimension you like.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! In the first example, we simulate receving a remote NC, measuring ping, and updating our NC
|
||||
//! accordingly.
|
||||
//!
|
||||
//! ```
|
||||
//! use vivaldi_nc::NetworkCoordinate;
|
||||
//!
|
||||
//! // create our local NC as a 3-dimensional Vivaldi coordinate
|
||||
//! let mut my_nc = NetworkCoordinate::<3>::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();
|
||||
//!
|
||||
//! // mock up a measured RTT between us and that node (normally the nodes would coordinate
|
||||
//! // measuring this together)
|
||||
//! let rtt_actual = std::time::Duration::from_millis(78);
|
||||
//!
|
||||
//! // with the actual RTT in hand, we can update our NC to improve its accuracy
|
||||
//! my_nc.update(&remote_nc, rtt_actual);
|
||||
//!
|
||||
//! // by doing this iteratively (and forever), all NCs in the network will converge to better
|
||||
//! // represent the actual latency space of the network
|
||||
//! ```
|
||||
//!
|
||||
//! In this second example, we simulate receving a NC from another node indirectly; likely
|
||||
//! 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.
|
||||
//!
|
||||
//! ```
|
||||
//! use vivaldi_nc::NetworkCoordinate;
|
||||
//!
|
||||
//! // create our local NC as a 3-dimensional Vivaldi coordinate
|
||||
//! let mut my_nc = NetworkCoordinate::<3>::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();
|
||||
//!
|
||||
//! // estimate the RTT to this remote node without measuring it directly
|
||||
//! let rtt_estimated = my_nc.estimated_rtt(&remote_nc);
|
||||
//!
|
||||
//! // so with just the knowledge of another NC, we can get reasonably good estimates of RTT
|
||||
//! // without actually needing to measure it
|
||||
//! ```
|
||||
//!
|
||||
|
||||
mod height_vector;
|
||||
mod vector;
|
||||
|
||||
// publish our interface
|
||||
pub mod network_coordinate;
|
||||
pub use network_coordinate::NetworkCoordinate;
|
||||
@@ -27,6 +27,8 @@
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::height_vector::HeightVector;
|
||||
|
||||
//
|
||||
@@ -47,7 +49,7 @@ const MIN_ERROR: f32 = f32::EPSILON;
|
||||
// **** Structs ****
|
||||
//
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct NetworkCoordinate<const N: usize> {
|
||||
position: HeightVector<N>,
|
||||
error: f32,
|
||||
@@ -63,7 +65,7 @@ impl<const N: usize> NetworkCoordinate<N> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use vivaldi_nc::network_coordinate::NetworkCoordinate;
|
||||
/// use vivaldi_nc::NetworkCoordinate;
|
||||
///
|
||||
/// // create a new 3-dimensional random NC
|
||||
/// let a: NetworkCoordinate<3> = NetworkCoordinate::new();
|
||||
@@ -91,7 +93,7 @@ impl<const N: usize> NetworkCoordinate<N> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use vivaldi_nc::network_coordinate::NetworkCoordinate;
|
||||
/// use vivaldi_nc::NetworkCoordinate;
|
||||
///
|
||||
/// // create some 2-dimensional NCs for the sake of this example. These will just be random
|
||||
/// // NCs. In a real usecase these would have meaningful values.
|
||||
@@ -123,7 +125,7 @@ impl<const N: usize> NetworkCoordinate<N> {
|
||||
///
|
||||
/// ```
|
||||
/// use std::time::Duration;
|
||||
/// use vivaldi_nc::network_coordinate::NetworkCoordinate;
|
||||
/// use vivaldi_nc::NetworkCoordinate;
|
||||
///
|
||||
/// // We always have our own NC:
|
||||
/// let mut local: NetworkCoordinate<2> = NetworkCoordinate::new();
|
||||
@@ -274,6 +276,22 @@ mod tests {
|
||||
(slc.error.powf(2.0) + nyc.error.powf(2.0) + lax.error.powf(2.0) + mad.error.powf(2.0))
|
||||
.sqrt();
|
||||
println!("error = {error}");
|
||||
assert!(error < 0.07);
|
||||
assert!(error < 0.2);
|
||||
}
|
||||
|
||||
#[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 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_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
|
||||
let t = serde_json::to_string(&a);
|
||||
assert_eq!(t.as_ref().unwrap(), s);
|
||||
}
|
||||
}
|
||||
+60
-17
@@ -4,6 +4,8 @@
|
||||
//! Vivaldi `HeightVector`.
|
||||
|
||||
use num_traits::Float;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::serde_as;
|
||||
|
||||
use std::ops::{Add, Div, Index, Mul, Sub};
|
||||
|
||||
@@ -16,14 +18,24 @@ use std::ops::{Add, Div, Index, Mul, Sub};
|
||||
/// ## Generic parameters:
|
||||
/// - `T`: the type to use, must satisfy the `Float` trait (i.e. `f32` or `f64`)
|
||||
/// - `N`: the number of dimensions (i.e. 2 for 2D vectors, 3 for 3D, etc)
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) struct Vector<T: Float, const N: usize>([T; N]);
|
||||
#[serde_as]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub(crate) struct Vector<T, const N: usize>
|
||||
where
|
||||
T: Float + Serialize + for<'d> Deserialize<'d>,
|
||||
{
|
||||
#[serde_as(as = "[_; N]")]
|
||||
inner: [T; N],
|
||||
}
|
||||
|
||||
//
|
||||
// **** Implementations ****
|
||||
//
|
||||
|
||||
impl<T: Float, const N: usize> Vector<T, N> {
|
||||
impl<T, const N: usize> Vector<T, N>
|
||||
where
|
||||
T: Float + Serialize + for<'d> Deserialize<'d>,
|
||||
{
|
||||
/// Create a new Zero vector.
|
||||
pub(crate) fn new() -> Self {
|
||||
Self::default()
|
||||
@@ -32,8 +44,8 @@ impl<T: Float, const N: usize> Vector<T, N> {
|
||||
/// Compute magnitude of vector.
|
||||
pub(crate) fn len(&self) -> T {
|
||||
(0..N)
|
||||
.map(|i| self.0[i] * self.0[i])
|
||||
.fold(T::zero(), |acc, x| acc + x)
|
||||
.map(|i| self.inner[i] * self.inner[i])
|
||||
.fold(T::zero(), |sum_sq, x| sum_sq + x)
|
||||
.sqrt()
|
||||
}
|
||||
}
|
||||
@@ -42,21 +54,32 @@ impl<T: Float, const N: usize> Vector<T, N> {
|
||||
// **** Trait Implementations ****
|
||||
//
|
||||
|
||||
impl<T: Float, const N: usize> Default for Vector<T, N> {
|
||||
impl<T, const N: usize> Default for Vector<T, N>
|
||||
where
|
||||
T: Float + Serialize + for<'d> Deserialize<'d>,
|
||||
{
|
||||
/// A default vector is all zeroes
|
||||
fn default() -> Self {
|
||||
Self([T::zero(); N])
|
||||
Self {
|
||||
inner: [T::zero(); N],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float, const N: usize> From<[T; N]> for Vector<T, N> {
|
||||
impl<T, const N: usize> From<[T; N]> for Vector<T, N>
|
||||
where
|
||||
T: Float + Serialize + for<'d> Deserialize<'d>,
|
||||
{
|
||||
/// Create a new Vector from an array of type T, N dimensions.
|
||||
fn from(value: [T; N]) -> Self {
|
||||
Self(value)
|
||||
Self { inner: value }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float, const N: usize> Add for Vector<T, N> {
|
||||
impl<T, const N: usize> Add for Vector<T, N>
|
||||
where
|
||||
T: Float + Serialize + for<'d> Deserialize<'d>,
|
||||
{
|
||||
type Output = Self;
|
||||
|
||||
/// Vector+Vector addition.
|
||||
@@ -69,7 +92,10 @@ impl<T: Float, const N: usize> Add for Vector<T, N> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float, const N: usize> Sub for Vector<T, N> {
|
||||
impl<T, const N: usize> Sub for Vector<T, N>
|
||||
where
|
||||
T: Float + Serialize + for<'d> Deserialize<'d>,
|
||||
{
|
||||
type Output = Self;
|
||||
|
||||
/// Vector-Vector subtraction.
|
||||
@@ -82,38 +108,47 @@ impl<T: Float, const N: usize> Sub for Vector<T, N> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float, const N: usize> Mul<T> for Vector<T, N> {
|
||||
impl<T, const N: usize> Mul<T> for Vector<T, N>
|
||||
where
|
||||
T: Float + Serialize + for<'d> Deserialize<'d>,
|
||||
{
|
||||
type Output = Self;
|
||||
|
||||
/// Vector*scalar multiplication.
|
||||
fn mul(self, rhs: T) -> Self::Output {
|
||||
let mut ret = Self::Output::new();
|
||||
for i in 0..N {
|
||||
ret.0[i] = self.0[i] * rhs
|
||||
ret.inner[i] = self.inner[i] * rhs
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float, const N: usize> Div<T> for Vector<T, N> {
|
||||
impl<T, const N: usize> Div<T> for Vector<T, N>
|
||||
where
|
||||
T: Float + Serialize + for<'d> Deserialize<'d>,
|
||||
{
|
||||
type Output = Self;
|
||||
|
||||
/// Vector/scalar division.
|
||||
fn div(self, rhs: T) -> Self::Output {
|
||||
let mut ret = Self::Output::new();
|
||||
for i in 0..N {
|
||||
ret.0[i] = self.0[i] / rhs
|
||||
ret.inner[i] = self.inner[i] / rhs
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float, const N: usize> Index<usize> for Vector<T, N> {
|
||||
impl<T, const N: usize> Index<usize> for Vector<T, N>
|
||||
where
|
||||
T: Float + Serialize + for<'d> Deserialize<'d>,
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
/// Nicer indexing [] for read only references.
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.0[index]
|
||||
&self.inner[index]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,4 +218,12 @@ mod tests {
|
||||
let b = a * 10.0;
|
||||
assert_approx_eq!(b.len(), 37.416_57, 0.0001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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!(s.is_ok());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user