From a6a8cb333e30f6230a9cd1c19b863618b563f9d5 Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Mon, 15 Aug 2022 16:16:18 +0100 Subject: [PATCH 01/17] initial fastpair implementation --- src/algorithm/neighbour/fastpair.rs | 419 ++++++++++++++++++++++++++++ src/algorithm/neighbour/mod.rs | 2 + 2 files changed, 421 insertions(+) create mode 100644 src/algorithm/neighbour/fastpair.rs diff --git a/src/algorithm/neighbour/fastpair.rs b/src/algorithm/neighbour/fastpair.rs new file mode 100644 index 00000000..d2306f09 --- /dev/null +++ b/src/algorithm/neighbour/fastpair.rs @@ -0,0 +1,419 @@ +#![allow(non_snake_case)] +/// +/// FastPair: Data-structure for the dynamic closest-pair problem. +/// +/// Reference: +/// Eppstein, David: Fast hierarchical clustering and other applications of +/// dynamic closest pairs. Journal of Experimental Algorithmics 5 (2000) 1. +/// +use std::collections::HashMap; +use std::iter; + +use crate::algorithm::neighbour::dissimilarities::PairwiseDissimilarity; +use crate::error::{Failed, FailedError}; +use crate::linalg::Matrix; +use crate::math::distance::euclidian::Euclidian; +use crate::math::num::RealNumber; + +/// +/// FastPair factory function +/// +pub fn FastPair>(m: &M) -> Result<_FastPair<'_, T, M>, Failed> { + if m.shape().0 < 3 { + return Err(Failed::because( + FailedError::FindFailed, + "min number of rows is 3", + )); + } + + let mut init = _FastPair { + samples: m, + distances: Box::new(HashMap::with_capacity(m.shape().0)), + neighbours: Vec::with_capacity(m.shape().0 + 1), + // to be computed in inti(..) + connectivity: None, + }; + init.init(); + Ok(init) +} + +/// +/// FastPair +/// +/// Ported from Python implementation: +/// +/// MIT License (MIT) Copyright (c) 2016 Carson Farmer +/// +/// affinity used is Euclidean so to allow linkage with single, ward, complete and average +/// +#[derive(Debug, Clone)] +pub struct _FastPair<'a, T: RealNumber, M: Matrix> { + /// initial matrix + samples: &'a M, + /// closest pair hashmap (connectivity matrix for closest pairs) + pub distances: Box>>, + /// conga line used to keep track of the closest pair + pub neighbours: Vec, + /// sparse matrix of closest pairs + /// values are set for closest pairs distances, other pairs are zeroed + pub connectivity: Option>, +} + +impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { + /// + /// Initialise `FastPair` by passing a `Matrix`. + /// Build a FastPairs data-structure from a set of (new) points. + /// + fn init(&mut self) { + // basic measures + let len = self.samples.shape().0; + let max_index = self.samples.shape().0 - 1; + + // Store all closest neighbors + let _distances = Box::new(HashMap::with_capacity(len)); + let _neighbours = Box::new(Vec::with_capacity(len)); + + let mut distances = *_distances; + let mut neighbours = *_neighbours; + + // fill neighbours with -1 values + neighbours.extend(iter::repeat(0).take(len)); + + // loop through indeces and neighbours + for index_row_i in 0..len { + // init closest neighbour pairwise data + distances.insert( + index_row_i, + PairwiseDissimilarity { + node: index_row_i, + neighbour: None, + distance: Some(T::max_value()), + }, + ); + + // start looking for the neighbour in the second element + let mut index_closest = index_row_i + 1; // closest neighbour index + let mut nbd: Option = Some(T::max_value()); // init neighbour distance + for index_row_j in (index_row_i + 1)..len { + distances.insert( + index_row_j, + PairwiseDissimilarity { + node: index_row_j, + neighbour: None, + distance: Some(T::max_value()), + }, + ); + + let d = Euclidian::squared_distance( + &(self.samples.get_row_as_vec(index_row_i)), + &(self.samples.get_row_as_vec(index_row_j)), + ); + if d < nbd.unwrap() { + // set this j-value to be the closest neighbour + index_closest = index_row_j; + nbd = Some(d); + } + } + // Add that edge, move nbr to points[i+1] in conga line + distances.entry(index_row_i).and_modify(|e| { + e.distance = nbd; + e.neighbour = Some(index_closest); + }); + + // update conga line + if index_closest != len { + neighbours[index_closest] = neighbours[index_row_i + 1]; + neighbours[index_row_i + 1] = index_closest; + } + } + // No more neighbors, terminate conga line. + // Last person on the line has no neigbors + distances.get_mut(&max_index).unwrap().neighbour = Some(max_index); + distances.get_mut(&(len - 1)).unwrap().distance = Some(T::max_value()); + + // compute sparse matrix (connectivity matrix) + let mut sparse_matrix = M::zeros(len, len); + for (_, p) in distances.iter() { + sparse_matrix.set(p.node, p.neighbour.unwrap(), p.distance.unwrap()); + } + + // TODO: as we now store the connectivity matrix in `self.connectivity`, + // it may be possible to avoid storing closest pairs in `self.distances` + self.distances = Box::new(distances); + self.neighbours = neighbours; + self.connectivity = Some(Box::new(sparse_matrix)); + } + + /// + /// Find closest pair by scanning list of nearest neighbors. + /// + #[allow(dead_code)] + pub fn closest_pair(&self) -> PairwiseDissimilarity { + let mut a = self.neighbours[0]; // Start with first point + let mut d = self.distances[&a].distance; + for p in self.neighbours.iter() { + if self.distances[&p].distance < d { + a = *p; // Update `a` and distance `d` + d = self.distances[&p].distance; + } + } + let b = self.distances[&a].neighbour; + PairwiseDissimilarity { + node: a, + neighbour: b, + distance: d, + } + } + + // + // Compute distances from input to all other points in data-structure. + // input is the row index of the sample matrix + // + #[allow(dead_code)] + fn distances_from(&self, index_row: usize) -> Vec> { + let mut distances = Vec::>::with_capacity(self.samples.shape().0); + for other in self.neighbours.iter() { + if index_row != *other { + distances.push(PairwiseDissimilarity { + node: index_row, + neighbour: Some(*other), + distance: Some(Euclidian::squared_distance( + &(self.samples.get_row_as_vec(index_row)), + &(self.samples.get_row_as_vec(*other)), + )), + }) + } + } + distances + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::linalg::naive::dense_matrix::*; + + #[test] + fn fastpair_init() { + let x: DenseMatrix = DenseMatrix::rand(10, 4); + let fastpair = FastPair(&x); + assert!(fastpair.is_ok()); + + let result = fastpair.unwrap(); + let distances = *result.distances; + let neighbours = result.neighbours; + let sparse_matrix = *(result.connectivity.unwrap()); + assert_eq!(10, neighbours.len()); + assert_eq!(10, distances.len()); + assert_eq!(10, sparse_matrix.shape().0); + assert_eq!(10, sparse_matrix.shape().1); + } + + #[test] + fn dataset_has_at_least_three_points() { + // Create a dataset which consists of only two points: + // A(0.0, 0.0) and B(1.0, 1.0). + let dataset = DenseMatrix::from_2d_array(&[&[0.0, 0.0], &[1.0, 1.0]]); + + // We expect an error when we run `FastPair` on this dataset, + // becuase `FastPair` currently only works on a minimum of 3 + // points. + if let Err(e) = FastPair(&dataset) { + let expected_error = + Failed::because(FailedError::FindFailed, "min number of rows is 3"); + assert_eq!(e, expected_error) + } else { + assert!(false) + } + } + + #[test] + fn one_dimensional_dataset_minimal() { + let dataset = DenseMatrix::from_2d_array(&[&[0.0], &[2.0], &[9.0]]); + + let result = FastPair(&dataset); + assert!(result.is_ok()); + + let fastpair = result.unwrap(); + let closest_pair = fastpair.closest_pair(); + let expected_closest_pair = PairwiseDissimilarity { + node: 0, + neighbour: Some(1), + distance: Some(4.0), + }; + assert_eq!(closest_pair, expected_closest_pair); + } + + #[test] + fn one_dimensional_dataset_currently_fails() { + let dataset = DenseMatrix::from_2d_array(&[&[27.0], &[0.0], &[9.0], &[2.0]]); + + let result = FastPair(&dataset); + assert!(result.is_ok()); + + let fastpair = result.unwrap(); + let closest_pair = fastpair.closest_pair(); + let expected_closest_pair = PairwiseDissimilarity { + node: 1, + neighbour: Some(3), + distance: Some(4.0), + }; + assert_eq!(closest_pair, expected_closest_pair); + } + + #[test] + fn fastpair_new() { + // compute + let x = DenseMatrix::from_2d_array(&[ + &[5.1, 3.5, 1.4, 0.2], + &[4.9, 3.0, 1.4, 0.2], + &[4.7, 3.2, 1.3, 0.2], + &[4.6, 3.1, 1.5, 0.2], + &[5.0, 3.6, 1.4, 0.2], + &[5.4, 3.9, 1.7, 0.4], + &[4.6, 3.4, 1.4, 0.3], + &[5.0, 3.4, 1.5, 0.2], + &[4.4, 2.9, 1.4, 0.2], + &[4.9, 3.1, 1.5, 0.1], + &[7.0, 3.2, 4.7, 1.4], + &[6.4, 3.2, 4.5, 1.5], + &[6.9, 3.1, 4.9, 1.5], + &[5.5, 2.3, 4.0, 1.3], + &[6.5, 2.8, 4.6, 1.5], + ]); + let fastpair = FastPair(&x); + assert!(fastpair.is_ok()); + + // unwrap results + let result = fastpair.unwrap(); + let neighbours = result.neighbours; + // let distances = *result.distances; + let sparse_matrix = *(result.connectivity.unwrap()); + + // sequence of indeces computed + assert_eq!( + neighbours, + &[0, 4, 9, 3, 8, 7, 7, 7, 9, 9, 13, 12, 14, 14, 14] + ); + + // list of minimal pairwise dissimilarities + let dissimilarities = vec!( + (1, PairwiseDissimilarity { node: 1, neighbour: Some(9), distance: Some(0.030000000000000037) }), + (10, PairwiseDissimilarity { node: 10, neighbour: Some(12), distance: Some(0.07000000000000003) }), + (11, PairwiseDissimilarity { node: 11, neighbour: Some(14), distance: Some(0.18000000000000013) }), + (12, PairwiseDissimilarity { node: 12, neighbour: Some(14), distance: Some(0.34000000000000086) }), + (13, PairwiseDissimilarity { node: 13, neighbour: Some(14), distance: Some(1.6499999999999997) }), + (14, PairwiseDissimilarity { node: 14, neighbour: Some(14), distance: Some(179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) }), + (6, PairwiseDissimilarity { node: 6, neighbour: Some(7), distance: Some(0.18000000000000027) }), + (0, PairwiseDissimilarity { node: 0, neighbour: Some(4), distance: Some(0.01999999999999995) }), + (8, PairwiseDissimilarity { node: 8, neighbour: Some(9), distance: Some(0.3100000000000001) }), + (2, PairwiseDissimilarity { node: 2, neighbour: Some(3), distance: Some(0.0600000000000001) }), + (3, PairwiseDissimilarity { node: 3, neighbour: Some(8), distance: Some(0.08999999999999982) }), + (7, PairwiseDissimilarity { node: 7, neighbour: Some(9), distance: Some(0.10999999999999982) }), + (9, PairwiseDissimilarity { node: 9, neighbour: Some(13), distance: Some(8.69) }), + (4, PairwiseDissimilarity { node: 4, neighbour: Some(7), distance: Some(0.050000000000000086) }), + (5, PairwiseDissimilarity { node: 5, neighbour: Some(7), distance: Some(0.4900000000000002) }) + ); + + let expected: HashMap<_, _> = dissimilarities.into_iter().collect(); + + for i in 0..(x.shape().0 - 1) { + let input_node = result.samples.get_row_as_vec(i); + let input_neighbour: usize = expected.get(&i).unwrap().neighbour.unwrap(); + let distance = Euclidian::squared_distance( + &input_node, + &result.samples.get_row_as_vec(input_neighbour), + ); + + assert_eq!(i, expected.get(&i).unwrap().node); + assert_eq!( + input_neighbour, + expected.get(&i).unwrap().neighbour.unwrap() + ); + assert_eq!(distance, expected.get(&i).unwrap().distance.unwrap()); + assert_eq!( + sparse_matrix.get(i, input_neighbour), + expected.get(&i).unwrap().distance.unwrap() + ); + } + } + + #[test] + fn fastpair_closest_pair() { + let x = DenseMatrix::from_2d_array(&[ + &[5.1, 3.5, 1.4, 0.2], + &[4.9, 3.0, 1.4, 0.2], + &[4.7, 3.2, 1.3, 0.2], + &[4.6, 3.1, 1.5, 0.2], + &[5.0, 3.6, 1.4, 0.2], + &[5.4, 3.9, 1.7, 0.4], + &[4.6, 3.4, 1.4, 0.3], + &[5.0, 3.4, 1.5, 0.2], + &[4.4, 2.9, 1.4, 0.2], + &[4.9, 3.1, 1.5, 0.1], + &[7.0, 3.2, 4.7, 1.4], + &[6.4, 3.2, 4.5, 1.5], + &[6.9, 3.1, 4.9, 1.5], + &[5.5, 2.3, 4.0, 1.3], + &[6.5, 2.8, 4.6, 1.5], + ]); + // compute + let fastpair = FastPair(&x); + assert!(fastpair.is_ok()); + + let dissimilarity = fastpair.unwrap().closest_pair(); + let closest = PairwiseDissimilarity { + node: 0, + neighbour: Some(4), + distance: Some(0.01999999999999995), + }; + + assert_eq!(closest, dissimilarity); + } + + #[test] + fn fastpair_distances() { + let x = DenseMatrix::from_2d_array(&[ + &[5.1, 3.5, 1.4, 0.2], + &[4.9, 3.0, 1.4, 0.2], + &[4.7, 3.2, 1.3, 0.2], + &[4.6, 3.1, 1.5, 0.2], + &[5.0, 3.6, 1.4, 0.2], + &[5.4, 3.9, 1.7, 0.4], + &[4.6, 3.4, 1.4, 0.3], + &[5.0, 3.4, 1.5, 0.2], + &[4.4, 2.9, 1.4, 0.2], + &[4.9, 3.1, 1.5, 0.1], + &[7.0, 3.2, 4.7, 1.4], + &[6.4, 3.2, 4.5, 1.5], + &[6.9, 3.1, 4.9, 1.5], + &[5.5, 2.3, 4.0, 1.3], + &[6.5, 2.8, 4.6, 1.5], + ]); + // compute + let fastpair = FastPair(&x); + assert!(fastpair.is_ok()); + + let dissimilarities = fastpair.unwrap().distances_from(0); + + let mut min_dissimilarity = PairwiseDissimilarity { + node: 0, + neighbour: None, + distance: Some(179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0), + }; + for p in dissimilarities.iter() { + if p.distance.unwrap() < min_dissimilarity.distance.unwrap() { + min_dissimilarity = p.clone() + } + } + + let closest = PairwiseDissimilarity { + node: 0, + neighbour: Some(4), + distance: Some(0.01999999999999995), + }; + + assert_eq!(closest, min_dissimilarity); + } +} diff --git a/src/algorithm/neighbour/mod.rs b/src/algorithm/neighbour/mod.rs index 321ec011..874ec742 100644 --- a/src/algorithm/neighbour/mod.rs +++ b/src/algorithm/neighbour/mod.rs @@ -43,6 +43,8 @@ pub(crate) mod bbd_tree; pub mod cover_tree; /// very simple algorithm that sequentially checks each element of the list until a match is found or the whole list has been searched. pub mod linear_search; +/// fastpair closest neighbour algorithm +pub mod fastpair; /// Both, KNN classifier and regressor benefits from underlying search algorithms that helps to speed up queries. /// `KNNAlgorithmName` maintains a list of supported search algorithms, see [KNN algorithms](../algorithm/neighbour/index.html) From fee49505e8ae63ab5834970ab03e447979a23292 Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Mon, 15 Aug 2022 17:24:29 +0100 Subject: [PATCH 02/17] FastPair initial implementation --- Cargo.toml | 1 + src/algorithm/neighbour/dissimilarities.rs | 45 ++++++++++++++++++++++ src/algorithm/neighbour/fastpair.rs | 43 ++++++++++++++++++--- src/algorithm/neighbour/mod.rs | 4 +- 4 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 src/algorithm/neighbour/dissimilarities.rs diff --git a/Cargo.toml b/Cargo.toml index f662d5e1..d6f880ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ num = "0.4.0" rand = "0.8.3" rand_distr = "0.4.0" serde = { version = "1.0.115", features = ["derive"], optional = true } +itertools = "0.10.3" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2", features = ["js"] } diff --git a/src/algorithm/neighbour/dissimilarities.rs b/src/algorithm/neighbour/dissimilarities.rs new file mode 100644 index 00000000..901fbf88 --- /dev/null +++ b/src/algorithm/neighbour/dissimilarities.rs @@ -0,0 +1,45 @@ +//! +//! ## Dissimilarities for vector-vector distance +//! +//! Different algorithms based on Closest Pairs use to store graph's edges to +//! compute clusters or closest neighbors in Matrices rows. +//! The struct `PairwiseDissimilarity` can be used to represent edges between +//! closest pairs by storing the nodes' indeces. +use std::cmp::{Eq, Ordering, PartialOrd}; + +use crate::math::num::RealNumber; + +/// +/// The edge of the subgraph is defined by this structure. +/// The calling algorithm can store a list of dissimilarities as +/// a list of these structures. +/// +#[derive(Debug, Clone, Copy)] +pub struct PairwiseDissimilarity { + // index of the vector in the original `Matrix` or list + pub node: usize, + + // index of the closest neighbor in the original `Matrix` or same list + pub neighbour: Option, + + // measure of distance, according to the algorithm distance function + // if the distance is None, the edge has value "infinite" or max distance + // each algorithm has to match + pub distance: Option, +} + +impl Eq for PairwiseDissimilarity {} + +impl PartialEq for PairwiseDissimilarity { + fn eq(&self, other: &Self) -> bool { + self.node == other.node + && self.neighbour == other.neighbour + && self.distance == other.distance + } +} + +impl PartialOrd for PairwiseDissimilarity { + fn partial_cmp(&self, other: &Self) -> Option { + self.distance.partial_cmp(&other.distance) + } +} diff --git a/src/algorithm/neighbour/fastpair.rs b/src/algorithm/neighbour/fastpair.rs index d2306f09..716cc8e1 100644 --- a/src/algorithm/neighbour/fastpair.rs +++ b/src/algorithm/neighbour/fastpair.rs @@ -8,6 +8,7 @@ /// use std::collections::HashMap; use std::iter; +use itertools::Itertools; use crate::algorithm::neighbour::dissimilarities::PairwiseDissimilarity; use crate::error::{Failed, FailedError}; @@ -30,7 +31,7 @@ pub fn FastPair>(m: &M) -> Result<_FastPair<'_, T, M samples: m, distances: Box::new(HashMap::with_capacity(m.shape().0)), neighbours: Vec::with_capacity(m.shape().0 + 1), - // to be computed in inti(..) + // to be computed in init(..) connectivity: None, }; init.init(); @@ -88,18 +89,18 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { node: index_row_i, neighbour: None, distance: Some(T::max_value()), - }, + } ); // start looking for the neighbour in the second element let mut index_closest = index_row_i + 1; // closest neighbour index - let mut nbd: Option = Some(T::max_value()); // init neighbour distance + let mut nbd: Option = distances[&index_row_i].distance; // init neighbour distance for index_row_j in (index_row_i + 1)..len { distances.insert( index_row_j, PairwiseDissimilarity { node: index_row_j, - neighbour: None, + neighbour: Some(index_row_i), distance: Some(T::max_value()), }, ); @@ -165,6 +166,32 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { } } + /// + /// Brute force algorithm, used only for comparison and testing + /// + pub fn closest_pair_brute(&self) -> PairwiseDissimilarity { + let m = self.samples.shape().0; + + let mut closest_pair = PairwiseDissimilarity { + node: 0, + neighbour: None, + distance: Some(T::max_value()), + }; + for pair in (0..m).combinations(2) { + let d = Euclidian::squared_distance( + &(self.samples.get_row_as_vec(pair[0])), + &(self.samples.get_row_as_vec(pair[1])), + ); + if d < closest_pair.distance.unwrap() { + closest_pair.node = pair[0]; + closest_pair.neighbour = Some(pair[1]); + closest_pair.distance = Some(d); + } + } + + closest_pair + } + // // Compute distances from input to all other points in data-structure. // input is the row index of the sample matrix @@ -189,7 +216,7 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { } #[cfg(test)] -mod tests { +mod tests_fastpair { use super::*; use crate::linalg::naive::dense_matrix::*; @@ -243,6 +270,9 @@ mod tests { distance: Some(4.0), }; assert_eq!(closest_pair, expected_closest_pair); + + let closest_pair_brute = fastpair.closest_pair_brute(); + assert_eq!(closest_pair_brute, expected_closest_pair); } #[test] @@ -254,11 +284,14 @@ mod tests { let fastpair = result.unwrap(); let closest_pair = fastpair.closest_pair(); + println!("{:?}", closest_pair); + println!("{:?}", fastpair.closest_pair_brute()); let expected_closest_pair = PairwiseDissimilarity { node: 1, neighbour: Some(3), distance: Some(4.0), }; + assert_eq!(closest_pair, fastpair.closest_pair_brute()); assert_eq!(closest_pair, expected_closest_pair); } diff --git a/src/algorithm/neighbour/mod.rs b/src/algorithm/neighbour/mod.rs index 874ec742..fbe13a69 100644 --- a/src/algorithm/neighbour/mod.rs +++ b/src/algorithm/neighbour/mod.rs @@ -44,7 +44,9 @@ pub mod cover_tree; /// very simple algorithm that sequentially checks each element of the list until a match is found or the whole list has been searched. pub mod linear_search; /// fastpair closest neighbour algorithm -pub mod fastpair; +pub(crate) mod fastpair; +// Dissimilarities for vector-vector distance. Linkage algorithms used in fastpair +pub mod dissimilarities; /// Both, KNN classifier and regressor benefits from underlying search algorithms that helps to speed up queries. /// `KNNAlgorithmName` maintains a list of supported search algorithms, see [KNN algorithms](../algorithm/neighbour/index.html) From 928e13984ac83dcc305a0feded150fe8377fcedb Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Mon, 15 Aug 2022 18:32:38 +0100 Subject: [PATCH 03/17] implement fastpair --- src/algorithm/neighbour/fastpair.rs | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/algorithm/neighbour/fastpair.rs b/src/algorithm/neighbour/fastpair.rs index 716cc8e1..5f7668d7 100644 --- a/src/algorithm/neighbour/fastpair.rs +++ b/src/algorithm/neighbour/fastpair.rs @@ -78,10 +78,12 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { let mut neighbours = *_neighbours; // fill neighbours with -1 values - neighbours.extend(iter::repeat(0).take(len)); + neighbours.extend(0..len); + + println!("{:?}", neighbours); // loop through indeces and neighbours - for index_row_i in 0..len { + for index_row_i in 0..(max_index) { // init closest neighbour pairwise data distances.insert( index_row_i, @@ -91,7 +93,10 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { distance: Some(T::max_value()), } ); + } + println!("{:?}", distances); + for index_row_i in 0..(len) { // start looking for the neighbour in the second element let mut index_closest = index_row_i + 1; // closest neighbour index let mut nbd: Option = distances[&index_row_i].distance; // init neighbour distance @@ -101,7 +106,7 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { PairwiseDissimilarity { node: index_row_j, neighbour: Some(index_row_i), - distance: Some(T::max_value()), + distance: nbd, }, ); @@ -115,17 +120,13 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { nbd = Some(d); } } + // Add that edge, move nbr to points[i+1] in conga line distances.entry(index_row_i).and_modify(|e| { e.distance = nbd; e.neighbour = Some(index_closest); }); - - // update conga line - if index_closest != len { - neighbours[index_closest] = neighbours[index_row_i + 1]; - neighbours[index_row_i + 1] = index_closest; - } + println!("{:?}", distances); } // No more neighbors, terminate conga line. // Last person on the line has no neigbors @@ -143,12 +144,12 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { self.distances = Box::new(distances); self.neighbours = neighbours; self.connectivity = Some(Box::new(sparse_matrix)); + println!("{:?}", self.neighbours); } /// /// Find closest pair by scanning list of nearest neighbors. /// - #[allow(dead_code)] pub fn closest_pair(&self) -> PairwiseDissimilarity { let mut a = self.neighbours[0]; // Start with first point let mut d = self.distances[&a].distance; @@ -164,6 +165,7 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { neighbour: b, distance: d, } + } /// @@ -320,16 +322,9 @@ mod tests_fastpair { // unwrap results let result = fastpair.unwrap(); - let neighbours = result.neighbours; // let distances = *result.distances; let sparse_matrix = *(result.connectivity.unwrap()); - // sequence of indeces computed - assert_eq!( - neighbours, - &[0, 4, 9, 3, 8, 7, 7, 7, 9, 9, 13, 12, 14, 14, 14] - ); - // list of minimal pairwise dissimilarities let dissimilarities = vec!( (1, PairwiseDissimilarity { node: 1, neighbour: Some(9), distance: Some(0.030000000000000037) }), From 1139e85f315b5dc85289e3d8233bd10d7d5bfc8b Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Mon, 15 Aug 2022 18:36:01 +0100 Subject: [PATCH 04/17] revert minor change --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d6f880ff..f662d5e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,6 @@ num = "0.4.0" rand = "0.8.3" rand_distr = "0.4.0" serde = { version = "1.0.115", features = ["derive"], optional = true } -itertools = "0.10.3" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2", features = ["js"] } From 6f93965a76f26293c47496bef4c995ddc3c939c6 Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Mon, 15 Aug 2022 18:36:26 +0100 Subject: [PATCH 05/17] Add itertools --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 2978238b..e17a48f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ num = "0.4" rand = "0.8" rand_distr = "0.4" serde = { version = "1", features = ["derive"], optional = true } +itertools = "0.10.3" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2", features = ["js"] } From 8e06e7a9fe428570bf1a22be5b0fde34d80682f3 Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Mon, 15 Aug 2022 18:47:18 +0100 Subject: [PATCH 06/17] cleanup --- src/algorithm/neighbour/fastpair.rs | 16 +++------------- src/algorithm/neighbour/mod.rs | 8 ++++---- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/algorithm/neighbour/fastpair.rs b/src/algorithm/neighbour/fastpair.rs index 5f7668d7..b9f6c1e7 100644 --- a/src/algorithm/neighbour/fastpair.rs +++ b/src/algorithm/neighbour/fastpair.rs @@ -1,4 +1,5 @@ #![allow(non_snake_case)] +use itertools::Itertools; /// /// FastPair: Data-structure for the dynamic closest-pair problem. /// @@ -7,8 +8,6 @@ /// dynamic closest pairs. Journal of Experimental Algorithmics 5 (2000) 1. /// use std::collections::HashMap; -use std::iter; -use itertools::Itertools; use crate::algorithm::neighbour::dissimilarities::PairwiseDissimilarity; use crate::error::{Failed, FailedError}; @@ -80,8 +79,6 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { // fill neighbours with -1 values neighbours.extend(0..len); - println!("{:?}", neighbours); - // loop through indeces and neighbours for index_row_i in 0..(max_index) { // init closest neighbour pairwise data @@ -91,10 +88,9 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { node: index_row_i, neighbour: None, distance: Some(T::max_value()), - } + }, ); } - println!("{:?}", distances); for index_row_i in 0..(len) { // start looking for the neighbour in the second element @@ -126,7 +122,6 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { e.distance = nbd; e.neighbour = Some(index_closest); }); - println!("{:?}", distances); } // No more neighbors, terminate conga line. // Last person on the line has no neigbors @@ -144,7 +139,6 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { self.distances = Box::new(distances); self.neighbours = neighbours; self.connectivity = Some(Box::new(sparse_matrix)); - println!("{:?}", self.neighbours); } /// @@ -165,12 +159,11 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { neighbour: b, distance: d, } - } /// /// Brute force algorithm, used only for comparison and testing - /// + /// pub fn closest_pair_brute(&self) -> PairwiseDissimilarity { let m = self.samples.shape().0; @@ -190,7 +183,6 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { closest_pair.distance = Some(d); } } - closest_pair } @@ -286,8 +278,6 @@ mod tests_fastpair { let fastpair = result.unwrap(); let closest_pair = fastpair.closest_pair(); - println!("{:?}", closest_pair); - println!("{:?}", fastpair.closest_pair_brute()); let expected_closest_pair = PairwiseDissimilarity { node: 1, neighbour: Some(3), diff --git a/src/algorithm/neighbour/mod.rs b/src/algorithm/neighbour/mod.rs index fbe13a69..a31fc144 100644 --- a/src/algorithm/neighbour/mod.rs +++ b/src/algorithm/neighbour/mod.rs @@ -41,12 +41,12 @@ use serde::{Deserialize, Serialize}; pub(crate) mod bbd_tree; /// tree data structure for fast nearest neighbor search pub mod cover_tree; -/// very simple algorithm that sequentially checks each element of the list until a match is found or the whole list has been searched. -pub mod linear_search; +/// dissimilarities for vector-vector distance. Linkage algorithms used in fastpair +pub mod dissimilarities; /// fastpair closest neighbour algorithm pub(crate) mod fastpair; -// Dissimilarities for vector-vector distance. Linkage algorithms used in fastpair -pub mod dissimilarities; +/// very simple algorithm that sequentially checks each element of the list until a match is found or the whole list has been searched. +pub mod linear_search; /// Both, KNN classifier and regressor benefits from underlying search algorithms that helps to speed up queries. /// `KNNAlgorithmName` maintains a list of supported search algorithms, see [KNN algorithms](../algorithm/neighbour/index.html) From f8281fe12bdd16a67c6d8a758717fcbee64087d3 Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Mon, 15 Aug 2022 19:21:36 +0100 Subject: [PATCH 07/17] Clear clippy --- src/algorithm/neighbour/dissimilarities.rs | 10 +++++----- src/algorithm/neighbour/fastpair.rs | 16 ++++++++++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/algorithm/neighbour/dissimilarities.rs b/src/algorithm/neighbour/dissimilarities.rs index 901fbf88..c314f793 100644 --- a/src/algorithm/neighbour/dissimilarities.rs +++ b/src/algorithm/neighbour/dissimilarities.rs @@ -16,15 +16,15 @@ use crate::math::num::RealNumber; /// #[derive(Debug, Clone, Copy)] pub struct PairwiseDissimilarity { - // index of the vector in the original `Matrix` or list + /// index of the vector in the original `Matrix` or list pub node: usize, - // index of the closest neighbor in the original `Matrix` or same list + /// index of the closest neighbor in the original `Matrix` or same list pub neighbour: Option, - // measure of distance, according to the algorithm distance function - // if the distance is None, the edge has value "infinite" or max distance - // each algorithm has to match + /// measure of distance, according to the algorithm distance function + /// if the distance is None, the edge has value "infinite" or max distance + /// each algorithm has to match pub distance: Option, } diff --git a/src/algorithm/neighbour/fastpair.rs b/src/algorithm/neighbour/fastpair.rs index b9f6c1e7..e513af03 100644 --- a/src/algorithm/neighbour/fastpair.rs +++ b/src/algorithm/neighbour/fastpair.rs @@ -18,6 +18,7 @@ use crate::math::num::RealNumber; /// /// FastPair factory function /// +#[allow(dead_code)] pub fn FastPair>(m: &M) -> Result<_FastPair<'_, T, M>, Failed> { if m.shape().0 < 3 { return Err(Failed::because( @@ -28,7 +29,7 @@ pub fn FastPair>(m: &M) -> Result<_FastPair<'_, T, M let mut init = _FastPair { samples: m, - distances: Box::new(HashMap::with_capacity(m.shape().0)), + distances: HashMap::with_capacity(m.shape().0), neighbours: Vec::with_capacity(m.shape().0 + 1), // to be computed in init(..) connectivity: None, @@ -51,7 +52,7 @@ pub struct _FastPair<'a, T: RealNumber, M: Matrix> { /// initial matrix samples: &'a M, /// closest pair hashmap (connectivity matrix for closest pairs) - pub distances: Box>>, + pub distances: HashMap>, /// conga line used to keep track of the closest pair pub neighbours: Vec, /// sparse matrix of closest pairs @@ -64,6 +65,7 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { /// Initialise `FastPair` by passing a `Matrix`. /// Build a FastPairs data-structure from a set of (new) points. /// + #[allow(dead_code)] fn init(&mut self) { // basic measures let len = self.samples.shape().0; @@ -136,7 +138,7 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { // TODO: as we now store the connectivity matrix in `self.connectivity`, // it may be possible to avoid storing closest pairs in `self.distances` - self.distances = Box::new(distances); + self.distances = distances; self.neighbours = neighbours; self.connectivity = Some(Box::new(sparse_matrix)); } @@ -144,13 +146,14 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { /// /// Find closest pair by scanning list of nearest neighbors. /// + #[allow(dead_code)] pub fn closest_pair(&self) -> PairwiseDissimilarity { let mut a = self.neighbours[0]; // Start with first point let mut d = self.distances[&a].distance; for p in self.neighbours.iter() { - if self.distances[&p].distance < d { + if self.distances[p].distance < d { a = *p; // Update `a` and distance `d` - d = self.distances[&p].distance; + d = self.distances[p].distance; } } let b = self.distances[&a].neighbour; @@ -164,6 +167,7 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { /// /// Brute force algorithm, used only for comparison and testing /// + #[allow(dead_code)] pub fn closest_pair_brute(&self) -> PairwiseDissimilarity { let m = self.samples.shape().0; @@ -222,7 +226,7 @@ mod tests_fastpair { assert!(fastpair.is_ok()); let result = fastpair.unwrap(); - let distances = *result.distances; + let distances = result.distances; let neighbours = result.neighbours; let sparse_matrix = *(result.connectivity.unwrap()); assert_eq!(10, neighbours.len()); From a976a4f0f0f35a7107648d90c568f8a6c5eb729c Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Tue, 16 Aug 2022 14:03:21 +0100 Subject: [PATCH 08/17] Add random test --- src/algorithm/neighbour/fastpair.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/algorithm/neighbour/fastpair.rs b/src/algorithm/neighbour/fastpair.rs index e513af03..b31e4152 100644 --- a/src/algorithm/neighbour/fastpair.rs +++ b/src/algorithm/neighbour/fastpair.rs @@ -274,7 +274,7 @@ mod tests_fastpair { } #[test] - fn one_dimensional_dataset_currently_fails() { + fn one_dimensional_dataset_2() { let dataset = DenseMatrix::from_2d_array(&[&[27.0], &[0.0], &[9.0], &[2.0]]); let result = FastPair(&dataset); @@ -394,6 +394,21 @@ mod tests_fastpair { assert_eq!(closest, dissimilarity); } + #[test] + fn fastpair_closest_pair_random_matrix() { + let x = DenseMatrix::::rand(200, 25); + // compute + let fastpair = FastPair(&x); + assert!(fastpair.is_ok()); + + let result = fastpair.unwrap(); + + let dissimilarity1 = result.closest_pair(); + let dissimilarity2 = result.closest_pair_brute(); + + assert_eq!(dissimilarity1, dissimilarity2); + } + #[test] fn fastpair_distances() { let x = DenseMatrix::from_2d_array(&[ From 781fa2ca7cedc52e1ca5de40ce4d464ef048a83c Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Tue, 16 Aug 2022 14:28:28 +0100 Subject: [PATCH 09/17] improve tests --- src/algorithm/neighbour/dissimilarities.rs | 4 +- src/algorithm/neighbour/fastpair.rs | 172 ++++++++++++++++----- 2 files changed, 133 insertions(+), 43 deletions(-) diff --git a/src/algorithm/neighbour/dissimilarities.rs b/src/algorithm/neighbour/dissimilarities.rs index c314f793..e5161976 100644 --- a/src/algorithm/neighbour/dissimilarities.rs +++ b/src/algorithm/neighbour/dissimilarities.rs @@ -1,10 +1,10 @@ //! -//! ## Dissimilarities for vector-vector distance +//! Dissimilarities for vector-vector distance //! //! Different algorithms based on Closest Pairs use to store graph's edges to //! compute clusters or closest neighbors in Matrices rows. //! The struct `PairwiseDissimilarity` can be used to represent edges between -//! closest pairs by storing the nodes' indeces. +//! closest pairs by storing the nodes' indices. use std::cmp::{Eq, Ordering, PartialOrd}; use crate::math::num::RealNumber; diff --git a/src/algorithm/neighbour/fastpair.rs b/src/algorithm/neighbour/fastpair.rs index b31e4152..08ff7c86 100644 --- a/src/algorithm/neighbour/fastpair.rs +++ b/src/algorithm/neighbour/fastpair.rs @@ -29,10 +29,9 @@ pub fn FastPair>(m: &M) -> Result<_FastPair<'_, T, M let mut init = _FastPair { samples: m, + // to be computed in init(..) distances: HashMap::with_capacity(m.shape().0), neighbours: Vec::with_capacity(m.shape().0 + 1), - // to be computed in init(..) - connectivity: None, }; init.init(); Ok(init) @@ -55,9 +54,6 @@ pub struct _FastPair<'a, T: RealNumber, M: Matrix> { pub distances: HashMap>, /// conga line used to keep track of the closest pair pub neighbours: Vec, - /// sparse matrix of closest pairs - /// values are set for closest pairs distances, other pairs are zeroed - pub connectivity: Option>, } impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { @@ -81,9 +77,8 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { // fill neighbours with -1 values neighbours.extend(0..len); - // loop through indeces and neighbours + // init closest neighbour pairwise data for index_row_i in 0..(max_index) { - // init closest neighbour pairwise data distances.insert( index_row_i, PairwiseDissimilarity { @@ -94,6 +89,7 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { ); } + // loop through indeces and neighbours for index_row_i in 0..(len) { // start looking for the neighbour in the second element let mut index_closest = index_row_i + 1; // closest neighbour index @@ -119,7 +115,7 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { } } - // Add that edge, move nbr to points[i+1] in conga line + // Add that edge distances.entry(index_row_i).and_modify(|e| { e.distance = nbd; e.neighbour = Some(index_closest); @@ -136,11 +132,8 @@ impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { sparse_matrix.set(p.node, p.neighbour.unwrap(), p.distance.unwrap()); } - // TODO: as we now store the connectivity matrix in `self.connectivity`, - // it may be possible to avoid storing closest pairs in `self.distances` self.distances = distances; self.neighbours = neighbours; - self.connectivity = Some(Box::new(sparse_matrix)); } /// @@ -228,11 +221,9 @@ mod tests_fastpair { let result = fastpair.unwrap(); let distances = result.distances; let neighbours = result.neighbours; - let sparse_matrix = *(result.connectivity.unwrap()); + assert_eq!(10, neighbours.len()); assert_eq!(10, distances.len()); - assert_eq!(10, sparse_matrix.shape().0); - assert_eq!(10, sparse_matrix.shape().1); } #[test] @@ -294,7 +285,7 @@ mod tests_fastpair { #[test] fn fastpair_new() { // compute - let x = DenseMatrix::from_2d_array(&[ + let x = DenseMatrix::::from_2d_array(&[ &[5.1, 3.5, 1.4, 0.2], &[4.9, 3.0, 1.4, 0.2], &[4.7, 3.2, 1.3, 0.2], @@ -316,27 +307,130 @@ mod tests_fastpair { // unwrap results let result = fastpair.unwrap(); - // let distances = *result.distances; - let sparse_matrix = *(result.connectivity.unwrap()); // list of minimal pairwise dissimilarities - let dissimilarities = vec!( - (1, PairwiseDissimilarity { node: 1, neighbour: Some(9), distance: Some(0.030000000000000037) }), - (10, PairwiseDissimilarity { node: 10, neighbour: Some(12), distance: Some(0.07000000000000003) }), - (11, PairwiseDissimilarity { node: 11, neighbour: Some(14), distance: Some(0.18000000000000013) }), - (12, PairwiseDissimilarity { node: 12, neighbour: Some(14), distance: Some(0.34000000000000086) }), - (13, PairwiseDissimilarity { node: 13, neighbour: Some(14), distance: Some(1.6499999999999997) }), - (14, PairwiseDissimilarity { node: 14, neighbour: Some(14), distance: Some(179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) }), - (6, PairwiseDissimilarity { node: 6, neighbour: Some(7), distance: Some(0.18000000000000027) }), - (0, PairwiseDissimilarity { node: 0, neighbour: Some(4), distance: Some(0.01999999999999995) }), - (8, PairwiseDissimilarity { node: 8, neighbour: Some(9), distance: Some(0.3100000000000001) }), - (2, PairwiseDissimilarity { node: 2, neighbour: Some(3), distance: Some(0.0600000000000001) }), - (3, PairwiseDissimilarity { node: 3, neighbour: Some(8), distance: Some(0.08999999999999982) }), - (7, PairwiseDissimilarity { node: 7, neighbour: Some(9), distance: Some(0.10999999999999982) }), - (9, PairwiseDissimilarity { node: 9, neighbour: Some(13), distance: Some(8.69) }), - (4, PairwiseDissimilarity { node: 4, neighbour: Some(7), distance: Some(0.050000000000000086) }), - (5, PairwiseDissimilarity { node: 5, neighbour: Some(7), distance: Some(0.4900000000000002) }) - ); + let dissimilarities = vec![ + ( + 1, + PairwiseDissimilarity { + node: 1, + neighbour: Some(9), + distance: Some(0.030000000000000037), + }, + ), + ( + 10, + PairwiseDissimilarity { + node: 10, + neighbour: Some(12), + distance: Some(0.07000000000000003), + }, + ), + ( + 11, + PairwiseDissimilarity { + node: 11, + neighbour: Some(14), + distance: Some(0.18000000000000013), + }, + ), + ( + 12, + PairwiseDissimilarity { + node: 12, + neighbour: Some(14), + distance: Some(0.34000000000000086), + }, + ), + ( + 13, + PairwiseDissimilarity { + node: 13, + neighbour: Some(14), + distance: Some(1.6499999999999997), + }, + ), + ( + 14, + PairwiseDissimilarity { + node: 14, + neighbour: Some(14), + distance: Some(f64::MAX), + }, + ), + ( + 6, + PairwiseDissimilarity { + node: 6, + neighbour: Some(7), + distance: Some(0.18000000000000027), + }, + ), + ( + 0, + PairwiseDissimilarity { + node: 0, + neighbour: Some(4), + distance: Some(0.01999999999999995), + }, + ), + ( + 8, + PairwiseDissimilarity { + node: 8, + neighbour: Some(9), + distance: Some(0.3100000000000001), + }, + ), + ( + 2, + PairwiseDissimilarity { + node: 2, + neighbour: Some(3), + distance: Some(0.0600000000000001), + }, + ), + ( + 3, + PairwiseDissimilarity { + node: 3, + neighbour: Some(8), + distance: Some(0.08999999999999982), + }, + ), + ( + 7, + PairwiseDissimilarity { + node: 7, + neighbour: Some(9), + distance: Some(0.10999999999999982), + }, + ), + ( + 9, + PairwiseDissimilarity { + node: 9, + neighbour: Some(13), + distance: Some(8.69), + }, + ), + ( + 4, + PairwiseDissimilarity { + node: 4, + neighbour: Some(7), + distance: Some(0.050000000000000086), + }, + ), + ( + 5, + PairwiseDissimilarity { + node: 5, + neighbour: Some(7), + distance: Some(0.4900000000000002), + }, + ), + ]; let expected: HashMap<_, _> = dissimilarities.into_iter().collect(); @@ -354,16 +448,12 @@ mod tests_fastpair { expected.get(&i).unwrap().neighbour.unwrap() ); assert_eq!(distance, expected.get(&i).unwrap().distance.unwrap()); - assert_eq!( - sparse_matrix.get(i, input_neighbour), - expected.get(&i).unwrap().distance.unwrap() - ); } } #[test] fn fastpair_closest_pair() { - let x = DenseMatrix::from_2d_array(&[ + let x = DenseMatrix::::from_2d_array(&[ &[5.1, 3.5, 1.4, 0.2], &[4.9, 3.0, 1.4, 0.2], &[4.7, 3.2, 1.3, 0.2], @@ -411,7 +501,7 @@ mod tests_fastpair { #[test] fn fastpair_distances() { - let x = DenseMatrix::from_2d_array(&[ + let x = DenseMatrix::::from_2d_array(&[ &[5.1, 3.5, 1.4, 0.2], &[4.9, 3.0, 1.4, 0.2], &[4.7, 3.2, 1.3, 0.2], @@ -437,7 +527,7 @@ mod tests_fastpair { let mut min_dissimilarity = PairwiseDissimilarity { node: 0, neighbour: None, - distance: Some(179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0), + distance: Some(f64::MAX), }; for p in dissimilarities.iter() { if p.distance.unwrap() < min_dissimilarity.distance.unwrap() { From 4134568b07a75aa79265df330c341f83c3cd2af6 Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Tue, 16 Aug 2022 15:35:40 +0100 Subject: [PATCH 10/17] Add bench for fastpair --- Cargo.toml | 4 ++++ benches/fastpair.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 benches/fastpair.rs diff --git a/Cargo.toml b/Cargo.toml index e17a48f7..9da75a9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,3 +47,7 @@ harness = false name = "naive_bayes" harness = false required-features = ["ndarray-bindings", "nalgebra-bindings"] + +[[bench]] +name = "fastpair" +harness = false \ No newline at end of file diff --git a/benches/fastpair.rs b/benches/fastpair.rs new file mode 100644 index 00000000..3d12766f --- /dev/null +++ b/benches/fastpair.rs @@ -0,0 +1,56 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; + +// to run this bench you have to change the declaraion in mod.rs ---> pub mod fastpair; +use smartcore::algorithm::neighbour::fastpair::FastPair; +use smartcore::linalg::naive::dense_matrix::*; +use std::time::Duration; + +fn closest_pair_bench(n: usize, m: usize) -> () { + let x = DenseMatrix::::rand(n, m); + let fastpair = FastPair(&x); + let result = fastpair.unwrap(); + + result.closest_pair(); +} + +fn closest_pair_brute_bench(n: usize, m: usize) -> () { + let x = DenseMatrix::::rand(n, m); + let fastpair = FastPair(&x); + let result = fastpair.unwrap(); + + result.closest_pair_brute(); +} + +fn bench_fastpair(c: &mut Criterion) { + let mut group = c.benchmark_group("FastPair"); + + // with full samples size (100) the test will take too long + group.significance_level(0.1).sample_size(30); + // increase from default 5.0 secs + group.measurement_time(Duration::from_secs(60)); + + for n_samples in [100_usize, 1000_usize, 10000_usize].iter() { + for n_features in [10_usize, 100_usize, 1000_usize].iter() { + group.bench_with_input( + BenchmarkId::from_parameter(format!( + "fastpair --- n_samples: {}, n_features: {}", + n_samples, n_features + )), + n_samples, + |b, _| b.iter(|| closest_pair_bench(*n_samples, *n_features)), + ); + group.bench_with_input( + BenchmarkId::from_parameter(format!( + "brute --- n_samples: {}, n_features: {}", + n_samples, n_features + )), + n_samples, + |b, _| b.iter(|| closest_pair_brute_bench(*n_samples, *n_features)), + ); + } + } + group.finish(); +} + +criterion_group!(benches, bench_fastpair); +criterion_main!(benches); From e06bbc5651b999eab5fc1a13c173133aba77233e Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Tue, 16 Aug 2022 15:43:46 +0100 Subject: [PATCH 11/17] Update docstring --- src/algorithm/neighbour/dissimilarities.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/algorithm/neighbour/dissimilarities.rs b/src/algorithm/neighbour/dissimilarities.rs index e5161976..3bc17ac5 100644 --- a/src/algorithm/neighbour/dissimilarities.rs +++ b/src/algorithm/neighbour/dissimilarities.rs @@ -1,10 +1,9 @@ //! //! Dissimilarities for vector-vector distance //! -//! Different algorithms based on Closest Pairs use to store graph's edges to -//! compute clusters or closest neighbors in Matrices rows. -//! The struct `PairwiseDissimilarity` can be used to represent edges between -//! closest pairs by storing the nodes' indices. +//! Representing distances as pairwise dissimilarities, so to build a +//! graph of closest neoghbour. This representation can be reused for +//! different implementations (initially used in this library for FastPair). use std::cmp::{Eq, Ordering, PartialOrd}; use crate::math::num::RealNumber; From fb9e7a28ddd238fea009308af0061175220cb02d Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Thu, 18 Aug 2022 20:06:48 +0100 Subject: [PATCH 12/17] Refactor with constructor for FastPair --- benches/fastpair.rs | 6 +- src/algorithm/neighbour/fastpair.rs | 98 ++++++++++++++++------------- src/algorithm/neighbour/mod.rs | 2 +- 3 files changed, 57 insertions(+), 49 deletions(-) diff --git a/benches/fastpair.rs b/benches/fastpair.rs index 3d12766f..baa0e901 100644 --- a/benches/fastpair.rs +++ b/benches/fastpair.rs @@ -7,7 +7,7 @@ use std::time::Duration; fn closest_pair_bench(n: usize, m: usize) -> () { let x = DenseMatrix::::rand(n, m); - let fastpair = FastPair(&x); + let fastpair = FastPair::new(&x); let result = fastpair.unwrap(); result.closest_pair(); @@ -15,7 +15,7 @@ fn closest_pair_bench(n: usize, m: usize) -> () { fn closest_pair_brute_bench(n: usize, m: usize) -> () { let x = DenseMatrix::::rand(n, m); - let fastpair = FastPair(&x); + let fastpair = FastPair::new(&x); let result = fastpair.unwrap(); result.closest_pair_brute(); @@ -29,7 +29,7 @@ fn bench_fastpair(c: &mut Criterion) { // increase from default 5.0 secs group.measurement_time(Duration::from_secs(60)); - for n_samples in [100_usize, 1000_usize, 10000_usize].iter() { + for n_samples in [100_usize, 1000_usize].iter() { for n_features in [10_usize, 100_usize, 1000_usize].iter() { group.bench_with_input( BenchmarkId::from_parameter(format!( diff --git a/src/algorithm/neighbour/fastpair.rs b/src/algorithm/neighbour/fastpair.rs index 08ff7c86..24c616cd 100644 --- a/src/algorithm/neighbour/fastpair.rs +++ b/src/algorithm/neighbour/fastpair.rs @@ -15,28 +15,6 @@ use crate::linalg::Matrix; use crate::math::distance::euclidian::Euclidian; use crate::math::num::RealNumber; -/// -/// FastPair factory function -/// -#[allow(dead_code)] -pub fn FastPair>(m: &M) -> Result<_FastPair<'_, T, M>, Failed> { - if m.shape().0 < 3 { - return Err(Failed::because( - FailedError::FindFailed, - "min number of rows is 3", - )); - } - - let mut init = _FastPair { - samples: m, - // to be computed in init(..) - distances: HashMap::with_capacity(m.shape().0), - neighbours: Vec::with_capacity(m.shape().0 + 1), - }; - init.init(); - Ok(init) -} - /// /// FastPair /// @@ -47,7 +25,7 @@ pub fn FastPair>(m: &M) -> Result<_FastPair<'_, T, M /// affinity used is Euclidean so to allow linkage with single, ward, complete and average /// #[derive(Debug, Clone)] -pub struct _FastPair<'a, T: RealNumber, M: Matrix> { +pub struct FastPair<'a, T: RealNumber, M: Matrix> { /// initial matrix samples: &'a M, /// closest pair hashmap (connectivity matrix for closest pairs) @@ -56,12 +34,33 @@ pub struct _FastPair<'a, T: RealNumber, M: Matrix> { pub neighbours: Vec, } -impl<'a, T: RealNumber, M: Matrix> _FastPair<'a, T, M> { +impl<'a, T: RealNumber, M: Matrix> FastPair<'a, T, M> { + /// + /// Constructor + /// Instantiate and inizialise the algorithm + /// + pub fn new(m: &'a M) -> Result { + if m.shape().0 < 3 { + return Err(Failed::because( + FailedError::FindFailed, + "min number of rows is 3", + )); + } + + let mut init = Self { + samples: m, + // to be computed in init(..) + distances: HashMap::with_capacity(m.shape().0), + neighbours: Vec::with_capacity(m.shape().0 + 1), + }; + init.init(); + Ok(init) + } + /// /// Initialise `FastPair` by passing a `Matrix`. /// Build a FastPairs data-structure from a set of (new) points. /// - #[allow(dead_code)] fn init(&mut self) { // basic measures let len = self.samples.shape().0; @@ -215,12 +214,16 @@ mod tests_fastpair { #[test] fn fastpair_init() { let x: DenseMatrix = DenseMatrix::rand(10, 4); - let fastpair = FastPair(&x); - assert!(fastpair.is_ok()); + let _fastpair = FastPair::new(&x); + assert!(_fastpair.is_ok()); - let result = fastpair.unwrap(); - let distances = result.distances; - let neighbours = result.neighbours; + let fastpair = _fastpair.unwrap(); + + let distances = fastpair.distances; + let neighbours = fastpair.neighbours; + + assert!(distances.len() != 0); + assert!(neighbours.len() != 0); assert_eq!(10, neighbours.len()); assert_eq!(10, distances.len()); @@ -230,25 +233,30 @@ mod tests_fastpair { fn dataset_has_at_least_three_points() { // Create a dataset which consists of only two points: // A(0.0, 0.0) and B(1.0, 1.0). - let dataset = DenseMatrix::from_2d_array(&[&[0.0, 0.0], &[1.0, 1.0]]); + let dataset = DenseMatrix::::from_2d_array(&[&[0.0, 0.0], &[1.0, 1.0]]); // We expect an error when we run `FastPair` on this dataset, // becuase `FastPair` currently only works on a minimum of 3 // points. - if let Err(e) = FastPair(&dataset) { - let expected_error = - Failed::because(FailedError::FindFailed, "min number of rows is 3"); - assert_eq!(e, expected_error) - } else { - assert!(false) + let _fastpair = FastPair::new(&dataset); + + match _fastpair { + Err(e) => { + let expected_error = + Failed::because(FailedError::FindFailed, "min number of rows is 3"); + assert_eq!(e, expected_error) + } + _ => { + assert!(false); + } } } #[test] fn one_dimensional_dataset_minimal() { - let dataset = DenseMatrix::from_2d_array(&[&[0.0], &[2.0], &[9.0]]); + let dataset = DenseMatrix::::from_2d_array(&[&[0.0], &[2.0], &[9.0]]); - let result = FastPair(&dataset); + let result = FastPair::new(&dataset); assert!(result.is_ok()); let fastpair = result.unwrap(); @@ -266,9 +274,9 @@ mod tests_fastpair { #[test] fn one_dimensional_dataset_2() { - let dataset = DenseMatrix::from_2d_array(&[&[27.0], &[0.0], &[9.0], &[2.0]]); + let dataset = DenseMatrix::::from_2d_array(&[&[27.0], &[0.0], &[9.0], &[2.0]]); - let result = FastPair(&dataset); + let result = FastPair::new(&dataset); assert!(result.is_ok()); let fastpair = result.unwrap(); @@ -302,7 +310,7 @@ mod tests_fastpair { &[5.5, 2.3, 4.0, 1.3], &[6.5, 2.8, 4.6, 1.5], ]); - let fastpair = FastPair(&x); + let fastpair = FastPair::new(&x); assert!(fastpair.is_ok()); // unwrap results @@ -471,7 +479,7 @@ mod tests_fastpair { &[6.5, 2.8, 4.6, 1.5], ]); // compute - let fastpair = FastPair(&x); + let fastpair = FastPair::new(&x); assert!(fastpair.is_ok()); let dissimilarity = fastpair.unwrap().closest_pair(); @@ -488,7 +496,7 @@ mod tests_fastpair { fn fastpair_closest_pair_random_matrix() { let x = DenseMatrix::::rand(200, 25); // compute - let fastpair = FastPair(&x); + let fastpair = FastPair::new(&x); assert!(fastpair.is_ok()); let result = fastpair.unwrap(); @@ -519,7 +527,7 @@ mod tests_fastpair { &[6.5, 2.8, 4.6, 1.5], ]); // compute - let fastpair = FastPair(&x); + let fastpair = FastPair::new(&x); assert!(fastpair.is_ok()); let dissimilarities = fastpair.unwrap().distances_from(0); diff --git a/src/algorithm/neighbour/mod.rs b/src/algorithm/neighbour/mod.rs index a31fc144..fbb455d3 100644 --- a/src/algorithm/neighbour/mod.rs +++ b/src/algorithm/neighbour/mod.rs @@ -44,7 +44,7 @@ pub mod cover_tree; /// dissimilarities for vector-vector distance. Linkage algorithms used in fastpair pub mod dissimilarities; /// fastpair closest neighbour algorithm -pub(crate) mod fastpair; +pub mod fastpair; /// very simple algorithm that sequentially checks each element of the list until a match is found or the whole list has been searched. pub mod linear_search; From 5073d7f573d1bb9e081a019ffed8aba58b105e97 Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Sun, 21 Aug 2022 17:22:45 +0100 Subject: [PATCH 13/17] Improve error message --- src/algorithm/neighbour/fastpair.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/algorithm/neighbour/fastpair.rs b/src/algorithm/neighbour/fastpair.rs index 24c616cd..15d4662c 100644 --- a/src/algorithm/neighbour/fastpair.rs +++ b/src/algorithm/neighbour/fastpair.rs @@ -43,7 +43,7 @@ impl<'a, T: RealNumber, M: Matrix> FastPair<'a, T, M> { if m.shape().0 < 3 { return Err(Failed::because( FailedError::FindFailed, - "min number of rows is 3", + "min number of rows should be 3", )); } @@ -243,7 +243,7 @@ mod tests_fastpair { match _fastpair { Err(e) => { let expected_error = - Failed::because(FailedError::FindFailed, "min number of rows is 3"); + Failed::because(FailedError::FindFailed, "min number of rows should be 3"); assert_eq!(e, expected_error) } _ => { From 009432696cfb36fdffcc11b1b7d49ed0dac1515d Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Sun, 21 Aug 2022 17:34:51 +0100 Subject: [PATCH 14/17] Rename differences to distances, as it is more common naming --- .../{dissimilarities.rs => distances.rs} | 8 +-- src/algorithm/neighbour/fastpair.rs | 62 +++++++++---------- src/algorithm/neighbour/mod.rs | 2 +- 3 files changed, 36 insertions(+), 36 deletions(-) rename src/algorithm/neighbour/{dissimilarities.rs => distances.rs} (84%) diff --git a/src/algorithm/neighbour/dissimilarities.rs b/src/algorithm/neighbour/distances.rs similarity index 84% rename from src/algorithm/neighbour/dissimilarities.rs rename to src/algorithm/neighbour/distances.rs index 3bc17ac5..d24c96d1 100644 --- a/src/algorithm/neighbour/dissimilarities.rs +++ b/src/algorithm/neighbour/distances.rs @@ -14,7 +14,7 @@ use crate::math::num::RealNumber; /// a list of these structures. /// #[derive(Debug, Clone, Copy)] -pub struct PairwiseDissimilarity { +pub struct PairwiseDistance { /// index of the vector in the original `Matrix` or list pub node: usize, @@ -27,9 +27,9 @@ pub struct PairwiseDissimilarity { pub distance: Option, } -impl Eq for PairwiseDissimilarity {} +impl Eq for PairwiseDistance {} -impl PartialEq for PairwiseDissimilarity { +impl PartialEq for PairwiseDistance { fn eq(&self, other: &Self) -> bool { self.node == other.node && self.neighbour == other.neighbour @@ -37,7 +37,7 @@ impl PartialEq for PairwiseDissimilarity { } } -impl PartialOrd for PairwiseDissimilarity { +impl PartialOrd for PairwiseDistance { fn partial_cmp(&self, other: &Self) -> Option { self.distance.partial_cmp(&other.distance) } diff --git a/src/algorithm/neighbour/fastpair.rs b/src/algorithm/neighbour/fastpair.rs index 15d4662c..36d0e548 100644 --- a/src/algorithm/neighbour/fastpair.rs +++ b/src/algorithm/neighbour/fastpair.rs @@ -9,7 +9,7 @@ use itertools::Itertools; /// use std::collections::HashMap; -use crate::algorithm::neighbour::dissimilarities::PairwiseDissimilarity; +use crate::algorithm::neighbour::distances::PairwiseDistance; use crate::error::{Failed, FailedError}; use crate::linalg::Matrix; use crate::math::distance::euclidian::Euclidian; @@ -29,7 +29,7 @@ pub struct FastPair<'a, T: RealNumber, M: Matrix> { /// initial matrix samples: &'a M, /// closest pair hashmap (connectivity matrix for closest pairs) - pub distances: HashMap>, + pub distances: HashMap>, /// conga line used to keep track of the closest pair pub neighbours: Vec, } @@ -80,7 +80,7 @@ impl<'a, T: RealNumber, M: Matrix> FastPair<'a, T, M> { for index_row_i in 0..(max_index) { distances.insert( index_row_i, - PairwiseDissimilarity { + PairwiseDistance { node: index_row_i, neighbour: None, distance: Some(T::max_value()), @@ -96,7 +96,7 @@ impl<'a, T: RealNumber, M: Matrix> FastPair<'a, T, M> { for index_row_j in (index_row_i + 1)..len { distances.insert( index_row_j, - PairwiseDissimilarity { + PairwiseDistance { node: index_row_j, neighbour: Some(index_row_i), distance: nbd, @@ -139,7 +139,7 @@ impl<'a, T: RealNumber, M: Matrix> FastPair<'a, T, M> { /// Find closest pair by scanning list of nearest neighbors. /// #[allow(dead_code)] - pub fn closest_pair(&self) -> PairwiseDissimilarity { + pub fn closest_pair(&self) -> PairwiseDistance { let mut a = self.neighbours[0]; // Start with first point let mut d = self.distances[&a].distance; for p in self.neighbours.iter() { @@ -149,7 +149,7 @@ impl<'a, T: RealNumber, M: Matrix> FastPair<'a, T, M> { } } let b = self.distances[&a].neighbour; - PairwiseDissimilarity { + PairwiseDistance { node: a, neighbour: b, distance: d, @@ -160,10 +160,10 @@ impl<'a, T: RealNumber, M: Matrix> FastPair<'a, T, M> { /// Brute force algorithm, used only for comparison and testing /// #[allow(dead_code)] - pub fn closest_pair_brute(&self) -> PairwiseDissimilarity { + pub fn closest_pair_brute(&self) -> PairwiseDistance { let m = self.samples.shape().0; - let mut closest_pair = PairwiseDissimilarity { + let mut closest_pair = PairwiseDistance { node: 0, neighbour: None, distance: Some(T::max_value()), @@ -187,11 +187,11 @@ impl<'a, T: RealNumber, M: Matrix> FastPair<'a, T, M> { // input is the row index of the sample matrix // #[allow(dead_code)] - fn distances_from(&self, index_row: usize) -> Vec> { - let mut distances = Vec::>::with_capacity(self.samples.shape().0); + fn distances_from(&self, index_row: usize) -> Vec> { + let mut distances = Vec::>::with_capacity(self.samples.shape().0); for other in self.neighbours.iter() { if index_row != *other { - distances.push(PairwiseDissimilarity { + distances.push(PairwiseDistance { node: index_row, neighbour: Some(*other), distance: Some(Euclidian::squared_distance( @@ -261,7 +261,7 @@ mod tests_fastpair { let fastpair = result.unwrap(); let closest_pair = fastpair.closest_pair(); - let expected_closest_pair = PairwiseDissimilarity { + let expected_closest_pair = PairwiseDistance { node: 0, neighbour: Some(1), distance: Some(4.0), @@ -281,7 +281,7 @@ mod tests_fastpair { let fastpair = result.unwrap(); let closest_pair = fastpair.closest_pair(); - let expected_closest_pair = PairwiseDissimilarity { + let expected_closest_pair = PairwiseDistance { node: 1, neighbour: Some(3), distance: Some(4.0), @@ -320,7 +320,7 @@ mod tests_fastpair { let dissimilarities = vec![ ( 1, - PairwiseDissimilarity { + PairwiseDistance { node: 1, neighbour: Some(9), distance: Some(0.030000000000000037), @@ -328,7 +328,7 @@ mod tests_fastpair { ), ( 10, - PairwiseDissimilarity { + PairwiseDistance { node: 10, neighbour: Some(12), distance: Some(0.07000000000000003), @@ -336,7 +336,7 @@ mod tests_fastpair { ), ( 11, - PairwiseDissimilarity { + PairwiseDistance { node: 11, neighbour: Some(14), distance: Some(0.18000000000000013), @@ -344,7 +344,7 @@ mod tests_fastpair { ), ( 12, - PairwiseDissimilarity { + PairwiseDistance { node: 12, neighbour: Some(14), distance: Some(0.34000000000000086), @@ -352,7 +352,7 @@ mod tests_fastpair { ), ( 13, - PairwiseDissimilarity { + PairwiseDistance { node: 13, neighbour: Some(14), distance: Some(1.6499999999999997), @@ -360,7 +360,7 @@ mod tests_fastpair { ), ( 14, - PairwiseDissimilarity { + PairwiseDistance { node: 14, neighbour: Some(14), distance: Some(f64::MAX), @@ -368,7 +368,7 @@ mod tests_fastpair { ), ( 6, - PairwiseDissimilarity { + PairwiseDistance { node: 6, neighbour: Some(7), distance: Some(0.18000000000000027), @@ -376,7 +376,7 @@ mod tests_fastpair { ), ( 0, - PairwiseDissimilarity { + PairwiseDistance { node: 0, neighbour: Some(4), distance: Some(0.01999999999999995), @@ -384,7 +384,7 @@ mod tests_fastpair { ), ( 8, - PairwiseDissimilarity { + PairwiseDistance { node: 8, neighbour: Some(9), distance: Some(0.3100000000000001), @@ -392,7 +392,7 @@ mod tests_fastpair { ), ( 2, - PairwiseDissimilarity { + PairwiseDistance { node: 2, neighbour: Some(3), distance: Some(0.0600000000000001), @@ -400,7 +400,7 @@ mod tests_fastpair { ), ( 3, - PairwiseDissimilarity { + PairwiseDistance { node: 3, neighbour: Some(8), distance: Some(0.08999999999999982), @@ -408,7 +408,7 @@ mod tests_fastpair { ), ( 7, - PairwiseDissimilarity { + PairwiseDistance { node: 7, neighbour: Some(9), distance: Some(0.10999999999999982), @@ -416,7 +416,7 @@ mod tests_fastpair { ), ( 9, - PairwiseDissimilarity { + PairwiseDistance { node: 9, neighbour: Some(13), distance: Some(8.69), @@ -424,7 +424,7 @@ mod tests_fastpair { ), ( 4, - PairwiseDissimilarity { + PairwiseDistance { node: 4, neighbour: Some(7), distance: Some(0.050000000000000086), @@ -432,7 +432,7 @@ mod tests_fastpair { ), ( 5, - PairwiseDissimilarity { + PairwiseDistance { node: 5, neighbour: Some(7), distance: Some(0.4900000000000002), @@ -483,7 +483,7 @@ mod tests_fastpair { assert!(fastpair.is_ok()); let dissimilarity = fastpair.unwrap().closest_pair(); - let closest = PairwiseDissimilarity { + let closest = PairwiseDistance { node: 0, neighbour: Some(4), distance: Some(0.01999999999999995), @@ -532,7 +532,7 @@ mod tests_fastpair { let dissimilarities = fastpair.unwrap().distances_from(0); - let mut min_dissimilarity = PairwiseDissimilarity { + let mut min_dissimilarity = PairwiseDistance { node: 0, neighbour: None, distance: Some(f64::MAX), @@ -543,7 +543,7 @@ mod tests_fastpair { } } - let closest = PairwiseDissimilarity { + let closest = PairwiseDistance { node: 0, neighbour: Some(4), distance: Some(0.01999999999999995), diff --git a/src/algorithm/neighbour/mod.rs b/src/algorithm/neighbour/mod.rs index fbb455d3..42ab7bc8 100644 --- a/src/algorithm/neighbour/mod.rs +++ b/src/algorithm/neighbour/mod.rs @@ -42,7 +42,7 @@ pub(crate) mod bbd_tree; /// tree data structure for fast nearest neighbor search pub mod cover_tree; /// dissimilarities for vector-vector distance. Linkage algorithms used in fastpair -pub mod dissimilarities; +pub mod distances; /// fastpair closest neighbour algorithm pub mod fastpair; /// very simple algorithm that sequentially checks each element of the list until a match is found or the whole list has been searched. From 2dbbb7664d13b29a2f10153c2f8374978366cf4a Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Sun, 21 Aug 2022 17:49:51 +0100 Subject: [PATCH 15/17] Improve docstrings --- src/algorithm/neighbour/distances.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/algorithm/neighbour/distances.rs b/src/algorithm/neighbour/distances.rs index d24c96d1..04b163a1 100644 --- a/src/algorithm/neighbour/distances.rs +++ b/src/algorithm/neighbour/distances.rs @@ -2,15 +2,15 @@ //! Dissimilarities for vector-vector distance //! //! Representing distances as pairwise dissimilarities, so to build a -//! graph of closest neoghbour. This representation can be reused for +//! graph of closest neighbours. This representation can be reused for //! different implementations (initially used in this library for FastPair). use std::cmp::{Eq, Ordering, PartialOrd}; use crate::math::num::RealNumber; /// -/// The edge of the subgraph is defined by this structure. -/// The calling algorithm can store a list of dissimilarities as +/// The edge of the subgraph is defined by `PairwiseDistance`. +/// The calling algorithm can store a list of distsances as /// a list of these structures. /// #[derive(Debug, Clone, Copy)] From b650b4c88b94cc07bc12540b18c9e066af201ea9 Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Tue, 23 Aug 2022 16:19:37 +0100 Subject: [PATCH 16/17] Add serialization for PairwiseDistance --- src/algorithm/neighbour/distances.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/algorithm/neighbour/distances.rs b/src/algorithm/neighbour/distances.rs index 04b163a1..56a7ed63 100644 --- a/src/algorithm/neighbour/distances.rs +++ b/src/algorithm/neighbour/distances.rs @@ -6,6 +6,9 @@ //! different implementations (initially used in this library for FastPair). use std::cmp::{Eq, Ordering, PartialOrd}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + use crate::math::num::RealNumber; /// @@ -13,6 +16,7 @@ use crate::math::num::RealNumber; /// The calling algorithm can store a list of distsances as /// a list of these structures. /// +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy)] pub struct PairwiseDistance { /// index of the vector in the original `Matrix` or list From 03b28472cbdda6503b0a2737c75d163147b3a011 Mon Sep 17 00:00:00 2001 From: "Lorenzo (Mec-iS)" Date: Tue, 23 Aug 2022 16:31:57 +0100 Subject: [PATCH 17/17] Add fp_bench feature for fastpair bench --- Cargo.toml | 4 +++- src/algorithm/neighbour/fastpair.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9da75a9b..e83a0cca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ default = ["datasets"] ndarray-bindings = ["ndarray"] nalgebra-bindings = ["nalgebra"] datasets = [] +fp_bench = [] [dependencies] ndarray = { version = "0.15", optional = true } @@ -50,4 +51,5 @@ required-features = ["ndarray-bindings", "nalgebra-bindings"] [[bench]] name = "fastpair" -harness = false \ No newline at end of file +harness = false +required-features = ["fp_bench"] \ No newline at end of file diff --git a/src/algorithm/neighbour/fastpair.rs b/src/algorithm/neighbour/fastpair.rs index 36d0e548..dfc6f586 100644 --- a/src/algorithm/neighbour/fastpair.rs +++ b/src/algorithm/neighbour/fastpair.rs @@ -159,7 +159,7 @@ impl<'a, T: RealNumber, M: Matrix> FastPair<'a, T, M> { /// /// Brute force algorithm, used only for comparison and testing /// - #[allow(dead_code)] + #[cfg(feature = "fp_bench")] pub fn closest_pair_brute(&self) -> PairwiseDistance { let m = self.samples.shape().0;