pub mod chebyshev;
pub mod coords;
pub mod cos;
pub mod distance;
pub mod euclid;
pub mod exhaustive;
pub mod hamming;
pub mod kd;
pub mod lp;
pub mod taxi;
pub mod vp;
mod util;
pub use coords::Coordinates;
pub use distance::{Distance, Metric, Proximity};
pub use euclid::{euclidean_distance, Euclidean, EuclideanDistance};
use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::convert::TryInto;
#[derive(Clone, Copy, Debug)]
pub struct Neighbor<V, D> {
pub item: V,
pub distance: D,
}
impl<V, D> Neighbor<V, D> {
pub fn new(item: V, distance: D) -> Self {
Self { item, distance }
}
}
impl<V1, D1, V2, D2> PartialEq<Neighbor<V2, D2>> for Neighbor<V1, D1>
where
V1: PartialEq<V2>,
D1: PartialEq<D2>,
{
fn eq(&self, other: &Neighbor<V2, D2>) -> bool {
self.item == other.item && self.distance == other.distance
}
}
pub trait Neighborhood<K: Proximity<V>, V> {
fn target(&self) -> K;
fn contains<D>(&self, distance: D) -> bool
where
D: PartialOrd<K::Distance>;
fn consider(&mut self, item: V) -> K::Distance;
}
#[derive(Debug)]
struct Candidate<V, D>(Neighbor<V, D>);
impl<V, D: Distance> Candidate<V, D> {
fn new<K>(target: K, item: V) -> Self
where
K: Proximity<V, Distance = D>,
{
let distance = target.distance(&item);
Self(Neighbor::new(item, distance))
}
}
impl<V, D: PartialOrd> PartialOrd for Candidate<V, D> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.0.distance.partial_cmp(&other.0.distance)
}
}
impl<V, D: PartialOrd> Ord for Candidate<V, D> {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other)
.expect("Unordered distances found during nearest neighbor search")
}
}
impl<V, D: PartialEq> PartialEq for Candidate<V, D> {
fn eq(&self, other: &Self) -> bool {
self.0.distance == other.0.distance
}
}
impl<V, D: PartialEq> Eq for Candidate<V, D> {}
#[derive(Debug)]
struct SingletonNeighborhood<K, V, D> {
target: K,
threshold: Option<D>,
candidate: Option<Candidate<V, D>>,
}
impl<K, V, D> SingletonNeighborhood<K, V, D> {
fn new(target: K, threshold: Option<D>) -> Self {
Self {
target,
threshold,
candidate: None,
}
}
fn into_option(self) -> Option<Neighbor<V, D>> {
self.candidate.map(|c| c.0)
}
}
impl<K, V> Neighborhood<K, V> for SingletonNeighborhood<K, V, K::Distance>
where
K: Copy + Proximity<V>,
{
fn target(&self) -> K {
self.target
}
fn contains<D>(&self, distance: D) -> bool
where
D: PartialOrd<K::Distance>,
{
self.threshold.map_or(true, |t| distance <= t)
}
fn consider(&mut self, item: V) -> K::Distance {
let candidate = Candidate::new(self.target, item);
let distance = candidate.0.distance;
if self.contains(distance) {
self.threshold = Some(distance);
self.candidate = Some(candidate);
}
distance
}
}
#[derive(Debug)]
struct HeapNeighborhood<K, V, D> {
target: K,
k: usize,
threshold: Option<D>,
heap: BinaryHeap<Candidate<V, D>>,
}
impl<K, V, D: PartialOrd> HeapNeighborhood<K, V, D> {
fn new(target: K, k: usize, threshold: Option<D>) -> Self {
Self {
target,
k,
threshold,
heap: BinaryHeap::new(),
}
}
fn into_vec(self) -> Vec<Neighbor<V, D>> {
self.heap
.into_sorted_vec()
.into_iter()
.map(|c| c.0)
.collect()
}
}
impl<K, V> Neighborhood<K, V> for HeapNeighborhood<K, V, K::Distance>
where
K: Copy + Proximity<V>,
{
fn target(&self) -> K {
self.target
}
fn contains<D>(&self, distance: D) -> bool
where
D: PartialOrd<K::Distance>,
{
self.k > 0 && self.threshold.map_or(true, |t| distance <= t)
}
fn consider(&mut self, item: V) -> K::Distance {
let candidate = Candidate::new(self.target, item);
let distance = candidate.0.distance;
if self.contains(distance) {
let heap = &mut self.heap;
if heap.len() == self.k {
heap.pop();
}
heap.push(candidate);
if heap.len() == self.k {
self.threshold = heap.peek().map(|c| c.0.distance)
}
}
distance
}
}
pub trait NearestNeighbors<K: Proximity<V>, V = K> {
fn nearest(&self, target: &K) -> Option<Neighbor<&V, K::Distance>> {
self.search(SingletonNeighborhood::new(target, None))
.into_option()
}
fn nearest_within<D>(&self, target: &K, threshold: D) -> Option<Neighbor<&V, K::Distance>>
where
D: TryInto<K::Distance>,
{
if let Ok(distance) = threshold.try_into() {
self.search(SingletonNeighborhood::new(target, Some(distance)))
.into_option()
} else {
None
}
}
fn k_nearest(&self, target: &K, k: usize) -> Vec<Neighbor<&V, K::Distance>> {
self.search(HeapNeighborhood::new(target, k, None))
.into_vec()
}
fn k_nearest_within<D>(&self, target: &K, k: usize, threshold: D) -> Vec<Neighbor<&V, K::Distance>>
where
D: TryInto<K::Distance>,
{
if let Ok(distance) = threshold.try_into() {
self.search(HeapNeighborhood::new(target, k, Some(distance)))
.into_vec()
} else {
Vec::new()
}
}
fn search<'k, 'v, N>(&'v self, neighborhood: N) -> N
where
K: 'k,
V: 'v,
N: Neighborhood<&'k K, &'v V>;
}
pub trait ExactNeighbors<K: Proximity<V>, V = K>: NearestNeighbors<K, V> {}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::exhaustive::ExhaustiveSearch;
use rand::prelude::*;
use std::iter::FromIterator;
type Point = Euclidean<[f32; 3]>;
pub fn test_nearest_neighbors<T, F>(from_iter: F)
where
T: NearestNeighbors<Point>,
F: Fn(Vec<Point>) -> T,
{
test_empty(&from_iter);
test_pythagorean(&from_iter);
test_random_points(&from_iter);
}
fn test_empty<T, F>(from_iter: &F)
where
T: NearestNeighbors<Point>,
F: Fn(Vec<Point>) -> T,
{
let points = Vec::new();
let index = from_iter(points);
let target = Euclidean([0.0, 0.0, 0.0]);
assert_eq!(index.nearest(&target), None);
assert_eq!(index.nearest_within(&target, 1.0), None);
assert!(index.k_nearest(&target, 0).is_empty());
assert!(index.k_nearest(&target, 3).is_empty());
assert!(index.k_nearest_within(&target, 0, 1.0).is_empty());
assert!(index.k_nearest_within(&target, 3, 1.0).is_empty());
}
fn test_pythagorean<T, F>(from_iter: &F)
where
T: NearestNeighbors<Point>,
F: Fn(Vec<Point>) -> T,
{
let points = vec![
Euclidean([3.0, 4.0, 0.0]),
Euclidean([5.0, 0.0, 12.0]),
Euclidean([0.0, 8.0, 15.0]),
Euclidean([1.0, 2.0, 2.0]),
Euclidean([2.0, 3.0, 6.0]),
Euclidean([4.0, 4.0, 7.0]),
];
let index = from_iter(points);
let target = Euclidean([0.0, 0.0, 0.0]);
assert_eq!(
index.nearest(&target).expect("No nearest neighbor found"),
Neighbor::new(&Euclidean([1.0, 2.0, 2.0]), 3.0)
);
assert_eq!(index.nearest_within(&target, 2.0), None);
assert_eq!(
index.nearest_within(&target, 4.0).expect("No nearest neighbor found within 4.0"),
Neighbor::new(&Euclidean([1.0, 2.0, 2.0]), 3.0)
);
assert!(index.k_nearest(&target, 0).is_empty());
assert_eq!(
index.k_nearest(&target, 3),
vec![
Neighbor::new(&Euclidean([1.0, 2.0, 2.0]), 3.0),
Neighbor::new(&Euclidean([3.0, 4.0, 0.0]), 5.0),
Neighbor::new(&Euclidean([2.0, 3.0, 6.0]), 7.0),
]
);
assert!(index.k_nearest(&target, 0).is_empty());
assert_eq!(
index.k_nearest_within(&target, 3, 6.0),
vec![
Neighbor::new(&Euclidean([1.0, 2.0, 2.0]), 3.0),
Neighbor::new(&Euclidean([3.0, 4.0, 0.0]), 5.0),
]
);
assert_eq!(
index.k_nearest_within(&target, 3, 8.0),
vec![
Neighbor::new(&Euclidean([1.0, 2.0, 2.0]), 3.0),
Neighbor::new(&Euclidean([3.0, 4.0, 0.0]), 5.0),
Neighbor::new(&Euclidean([2.0, 3.0, 6.0]), 7.0),
]
);
}
fn test_random_points<T, F>(from_iter: &F)
where
T: NearestNeighbors<Point>,
F: Fn(Vec<Point>) -> T,
{
let mut points = Vec::new();
for _ in 0..256 {
points.push(Euclidean([random(), random(), random()]));
}
let index = from_iter(points.clone());
let eindex = ExhaustiveSearch::from_iter(points.clone());
let target = Euclidean([random(), random(), random()]);
assert_eq!(
index.k_nearest(&target, 3),
eindex.k_nearest(&target, 3),
"target: {:?}, points: {:#?}",
target,
points,
);
}
}