use std::collections::BinaryHeap;
use std::cmp::Ordering;
pub trait Point : Sized + PartialEq {
fn distance(&self, other: &Self) -> f64;
fn move_towards(&self, other: &Self, d: f64) -> Self;
}
fn midpoint<P: Point>(a: &P, b: &P) -> P {
let d = a.distance(b);
a.move_towards(b, d / 2.0)
}
#[derive(PartialEq, PartialOrd)]
struct OrdF64(f64);
impl OrdF64 {
fn new(x: f64) -> Self {
assert!(!x.is_nan());
OrdF64(x)
}
}
impl Eq for OrdF64 {}
impl Ord for OrdF64 {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
struct Sphere<C> {
center: C,
radius: f64,
}
fn bounding_sphere<P: Point>(points: &[P]) -> Sphere<P> {
assert!(points.len() >= 2);
let a = &points
.iter()
.max_by_key(|a| OrdF64::new(points[0].distance(a)))
.unwrap();
let b = &points
.iter()
.max_by_key(|b| OrdF64::new(a.distance(b)))
.unwrap();
let mut center: P = midpoint(a, b);
let mut radius = center.distance(b).max(std::f64::EPSILON);
loop {
match points.iter().filter(|p| center.distance(p) > radius).next() {
None => break Sphere { center, radius },
Some(p) => {
let c_to_p = center.distance(&p);
let d = c_to_p - radius;
center = center.move_towards(p, d);
radius = radius * 1.01;
},
}
}
}
fn partition<P: Point, V>(mut points: Vec<P>, mut values: Vec<V>) -> ((Vec<P>, Vec<V>), (Vec<P>, Vec<V>)) {
assert!(points.len() >= 2);
assert_eq!(points.len(), values.len());
let a_i = points
.iter()
.enumerate()
.max_by_key(|(_,a)| OrdF64::new(points[0].distance(a)))
.unwrap().0;
let b_i = points
.iter()
.enumerate()
.max_by_key(|(_,b)| OrdF64::new(points[a_i].distance(b)))
.unwrap().0;
let (a_i, b_i) = (a_i.max(b_i), a_i.min(b_i));
let (mut aps, mut avs) = (vec![points.swap_remove(a_i)], vec![values.swap_remove(a_i)]);
let (mut bps, mut bvs) = (vec![points.swap_remove(b_i)], vec![values.swap_remove(b_i)]);
for (p,v) in points.into_iter().zip(values) {
if aps[0].distance(&p) < bps[0].distance(&p) {
aps.push(p);
avs.push(v);
} else {
bps.push(p);
bvs.push(v);
}
}
((aps, avs), (bps, bvs))
}
enum BallTreeInner<P, V> {
Empty,
Leaf(P, Vec<V>),
Branch(Sphere<P>, Box<BallTreeInner<P, V>>, Box<BallTreeInner<P, V>>),
}
impl <P: Point, V> BallTreeInner<P, V> {
fn new(mut points: Vec<P>, values: Vec<V>) -> Self {
assert_eq!(
points.len(), values.len(),
"Given two vectors of differing lengths. points: {}, values: {}",
points.len(),
values.len()
);
if points.is_empty() {
BallTreeInner::Empty
} else if points.iter().all(|p| p == &points[0]) {
BallTreeInner::Leaf(points.pop().unwrap(), values)
} else {
let sphere = bounding_sphere(&points);
let ((aps, avs), (bps, bvs)) = partition(points, values);
let (a_tree, b_tree) = (BallTreeInner::new(aps, avs), BallTreeInner::new(bps, bvs));
BallTreeInner::Branch(sphere, Box::new(a_tree), Box::new(b_tree))
}
}
fn distance(&self, p: &P) -> f64 {
match self {
BallTreeInner::Empty => std::f64::INFINITY,
BallTreeInner::Leaf(p0, _) => p.distance(p0),
BallTreeInner::Branch(sphere, _, _) => p.distance(&sphere.center) - sphere.radius
}
}
}
struct Item<T>(f64, T);
impl <T> PartialEq for Item<T> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl <T> Eq for Item<T> {}
impl <T> PartialOrd for Item<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.0.partial_cmp(&other.0).map(|ordering| ordering.reverse())
}
}
impl <T> Ord for Item<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
struct Iter<'a, P, V> {
point: &'a P,
balls: BinaryHeap<Item<&'a BallTreeInner<P, V>>>,
i: usize,
max_radius: f64,
}
impl <'a, P: 'a + Point, V: 'a> Iterator for Iter<'a, P, V> {
type Item = (&'a P, f64, &'a V);
fn next(&mut self) -> Option<Self::Item> {
while self.balls.len() > 0 {
if let Item(d, BallTreeInner::Leaf(p, vs)) = self.balls.peek().unwrap() {
if self.i < vs.len() && *d <= self.max_radius {
self.i += 1;
return Some((p, *d, &vs[self.i - 1]));
}
}
self.i = 0;
if let Item(_, BallTreeInner::Branch(_, a, b)) = self.balls.pop().unwrap() {
let d_a = a.distance(self.point);
let d_b = b.distance(self.point);
if d_a <= self.max_radius {
self.balls.push(Item(d_a, a));
}
if d_b <= self.max_radius {
self.balls.push(Item(d_b, b));
}
}
}
None
}
}
pub struct BallTree<P, V>(BallTreeInner<P, V>);
impl <P: Point, V> BallTree<P, V> {
pub fn new(points: Vec<P>, values: Vec<V>) -> Self {
BallTree(BallTreeInner::new(points, values))
}
pub fn nn<'a>(&'a self, point: &'a P) -> impl Iterator<Item = (&'a P, f64, &'a V)> + 'a {
self.nn_within(point, std::f64::INFINITY)
}
pub fn nn_within<'a>(&'a self, point: &'a P, max_radius: f64) -> impl Iterator<Item = (&'a P, f64, &'a V)> + 'a {
Iter {
point,
balls: vec![Item(self.0.distance(point), &self.0)].into(),
i: 0,
max_radius,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::{Rng, SeedableRng};
use rand_chacha::ChaChaRng;
#[derive(Debug, Clone, Copy, PartialEq)]
struct TestPoint(f64);
impl Point for TestPoint {
fn distance(&self, other: &Self) -> f64 {
(self.0 - other.0).abs()
}
fn move_towards(&self, other: &Self, d: f64) -> Self {
if self.0 > other.0 {
TestPoint(self.0 - d)
} else {
TestPoint(self.0 + d)
}
}
}
#[test]
fn test() {
let mut rng: ChaChaRng = SeedableRng::from_seed([1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,]);
for _ in 0..500 {
let n = rng.gen::<usize>() % 100;
let mut points = vec![];
let mut values = vec![];
for v in 0..n {
let p = TestPoint((rng.gen::<u32>() % 100) as f64);
points.push(p);
values.push(v);
}
let tree = BallTree::new(points.clone(), values.clone());
for _ in 0..100 {
let point = TestPoint(((rng.gen::<u32>() % 200) as i32 - 50) as f64);
let mut previous_d = 0.0;
let max_radius = (rng.gen::<f64>() * 200.0).floor();
let mut expected_values = points
.iter()
.zip(&values)
.filter(|(p,_)| p.distance(&point) <= max_radius)
.map(|(_, v)| v)
.cloned()
.collect::<Vec<_>>();
let mut found_values = vec![];
for (p, d, v) in tree.nn_within(&point, max_radius) {
assert_eq!(point.distance(p), d);
assert!(d >= previous_d);
assert!(d <= max_radius);
previous_d = d;
found_values.push(*v);
}
expected_values.sort();
found_values.sort();
assert_eq!(expected_values, found_values);
}
}
}
}