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}