From 92ad12bfe42635b6dc28b008ae9b55cc91de7710 Mon Sep 17 00:00:00 2001 From: Autumnal Date: Mon, 29 Nov 2021 15:20:26 +0100 Subject: [PATCH] Add fuzzy test and Readme; solves #18 --- kd_tree/README.md | 8 + kd_tree_sort/Cargo.toml | 3 + kd_tree_sort/README.md | 19 ++ kd_tree_sort/benches/search.rs | 40 ++- kd_tree_sort/fuzz/.gitignore | 3 + kd_tree_sort/fuzz/Cargo.lock | 303 ++++++++++++++++++ kd_tree_sort/fuzz/Cargo.toml | 28 ++ .../fuzz/fuzz_targets/kd_tree_search.rs | 55 ++++ 8 files changed, 442 insertions(+), 17 deletions(-) create mode 100644 kd_tree/README.md create mode 100644 kd_tree_sort/README.md create mode 100644 kd_tree_sort/fuzz/.gitignore create mode 100644 kd_tree_sort/fuzz/Cargo.lock create mode 100644 kd_tree_sort/fuzz/Cargo.toml create mode 100644 kd_tree_sort/fuzz/fuzz_targets/kd_tree_search.rs diff --git a/kd_tree/README.md b/kd_tree/README.md new file mode 100644 index 0000000..40d6dc6 --- /dev/null +++ b/kd_tree/README.md @@ -0,0 +1,8 @@ +# KD-Tree implementation + +Implements only functions required for searching an already initialized KD-Tree. +Because of this, this crate is no_std compatible + +Code for building the KD-Tree can be found in `kd_tree_sort`. +`kd_tree_sort` is not no_std compatible which is why these two crates were seperated. +A fuzzy tester as well as a benchmark verifying this implementation can be found in `kd_tree_sort`. \ No newline at end of file diff --git a/kd_tree_sort/Cargo.toml b/kd_tree_sort/Cargo.toml index cfb5832..9dbb41c 100644 --- a/kd_tree_sort/Cargo.toml +++ b/kd_tree_sort/Cargo.toml @@ -15,6 +15,9 @@ rand_pcg = "*" itertools = "0.10" criterion = "0.3" +[lib] +bench = false + [[bench]] name = "search" harness = false diff --git a/kd_tree_sort/README.md b/kd_tree_sort/README.md new file mode 100644 index 0000000..8c28be9 --- /dev/null +++ b/kd_tree_sort/README.md @@ -0,0 +1,19 @@ +# KD-Tree sort implemention + +Implements a non-no_std compatible sort function for usage with the `kd_tree` crate. + +## Benchmark +For benchmarking purposed, criterion.rs was used, comparing our kd-tree against a basic linear_search. + +Run: `cargo bench` in this directory. + +## Fuzzy Test +The implemented fuzzy test uses `cargo fuzz` +Install it before running the fuzzy test (a nightly toolchain may be required). + +It compares the result of the linear search algorithm against the result of the kd-tree search. +The fuzzy test input is filtering out `Inf` and `NaN` values since our kd-tree is +supposed to find the nearest-neighbor on valid input data only. + +For starting the fuzzy test run: `cargo fuzz run kd_tree_search --sanitizer=leak` in this directory. +*Note: the fuzzy test will run until it finds an error* \ No newline at end of file diff --git a/kd_tree_sort/benches/search.rs b/kd_tree_sort/benches/search.rs index 42da518..68f8883 100644 --- a/kd_tree_sort/benches/search.rs +++ b/kd_tree_sort/benches/search.rs @@ -1,8 +1,8 @@ -use std::time::Duration; -use criterion::{criterion_group, criterion_main, Criterion, BenchmarkId, BatchSize}; +use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; use kd_tree::{euclid, Node, Tree}; -use rand::Rng; use kd_tree_sort::sort; +use rand::Rng; +use std::time::Duration; type Prng = rand_pcg::Mcg128Xsl64; @@ -15,17 +15,15 @@ fn linear_search(nodes: &[Node], point: &[f64; 3]) -> f64 { } fn generate_values(len: usize, rng: &mut Prng) -> Vec<([f64; 3], i32)> { - (0..len) - .map(|_| (generate_point(rng), 0)) - .collect() + (0..len).map(|_| (generate_point(rng), 0)).collect() } -fn generate_sorted(len: usize, rng: &mut Prng) -> Vec>{ +fn generate_sorted(len: usize, rng: &mut Prng) -> Vec> { let sorted = sort(generate_values(len, rng)); sorted.iter().map(|(p, v)| Node::new(*p, *v)).collect() } -fn generate_point(rng: &mut Prng) -> [f64; 3]{ +fn generate_point(rng: &mut Prng) -> [f64; 3] { [rng.gen(), rng.gen(), rng.gen()] } @@ -41,19 +39,27 @@ pub fn search(c: &mut Criterion) { group.warm_up_time(Duration::from_millis(50)); group.measurement_time(Duration::from_secs(1)); - for len in (START_LEN..END_LEN).step_by(1000){ + for len in (START_LEN..END_LEN).step_by(1000) { let nodes = generate_sorted(len, &mut rng); let tree = Tree:: { - nodes: nodes.as_slice() + nodes: nodes.as_slice(), }; - group.bench_with_input(BenchmarkId::new("KD Tree", len), - &tree, |b, t| - b.iter_batched(|| generate_point(&mut rng),|p| t.search(&p), BatchSize::SmallInput)); - group.bench_with_input(BenchmarkId::new("Linear", len), - &nodes, |b, n| - b.iter_batched(|| generate_point(&mut rng),|p| linear_search(n, &p), BatchSize::SmallInput)); + group.bench_with_input(BenchmarkId::new("KD Tree", len), &tree, |b, t| { + b.iter_batched( + || generate_point(&mut rng), + |p| t.search(&p), + BatchSize::SmallInput, + ) + }); + group.bench_with_input(BenchmarkId::new("Linear", len), &nodes, |b, n| { + b.iter_batched( + || generate_point(&mut rng), + |p| linear_search(n, &p), + BatchSize::SmallInput, + ) + }); } } criterion_group!(benches, search); -criterion_main!(benches); \ No newline at end of file +criterion_main!(benches); diff --git a/kd_tree_sort/fuzz/.gitignore b/kd_tree_sort/fuzz/.gitignore new file mode 100644 index 0000000..a092511 --- /dev/null +++ b/kd_tree_sort/fuzz/.gitignore @@ -0,0 +1,3 @@ +target +corpus +artifacts diff --git a/kd_tree_sort/fuzz/Cargo.lock b/kd_tree_sort/fuzz/Cargo.lock new file mode 100644 index 0000000..1ed5d69 --- /dev/null +++ b/kd_tree_sort/fuzz/Cargo.lock @@ -0,0 +1,303 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arbitrary" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510c76ecefdceada737ea728f4f9a84bd2e1ef29f1ba555e560940fe279954de" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if", + "lazy_static", +] + +[[package]] +name = "derive_arbitrary" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b24629208e87a2d8b396ff43b15c4afb0a69cea3fbbaa9ed9b92b7c02f0aed73" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "kd_tree" +version = "0.1.0" +dependencies = [ + "num", +] + +[[package]] +name = "kd_tree_sort" +version = "0.1.0" +dependencies = [ + "kd_tree", + "rayon", +] + +[[package]] +name = "kd_tree_sort-fuzz" +version = "0.0.0" +dependencies = [ + "arbitrary", + "kd_tree", + "kd_tree_sort", + "libfuzzer-sys", + "rayon", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a9a84a6e8b55dfefb04235e55edb2b9a2a18488fcae777a6bdaa6f06f1deb3" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "proc-macro2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "syn" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" diff --git a/kd_tree_sort/fuzz/Cargo.toml b/kd_tree_sort/fuzz/Cargo.toml new file mode 100644 index 0000000..4f84664 --- /dev/null +++ b/kd_tree_sort/fuzz/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "kd_tree_sort-fuzz" +version = "0.0.0" +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +arbitrary = { version = "1", features = ["derive"] } +rayon = "1.5" + +[dependencies.kd_tree_sort] +path = ".." +[dependencies.kd_tree] +path = "../../kd_tree" + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "kd_tree_search" +path = "fuzz_targets/kd_tree_search.rs" +test = false +doc = false diff --git a/kd_tree_sort/fuzz/fuzz_targets/kd_tree_search.rs b/kd_tree_sort/fuzz/fuzz_targets/kd_tree_search.rs new file mode 100644 index 0000000..9fc928b --- /dev/null +++ b/kd_tree_sort/fuzz/fuzz_targets/kd_tree_search.rs @@ -0,0 +1,55 @@ +#![no_main] +use kd_tree::{euclid, Node, Tree}; +use kd_tree_sort::sort; +use libfuzzer_sys::fuzz_target; +use rayon::prelude::*; + +#[derive(Clone, Debug, arbitrary::Arbitrary)] +struct ArbitraryPoint { + pos: [f64; 3], + p: i32, +} + +unsafe impl Sync for ArbitraryPoint{} +unsafe impl Send for ArbitraryPoint{} + +fn linear_search(nodes: &[Node], point: &[f64; 3]) -> f64 { + nodes + .iter() + .map(|a| euclid(point, a.val())) + .min_by(|a, b| a.partial_cmp(b).unwrap()) + .unwrap() +} + +const MAX_LEVEL: usize = 16; + +fuzz_target!(|data: Vec| { + let mut data = data; + let mut data: Vec<_> = data + .drain(..) + .filter(|p| p.pos[0].is_finite() && p.pos[1].is_finite() && p.pos[2].is_finite()) + .take((2usize.pow(MAX_LEVEL as u32) - 1) * 2) + .par_bridge() + .map(|p| (p.pos, p.p)) + .collect(); + + if data.len() < 2 { + // Dont fuzzy test less than two elements + return; + } + + let search_data: Vec<_> = data.split_off(data.len() / 2); + + let sorted = sort(data); + let sorted: Vec<_> = sorted.iter().map(|(p, v)| Node::new(*p, *v)).collect(); + let tree = Tree:: { + nodes: sorted.as_slice(), + }; + + search_data.par_iter().for_each(|(p, _)| { + assert_eq!( + euclid(tree.search(&p).val(), &p), + linear_search(&sorted, &p) + ) + }); +});