21 Commits
Author SHA1 Message Date
swaits 16e3691e2d bump crate to 0.6.4
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-26 10:05:59 -07:00
swaits 6ab8f0dca4 chore: upgrade dependencies
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-26 10:05:33 -07:00
swaits 1a6b3983b3 chore: add MSRV (1.60)
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-26 09:54:23 -07:00
swaits db38079dfc bump crate to 0.6.3
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-13 13:11:33 -07:00
swaits f88c1d4375 test: make accepted licenses explicit
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-13 12:50:36 -07:00
swaits 90a90f49df test: fix cargo audit advisory db location
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-13 12:45:43 -07:00
swaits b3dca0d8a9 test: add audit, deny, outdated, and udeps to CI
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-13 12:36:24 -07:00
swaits 8812bacfdd test: add (and fix) more deny lints
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-13 12:35:26 -07:00
swaits a64732fa97 style: move clippy allows to specific lines of code
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-13 11:40:12 -07:00
swaits 1b50b0c042 style: more Cargo.toml formatting 2023-02-13 11:13:33 -07:00
swaits f414bdc10d style: format Cargo.toml 2023-02-13 11:10:44 -07:00
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
10 changed files with 483 additions and 127 deletions

No files matched your search

+32 -7
View File
@@ -1,7 +1,12 @@
image: archlinux image: archlinux
packages: packages:
- rustup - rustup
- cargo-audit
- cargo-deny
- cargo-msrv
- cargo-outdated
- cargo-tarpaulin - cargo-tarpaulin
- cargo-udeps
sources: sources:
- https://git.sr.ht/~swaits/vivaldi-nc - https://git.sr.ht/~swaits/vivaldi-nc
artifacts: artifacts:
@@ -14,11 +19,31 @@ 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
- outdated: |
rustup default stable
cd vivaldi-nc/
cargo outdated
- deny: |
rustup default stable
cd vivaldi-nc/
cargo deny check
- udeps: |
rustup default nightly
cd vivaldi-nc/
cargo +nightly udeps --all-features
- audit: |
rustup default stable
cd vivaldi-nc/
cargo audit -d /tmp/advisory-db
- msrv: |
rustup default stable
cd vivaldi-nc/
cargo msrv verify
- coverage: | - coverage: |
rustup default stable rustup default stable
cd vivaldi-nc/ cd vivaldi-nc/
@@ -26,18 +51,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/
+20 -11
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "vivaldi-nc" name = "vivaldi-nc"
version = "0.6.0" version = "0.6.4"
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."
@@ -11,9 +11,8 @@ documentation = "https://docs.rs/vivaldi-nc/"
readme = "README.md" readme = "README.md"
homepage = "https://sr.ht/~swaits/vivaldi-nc/" homepage = "https://sr.ht/~swaits/vivaldi-nc/"
repository = "https://git.sr.ht/~swaits/vivaldi-nc" repository = "https://git.sr.ht/~swaits/vivaldi-nc"
exclude = [ exclude = ["examples/*"]
"examples/*", rust-version = "1.60"
]
[features] [features]
default = [] default = []
@@ -25,18 +24,28 @@ debug = true
[profile.release] [profile.release]
debug = true debug = true
[dev-dependencies]
assert_approx_eq = "1.1.0"
criterion = "0.4.0"
proptest = "1.0.0"
serde_json = "1.0.91"
[[bench]] [[bench]]
name = "million-updates" name = "million-updates"
harness = false harness = false
[dev-dependencies]
assert_approx_eq = "1.1.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.1.0"
serde_json = "1.0.93"
[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"] }
serde_with = "2.2.0" serde_with = "2.2.0"
+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};
+270
View File
@@ -0,0 +1,270 @@
# This template contains all of the possible sections and their default values
# Note that all fields that take a lint level have these possible values:
# * deny - An error will be produced and the check will fail
# * warn - A warning will be produced, but the check will not fail
# * allow - No warning or error will be produced, though in some cases a note
# will be
# The values provided in this template are the default values that will be used
# when any section or field is not specified in your own configuration
# Root options
# If 1 or more target triples (and optionally, target_features) are specified,
# only the specified targets will be checked when running `cargo deny check`.
# This means, if a particular package is only ever used as a target specific
# dependency, such as, for example, the `nix` crate only being used via the
# `target_family = "unix"` configuration, that only having windows targets in
# this list would mean the nix crate, as well as any of its exclusive
# dependencies not shared by any other crates, would be ignored, as the target
# list here is effectively saying which targets you are building for.
targets = [
# The triple can be any string, but only the target triples built in to
# rustc (as of 1.40) can be checked against actual config expressions
#{ triple = "x86_64-unknown-linux-musl" },
# You can also specify which target_features you promise are enabled for a
# particular target. target_features are currently not validated against
# the actual valid features supported by the target architecture.
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
]
# When creating the dependency graph used as the source of truth when checks are
# executed, this field can be used to prune crates from the graph, removing them
# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
# is pruned from the graph, all of its dependencies will also be pruned unless
# they are connected to another crate in the graph that hasn't been pruned,
# so it should be used with care. The identifiers are [Package ID Specifications]
# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
#exclude = []
# If true, metadata will be collected with `--all-features`. Note that this can't
# be toggled off if true, if you want to conditionally enable `--all-features` it
# is recommended to pass `--all-features` on the cmd line instead
all-features = false
# If true, metadata will be collected with `--no-default-features`. The same
# caveat with `all-features` applies
no-default-features = false
# If set, these feature will be enabled when collecting metadata. If `--features`
# is specified on the cmd line they will take precedence over this option.
#features = []
# When outputting inclusion graphs in diagnostics that include features, this
# option can be used to specify the depth at which feature edges will be added.
# This option is included since the graphs can be quite large and the addition
# of features from the crate(s) to all of the graph roots can be far too verbose.
# This option can be overridden via `--feature-depth` on the cmd line
feature-depth = 1
# This section is considered when running `cargo deny check advisories`
# More documentation for the advisories section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
[advisories]
# The path where the advisory database is cloned/fetched into
db-path = "~/.cargo/advisory-db"
# The url(s) of the advisory databases to use
db-urls = ["https://github.com/rustsec/advisory-db"]
# The lint level for security vulnerabilities
vulnerability = "deny"
# The lint level for unmaintained crates
unmaintained = "warn"
# The lint level for crates that have been yanked from their source registry
yanked = "warn"
# The lint level for crates with security notices. Note that as of
# 2019-12-17 there are no security notice advisories in
# https://github.com/rustsec/advisory-db
notice = "warn"
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
#"RUSTSEC-0000-0000",
]
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
# lower than the range specified will be ignored. Note that ignored advisories
# will still output a note when they are encountered.
# * None - CVSS Score 0.0
# * Low - CVSS Score 0.1 - 3.9
# * Medium - CVSS Score 4.0 - 6.9
# * High - CVSS Score 7.0 - 8.9
# * Critical - CVSS Score 9.0 - 10.0
#severity-threshold =
# If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library.
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
# See Git Authentication for more information about setting up git authentication.
#git-fetch-with-cli = true
# This section is considered when running `cargo deny check licenses`
# More documentation for the licenses section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
[licenses]
# The lint level for crates which do not have a detectable license
unlicensed = "deny"
# List of explicitly allowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
allow = [
"Apache-2.0",
"BSD-3-Clause",
"MIT",
"Unicode-DFS-2016",
]
# List of explicitly disallowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
deny = [
#"Nokia",
]
# Lint level for licenses considered copyleft
copyleft = "deny"
# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
# * both - The license will be approved if it is both OSI-approved *AND* FSF
# * either - The license will be approved if it is either OSI-approved *OR* FSF
# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF
# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved
# * neither - This predicate is ignored and the default lint level is used
allow-osi-fsf-free = "neither"
# Lint level used when no other predicates are matched
# 1. License isn't in the allow or deny lists
# 2. License isn't copyleft
# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
default = "deny"
# The confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the
# canonical license text of a valid SPDX license file.
# [possible values: any between 0.0 and 1.0].
confidence-threshold = 0.8
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
# aren't accepted for every possible crate as with the normal allow list
exceptions = [
# Each entry is the crate and version constraint, and its specific allow
# list
#{ allow = ["Zlib"], name = "adler32", version = "*" },
]
# Some crates don't have (easily) machine readable licensing information,
# adding a clarification entry for it allows you to manually specify the
# licensing information
#[[licenses.clarify]]
# The name of the crate the clarification applies to
#name = "ring"
# The optional version constraint for the crate
#version = "*"
# The SPDX expression for the license requirements of the crate
#expression = "MIT AND ISC AND OpenSSL"
# One or more files in the crate's source used as the "source of truth" for
# the license expression. If the contents match, the clarification will be used
# when running the license check, otherwise the clarification will be ignored
# and the crate will be checked normally, which may produce warnings or errors
# depending on the rest of your configuration
#license-files = [
# Each entry is a crate relative path, and the (opaque) hash of its contents
#{ path = "LICENSE", hash = 0xbd0eed23 }
#]
[licenses.private]
# If true, ignores workspace crates that aren't published, or are only
# published to private registries.
# To see how to mark a crate as unpublished (to the official registry),
# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
ignore = false
# One or more private registries that you might publish crates to, if a crate
# is only published to private registries, and ignore is true, the crate will
# not have its license(s) checked
registries = [
#"https://sekretz.com/registry
]
# This section is considered when running `cargo deny check bans`.
# More documentation about the 'bans' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
[bans]
# Lint level for when multiple versions of the same crate are detected
multiple-versions = "warn"
# Lint level for when a crate version requirement is `*`
wildcards = "allow"
# The graph highlighting used when creating dotgraphs for crates
# with multiple versions
# * lowest-version - The path to the lowest versioned duplicate is highlighted
# * simplest-path - The path to the version with the fewest edges is highlighted
# * all - Both lowest-version and simplest-path are used
highlight = "all"
# The default lint level for `default` features for crates that are members of
# the workspace that is being checked. This can be overriden by allowing/denying
# `default` on a crate-by-crate basis if desired.
workspace-default-features = "allow"
# The default lint level for `default` features for external crates that are not
# members of the workspace. This can be overriden by allowing/denying `default`
# on a crate-by-crate basis if desired.
external-default-features = "allow"
# List of crates that are allowed. Use with care!
allow = [
#{ name = "ansi_term", version = "=0.11.0" },
]
# List of crates to deny
deny = [
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#{ name = "ansi_term", version = "=0.11.0" },
#
# Wrapper crates can optionally be specified to allow the crate when it
# is a direct dependency of the otherwise banned crate
#{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
]
# List of features to allow/deny
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#[[bans.features]]
#name = "reqwest"
# Features to not allow
#deny = ["json"]
# Features to allow
#allow = [
# "rustls",
# "__rustls",
# "__tls",
# "hyper-rustls",
# "rustls",
# "rustls-pemfile",
# "rustls-tls-webpki-roots",
# "tokio-rustls",
# "webpki-roots",
#]
# If true, the allowed features must exactly match the enabled feature set. If
# this is set there is no point setting `deny`
#exact = true
# Certain crates/versions that will be skipped when doing duplicate detection.
skip = [
#{ name = "ansi_term", version = "=0.11.0" },
]
# Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive
# dependencies starting at the specified crate, up to a certain depth, which is
# by default infinite.
skip-tree = [
#{ name = "ansi_term", version = "=0.11.0", depth = 20 },
]
# This section is considered when running `cargo deny check sources`.
# More documentation about the 'sources' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
[sources]
# Lint level for what to happen when a crate from a crate registry that is not
# in the allow list is encountered
unknown-registry = "warn"
# Lint level for what to happen when a crate from a git repository that is not
# in the allow list is encountered
unknown-git = "warn"
# List of URLs for allowed crate registries. Defaults to the crates.io index
# if not specified. If it is specified but empty, no registries are allowed.
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
# List of URLs for allowed Git repositories
allow-git = []
[sources.allow-org]
# 1 or more github.com organizations to allow git sources for
github = [""]
# 1 or more gitlab.com organizations to allow git sources for
gitlab = [""]
# 1 or more bitbucket.org organizations to allow git sources for
bitbucket = [""]
+35 -31
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,14 +94,9 @@ 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 = thread_rng();
let mut vec = [0.0; N]; let mut vec = [0.0; N];
for i in vec.iter_mut().take(N) { for i in vec.iter_mut().take(N) {
*i = rng.gen::<FloatType>() - 0.5; *i = rng.gen::<FloatType>() - 0.5;
@@ -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]
+28 -10
View File
@@ -93,17 +93,35 @@
//! ``` //! ```
//! //!
#![deny(clippy::all)] #![deny(
#![deny(clippy::correctness)] clippy::all,
#![deny(clippy::style)] clippy::correctness,
#![deny(clippy::suspicious)] clippy::style,
#![deny(clippy::complexity)] clippy::suspicious,
#![deny(clippy::perf)] clippy::complexity,
#![deny(clippy::cargo)] clippy::perf,
#![deny(clippy::pedantic)] clippy::cargo,
#![deny(clippy::nursery)] clippy::pedantic,
clippy::nursery,
clippy::unwrap_used
)]
#![deny(
anonymous_parameters,
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
nonstandard_style,
rust_2018_idioms,
single_use_lifetimes,
trivial_casts,
trivial_numeric_casts,
// unreachable_pub, // disagrees with clippy::nurclippy::nursery as of 13-02-2023
unused_extern_crates,
unused_qualifications,
variant_size_differences
)]
#![allow(clippy::type_repetition_in_bounds)] #![allow(clippy::type_repetition_in_bounds)]
#![allow(clippy::module_name_repetitions)] #![allow(single_use_lifetimes)]
mod height_vector; mod height_vector;
mod vector; mod vector;
+29 -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};
@@ -63,12 +63,14 @@ pub struct NetworkCoordinate<const N: usize> {
/// ///
/// This type alias is just for convenience. It's functionally equivalent to /// This type alias is just for convenience. It's functionally equivalent to
/// `NetworkCoordinate<2>`. For more information, see [`NetworkCoordinate`]. /// `NetworkCoordinate<2>`. For more information, see [`NetworkCoordinate`].
#[allow(clippy::module_name_repetitions)]
pub type NetworkCoordinate2D = NetworkCoordinate<2>; pub type NetworkCoordinate2D = NetworkCoordinate<2>;
/// A 3D [`NetworkCoordinate`]. Includes a 3D Euclidean position and a height. /// A 3D [`NetworkCoordinate`]. Includes a 3D Euclidean position and a height.
/// ///
/// This type alias is just for convenience. It's functionally equivalent to /// This type alias is just for convenience. It's functionally equivalent to
/// `NetworkCoordinate<3>`. For more information, see [`NetworkCoordinate`]. /// `NetworkCoordinate<3>`. For more information, see [`NetworkCoordinate`].
#[allow(clippy::module_name_repetitions)]
pub type NetworkCoordinate3D = NetworkCoordinate<3>; pub type NetworkCoordinate3D = NetworkCoordinate<3>;
// //
@@ -124,11 +126,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 +150,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 +194,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 +356,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 +363,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 +373,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 +393,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));
}
}
}