violin/lib.rs
1//! # `violin`
2//!
3//! ![Rust Version][rustc-image]
4//! [![crates.io][crate-image]][crate-link]
5//! [![Documentation][docs-image]][docs-link]
6//! [![Dependency Status][deps-image]][deps-link]
7//!
8//! A Rust `no_std` no `alloc` implementation of the [Vivaldi algorithm][1](PDF)
9//! for a network coordinate system.
10//!
11//! A network coordinate system allows nodes to accurately estimate network
12//! latencies by merely exchanging coordinates.
13//!
14//!
15//! <!-- vim-markdown-toc GFM -->
16//!
17//! * [Violin - The Pitch](#violin---the-pitch)
18//! * [Violin - The Anit-Pitch](#violin---the-anit-pitch)
19//! * [Compile from Source](#compile-from-source)
20//! * [Usage](#usage)
21//! * [Benchmarks](#benchmarks)
22//! * [Notes on `no_std` Performance](#notes-on-no_std-performance)
23//! * [License](#license)
24//! * [Contribution](#contribution)
25//! * [Related Papers and Research](#related-papers-and-research)
26//!
27//! <!-- vim-markdown-toc -->
28//!
29//! ## Violin - The Pitch
30//!
31//! Violin is an implementation of Vivaldi network coordinates that works in
32//! `no_std` and no `alloc` environments. Each coordinate is small consisting of
33//! a dimensional vector made up of an array of `f64`s. The arrays use const
34//! generics, so they can be as small as a single f64 or large as one needs.
35//! Although above a certain dimension there are diminishing returns.
36//!
37//! Nodes can measure real latencies between an origin node, or each-other to
38//! adjust their coordinates in space.
39//!
40//! The real power comes from being able to calculate distance between a remote
41//! coordinate without ever having done a real latency check. For example node
42//! `A` measures against node `Origin`, node `B` does the same. Then `A` can be
43//! given the coordinates to `B` and accurately estimate the latency without
44//! ever having measured `B` directly.
45//!
46//! ## Violin - The Anit-Pitch
47//!
48//! Vivaldi isn't a magic bullet and still requires measuring real latencies to
49//! adjust the coordinates. In a naive implementation, conducting a latency
50//! check prior to a coordinate calculation is not much better than just using
51//! the latency check directly as the answer. However, this is not how it's
52//! supposed to be used.
53//!
54//! Transferring a Violin coordinate in practice can be comparable data to a
55//! small set of ICMP messages. For example an 8-Dimension coordinate (plus
56//! three additional `f64`s of metadata) is 88 bytes. However, unlike ICMP
57//! messages, the Violin coordinates are a single transmission and only need to
58//! be re-transmitted on significant change. Work could even be done to only
59//! transmit deltas as well.
60//!
61//! ## Compile from Source
62//!
63//! Ensure you have a [Rust toolchain installed][rustup].
64//!
65//! ```notrust
66//! $ git clone https://github.com/kbknapp/violin
67//! $ cd violin
68//! $ RUSTFLAGS='-Ctarget-cpu=native' cargo build --release
69//! ```
70//!
71//! **NOTE:** The `RUSTFLAGS` can be omitted. However, if on a recent CPU that
72//! supports SIMD instructions, and the code will be run on the same CPU it's
73//! compiled for, including this flag can improve performance.
74//!
75//! ## Usage
76//!
77//! See the `examples/` directory in this repository for complete details,
78//! although at quick glance creating three coordinates (`origin`, `a` and `b`)
79//! and updating `a` and `b`'s coordinate from experienced real latency would
80//! look like this:
81//!
82//! ```notrust
83//! use std::time::Duration;
84//! use violin::{heapless::VecD, Coord, Node};
85//!
86//! // Create two nodes and an "origin" coordinate, all using a 4-Dimensional
87//! // coordinate. `VecD` is a dimensional vector.
88//! let origin = Coord::<VecD<4>>::rand();
89//! let mut a = Node::<VecD<4>>::rand();
90//! let mut b = Node::<VecD<4>>::rand();
91//!
92//! // **conduct some latency measurement from a to origin**
93//! // let's assume we observed a value of `0.2` seconds...
94//! //
95//! // **conduct some latency measurement from b to origin**
96//! // let's assume we observed a value of `0.03` seconds...
97//!
98//! a.update(Duration::from_secs_f64(0.2), &origin);
99//! b.update(Duration::from_secs_f64(0.03), &origin);
100//!
101//! // Estimate from a to b even though we never measured them directly
102//! println!(
103//! "a's estimate to b: {:.2}ms",
104//! a.distance_to(&b.coordinate()).as_millis()
105//! );
106//! ```
107//!
108//! ## Benchmarks
109//!
110//! A set of benchmarks are included using 8D, 4D, and 2D coordinates both using
111//! `heap::VecD` (requires the `alloc` feature) and `heapless::VecD`.
112//!
113//! The benchmarks measure both the higher level `Node` as well as a lower level
114//! `Coord` abstractions.
115//!
116//! To measure we create 10,000 coordinates and the coordinates are
117//! update for each coordinate 100 times, totaling 1,000,000 updates.
118//!
119//! On my 8 core AMD Ryzen 7 5850U laptop with 16GB RAM the benchmarks look as
120//! follows:
121//!
122//! | Abstraction | Memory | Dimensions | Time |
123//! | :-: | :-: | :-: | :-: |
124//! | `Node` | heap | 8 | 66.537 ms |
125//! | `Coord` | heap | 8 | 55.402 ms |
126//! | `Node` | heapless | 8 | 24.997 ms |
127//! | `Coord` | heapless | 8 | 16.552 ms |
128//! | `Node` | heap | 4 | 49.501 ms |
129//! | `Coord` | heap | 4 | 39.163 ms |
130//! | `Node` | heapless | 4 | 16.795 ms |
131//! | `Coord` | heapless | 4 | 11.780 ms |
132//! | `Node` | heap | 2 | 54.363 ms |
133//! | `Coord` | heap | 2 | 46.001 ms |
134//! | `Node` | heapless | 2 | 13.181 ms |
135//! | `Coord` | heapless | 2 | 10.916 ms |
136//!
137//! To run the benchmarks yourself use `RUSTFLAGS='-Ctarget-cpu=native' cargo
138//! bench`.
139//!
140//! ### Notes on `no_std` Performance
141//!
142//! The `no_std` version is _much_ slower because it cannot use platform
143//! intrinsics for square roots, floating point rounding, etc. Instead these
144//! functions had to be hand written.
145//!
146//! Additionally, the `no_std` square root functions round up to 8 decimals of
147//! precision.
148//!
149//! One should realistically only use the `no_std` version when there is a good
150//! reason to do so, such as an embedded device that absolutely does not support
151//! `std`.
152//!
153//! A single Vivaldi calculation only requires one square root calculation per
154//! distance estimate. So pragmatically, it should be rare where such a device
155//! is _also_ needing to calculate thousands of square root operations per
156//! second.
157//!
158//! ## License
159//!
160//! This crate is licensed under either of
161//!
162//! * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
163//! * [MIT license](http://opensource.org/licenses/MIT)
164//!
165//! at your option.
166//!
167//! ### Contribution
168//!
169//! Unless you explicitly Node otherwise, any contribution intentionally
170//! submitted for inclusion in the work by you, as defined in the Apache-2.0
171//! license, shall be dual licensed as above, without any additional terms or
172//! conditions.
173//!
174//! ## Related Papers and Research
175//!
176//! - [Vivaldi - A Decentralized Network Coordinate System][1](PDF)
177//! - [Network Coordinates in the Wild][2](PDF)
178//! - [Towards Network Triangle Inequality Violation Aware Distributed
179//! Systems][3](PDF)
180//! - [On Suitability of Euclidean Embedding for Host-based Network Coordinate
181//! Systems][4](PDF)
182//! - [Practical, Distributed Network Coordinates][5](PDF)
183//! - [Armon Dadgar on Vivaldi: Decentralized Network Coordinate
184//! System][6](Video)
185//!
186//! [//]: # (badges)
187//!
188//! [rustc-image]: https://img.shields.io/badge/rustc-1.59+-blue.svg
189//! [crate-image]: https://img.shields.io/crates/v/violin.svg
190//! [crate-link]: https://crates.io/crates/violin
191//! [docs-image]: https://docs.rs/violin/badge.svg
192//! [docs-link]: https://docs.rs/violin
193//! [deps-image]: https://deps.rs/repo/github/kbknapp/violin/status.svg
194//! [deps-link]: https://deps.rs/repo/github/kbknapp/violin
195//!
196//! [//]: # (links)
197//!
198//! [rustup]: https://rustup.rs
199//! [1]: https://pdos.csail.mit.edu/papers/vivaldi:sigcomm/paper.pdf
200//! [2]: https://www.usenix.org/legacy/event/nsdi07/tech/full_papers/ledlie/ledlie.pdf
201//! [3]: https://www.cs.rice.edu/~eugeneng/papers/IMC07.pdf
202//! [4]: https://www-users.cse.umn.edu/~zhang089/Papers/Lee-Suitability-tonfinal.pdf
203//! [5]: http://www.news.cs.nyu.edu/~jinyang/pub/hotnets03.pdf
204//! [6]: https://youtu.be/AszPoJjWK9Q?t=1690
205#![deny(
206 missing_docs,
207 missing_debug_implementations,
208 missing_copy_implementations,
209 trivial_casts,
210 unused_allocation,
211 trivial_numeric_casts
212)]
213#![forbid(unsafe_code)]
214#![cfg_attr(not(feature = "std"), no_std)]
215#![cfg_attr(docsrs, feature(doc_cfg))]
216
217#[cfg(any(feature = "std", test))]
218#[macro_use]
219extern crate std;
220
221// When we're building for a no-std target, we pull in `core`, but alias
222// it as `std` so the `use` statements are the same between `std` and `core`.
223#[cfg(all(not(feature = "std"), not(test)))]
224#[macro_use]
225extern crate core as std;
226
227#[cfg(feature = "alloc")]
228extern crate alloc;
229
230use crate::std::{
231 default::Default,
232 ops::{Add, AddAssign, Div, Mul},
233};
234
235#[macro_use]
236mod macros;
237mod coord;
238pub mod error;
239#[cfg(feature = "alloc")]
240#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
241pub mod heap;
242pub mod heapless;
243mod node;
244
245pub use coord::Coord;
246#[cfg(feature = "alloc")]
247#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
248pub use heap::VecD;
249pub use node::{Config, Node};
250
251/// Determines at what threshold two coordinates overlap
252const OVERLAP_THRESHOLD: f64 = 1.0e-6;
253const DEFAULT_HEIGHT_MIN: f64 = 0.0;
254
255/// The abstraction over coordinate vectors
256pub trait Vector:
257 Default
258 + Add<Self, Output = Self>
259 + Mul<f64, Output = Self>
260 + AddAssign<Self>
261 + Div<f64, Output = Self>
262 + AsRef<[f64]>
263 + AsMut<[f64]>
264where
265 Self: Sized,
266{
267 /// The length of the vector
268 const LEN: usize;
269
270 /// Returns a unit vector (`รข`) from `other` pointing at `self` along
271 /// with the magnitude of the difference beand tween both vectors
272 fn unit_vector_from(&self, other: &Self) -> (f64, Self) {
273 let diff = self.difference(other);
274 let mag = diff.magnitude();
275 // If the coordinates overlap return a unit vector in the first dimension
276 if mag < OVERLAP_THRESHOLD {
277 let mut ret = Self::default();
278 ret.as_mut()[0] = 1.0;
279 return (0.0, ret);
280 }
281 (mag, diff * (1. / mag))
282 }
283
284 /// Returns distance between `self` and `other`
285 #[cfg_attr(feature = "std", doc = "```rust")]
286 #[cfg_attr(not(feature = "std"), doc = "```no_run")]
287 /// use violin::{heapless::VecD, Vector};
288 ///
289 /// let a = VecD::from([1., 0., 5.]);
290 /// let b = VecD::from([0., 2., 4.]);
291 ///
292 /// assert_eq!(a.distance(&b), 2.449489742783178);
293 /// ```
294 fn distance(&self, other: &Self) -> f64 { self.difference(other).magnitude() }
295
296 /// Returns the difference between two vectors
297 ///
298 /// ```rust
299 /// use violin::{heapless::VecD, Vector};
300 ///
301 /// let a = VecD::from([1.0, -3.0, 3.0]);
302 /// let b = VecD::from([-4.0, 5.0, 6.0]);
303 ///
304 /// assert_eq!(a.difference(&b), VecD::from([5.0, -8.0, -3.0]));
305 /// assert_eq!(a.difference(&VecD::default()), a);
306 fn difference(&self, other: &Self) -> Self;
307
308 /// Returns the magnitude of the vector `v` (`|v|`) represented by `self`
309 ///
310 /// ```rust
311 /// use violin::{heapless::VecD, Vector};
312 ///
313 /// let a = VecD::from([1.0, -2.0, 3.0]);
314 /// let b = VecD::from([-2., 4., -4.]);
315 /// assert_eq!(a.magnitude(), 3.7416573867739413);
316 /// assert_eq!(b.magnitude(), 6.0f64);
317 /// ```
318 #[cfg(feature = "std")]
319 fn magnitude(&self) -> f64 { self.magnitude2().sqrt() }
320
321 /// Returns the magnitude of the vector `v` (`|v|`) represented by `self`
322 #[cfg_attr(feature = "std", doc = "```rust")]
323 #[cfg_attr(not(feature = "std"), doc = "```no_run")]
324 /// use violin::{heapless::VecD, Vector};
325 ///
326 /// let a = VecD::from([1.0, -2.0, 3.0]);
327 /// let b = VecD::from([-2., 4., -4.]);
328 /// assert_eq!(a.magnitude(), 3.7416573867739413);
329 /// assert_eq!(b.magnitude(), 6.0f64);
330 /// ```
331 #[cfg(not(feature = "std"))]
332 fn magnitude(&self) -> f64 { _sqrt(self.magnitude2()) }
333
334 /// Returns the magnitude of the vector `v` (`|v|`) represented by `self`
335 /// **without** performing the expensive square root operation
336 /// ```rust
337 /// use violin::{heapless::VecD, Vector};
338 ///
339 /// let a = VecD::from([1.0, -2.0, 3.0]);
340 /// let b = VecD::from([-2., 4., -4.]);
341 /// let c = VecD::from([0., 0., 0.]);
342 /// assert_eq!(a.magnitude2(), 14.0);
343 /// assert_eq!(b.magnitude2(), 36.0);
344 /// assert_eq!(c.magnitude2(), 0.0);
345 /// ```
346 fn magnitude2(&self) -> f64;
347}
348
349#[cfg(not(feature = "std"))]
350const PRECISION_INC: f64 = 1.0e-8;
351#[cfg(not(feature = "std"))]
352const PRECISION_POW: f64 = 1.0e+8;
353
354#[cfg(not(feature = "std"))]
355fn _sqrt(n: f64) -> f64 {
356 if n == 0.0 {
357 return 0.0;
358 }
359 let mut ans;
360 let mut last;
361 let mut mid = n / 2.0;
362 let mut top = n;
363 let r_n = _round(n);
364
365 loop {
366 last = mid;
367 let sq = _round(mid * mid);
368 if sq == r_n {
369 ans = mid;
370 break;
371 }
372 if sq > r_n {
373 mid = last / 2.0;
374 top = last;
375 } else {
376 mid += (top - mid) / 2.0;
377 }
378 if mid == last {
379 mid += 1.0;
380 }
381 }
382
383 ans = f64::max(ans - 1.0, 0.0);
384 mid = 0.5;
385 loop {
386 last = mid;
387 let sq = (mid + ans) * (mid + ans);
388 let r_sq = _round(sq);
389 if r_sq == r_n {
390 ans += mid;
391 break;
392 }
393 if sq > n {
394 mid = last / 2.0;
395 top = last;
396 } else {
397 mid += (top - mid) / 2.0;
398 }
399
400 if mid == last {
401 mid += PRECISION_INC;
402 }
403 }
404
405 _round(ans)
406}
407
408#[inline(always)]
409#[cfg(not(feature = "std"))]
410fn _round(n: f64) -> f64 { _ceil((n * PRECISION_POW) - 0.49999999) / PRECISION_POW }
411
412#[inline(always)]
413#[cfg(not(feature = "std"))]
414fn _ceil(n: f64) -> f64 {
415 let n_int = n as u64;
416 if n > n_int as f64 {
417 (n_int + 1) as f64
418 } else {
419 n
420 }
421}
422
423#[cfg(test)]
424mod tests {
425 #[cfg(not(feature = "std"))]
426 use super::*;
427
428 #[cfg_attr(not(feature = "std"), test)]
429 #[cfg(not(feature = "std"))]
430 fn test_sqrt() {
431 assert_eq!(_sqrt(36.0), 6.0);
432 assert_eq!(_sqrt(27.2934), 5.22430857);
433 assert_eq!(_sqrt(8.408207478410603), 2.89969093);
434 assert_eq!(_sqrt(158.57), 12.59245806);
435 assert_eq!(_sqrt(0.0), 0.0);
436 assert_eq!(_sqrt(0.0), 0.0);
437 assert_eq!(_sqrt(0.009983505350056349), 0.0999175);
438 }
439}