use std::{cmp::Ordering, collections::BinaryHeap};
use rand::Rng;
use num_traits::Float;
const SENTINEL: u32 = u32::MAX;
#[derive(Clone, Debug)]
struct Node<T: Float> {
item: u32,
threshold: T,
left: u32,
right: u32,
}
enum Side {
Root,
Left,
Right,
}
impl<T: Float> Default for Node<T> {
fn default() -> Self {
Node {
item: 0,
threshold: T::zero(),
left: SENTINEL,
right: SENTINEL,
}
}
}
struct HeapItem<T: Float> {
index: usize,
distance: T,
}
pub(crate) struct SearchScratch<T: Float> {
heap: BinaryHeap<HeapItem<T>>,
stack: Vec<u32>,
results: Vec<HeapItem<T>>,
}
impl<T: Float> Default for SearchScratch<T> {
fn default() -> Self {
Self {
heap: BinaryHeap::new(),
stack: Vec::new(),
results: Vec::new(),
}
}
}
impl<T: Float> PartialOrd for HeapItem<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T: Float> PartialEq for HeapItem<T> {
fn eq(&self, other: &Self) -> bool {
self.distance == other.distance
}
}
impl<T: Float> Eq for HeapItem<T> {}
impl<T: Float> Ord for HeapItem<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.distance.partial_cmp(&other.distance).unwrap()
}
}
pub(crate) struct VPTree<'a, T: Float + Send + Sync, U> {
items: Vec<(usize, &'a U)>,
nodes: Vec<Node<T>>,
}
impl<'a, T: Float + Send + Sync, U> VPTree<'a, T, U> {
pub fn new<F>(items: &'a [U], metric_f: F) -> Self
where
F: Fn(&U, &U) -> T,
{
let mut tree = VPTree {
items: items.iter().enumerate().collect(),
nodes: Vec::new(),
};
tree.build(&metric_f);
tree
}
fn build<F>(&mut self, metric_f: F)
where
F: Fn(&U, &U) -> T,
{
let n = self.items.len();
if n == 0 {
return;
}
let mut rng = super::make_rng();
let mut partition: Vec<(T, (usize, &'a U))> = Vec::new();
let mut stack = vec![(0usize, n, SENTINEL, Side::Root)];
while let Some((lower, upper, parent, side)) = stack.pop() {
if lower == upper {
continue;
}
let node_index = self.nodes.len() as u32;
self.nodes.push(Node {
item: lower as u32,
..Node::default()
});
match side {
Side::Root => {}
Side::Left => self.nodes[parent as usize].left = node_index,
Side::Right => self.nodes[parent as usize].right = node_index,
}
if upper - lower > 1 {
let i = rng.random_range(lower..upper);
self.items.swap(lower, i);
let vantage = self.items[lower].1;
let median = (upper + lower) / 2;
partition.clear();
partition.extend(
self.items[lower + 1..upper]
.iter()
.map(|pair @ (_, sample)| (metric_f(vantage, sample), *pair)),
);
let k = median - lower - 1;
partition.select_nth_unstable_by(k, |(a, ..), (b, ..)| {
a.partial_cmp(b).unwrap_or(Ordering::Equal)
});
for (slot, &(_, pair)) in self.items[lower + 1..upper]
.iter_mut()
.zip(partition.iter())
{
*slot = pair;
}
self.nodes[node_index as usize].threshold = metric_f(vantage, self.items[median].1);
stack.push((lower + 1, median, node_index, Side::Left));
stack.push((median, upper, node_index, Side::Right));
}
}
}
fn look_up<F>(
&self,
tau: &mut T, target: &U,
k: usize,
heap: &mut BinaryHeap<HeapItem<T>>,
stack: &mut Vec<u32>,
metric_f: F,
) where
F: Fn(&U, &U) -> T,
{
if self.nodes.is_empty() {
return;
}
stack.clear();
stack.push(0);
while let Some(node_index) = stack.pop() {
let node = &self.nodes[node_index as usize];
let (original_position, point) = self.items[node.item as usize];
let distance: T = metric_f(point, target);
if distance < *tau {
if heap.len() == k {
heap.pop();
}
heap.push(HeapItem {
index: original_position,
distance,
});
if heap.len() == k {
*tau = heap.peek().unwrap().distance;
}
}
if node.left == SENTINEL && node.right == SENTINEL {
continue;
}
if distance < node.threshold {
if distance + *tau >= node.threshold && node.right != SENTINEL {
stack.push(node.right);
}
if distance - *tau <= node.threshold && node.left != SENTINEL {
stack.push(node.left);
}
} else {
if distance - *tau <= node.threshold && node.left != SENTINEL {
stack.push(node.left);
}
if distance + *tau >= node.threshold && node.right != SENTINEL {
stack.push(node.right);
}
}
}
}
pub fn search<F>(
&self,
target: &U,
target_index: usize,
k: usize,
out: (&mut [u32], &mut [T]),
scratch: &mut SearchScratch<T>,
metric_f: F,
) where
F: Fn(&U, &U) -> T,
{
let (neighbors_indices, distances) = out;
debug_assert_eq!(neighbors_indices.len(), distances.len());
scratch.heap.clear();
self.look_up(
&mut T::max_value(),
target,
k,
&mut scratch.heap,
&mut scratch.stack,
metric_f,
);
scratch.results.clear();
while let Some(item) = scratch.heap.pop() {
scratch.results.push(item);
}
neighbors_indices
.iter_mut()
.zip(distances.iter_mut())
.zip(
scratch
.results
.iter()
.rev()
.filter(|result| target_index != result.index),
)
.for_each(|((idx, d), result)| {
*idx = result.index as u32;
*d = result.distance;
});
}
}