1use alloc::vec::Vec;
11use core::marker::PhantomData;
12
13use crate::bounds::Bounds;
14use crate::indexable::Indexable;
15use crate::nearest_bound::NearestBound;
16use crate::nearest_iter::NearestIter;
17use crate::node::Node;
18use crate::predicate::{Predicate, QueryPredicate};
19use crate::query_iter::{QueryIter, QueryWithIter};
20use crate::search_frontier::SearchFrontier;
21use crate::split::{AsymmetricRStarSplit, SplitParameters};
22use crate::values::Values;
23
24#[derive(Debug)]
52pub struct Rtree<T: Indexable, Params: SplitParameters = AsymmetricRStarSplit<6, 2, 12, 4, 4, 4>> {
53 root: Node<T>,
54 len: usize,
55 height: usize,
56 _params: PhantomData<Params>,
57}
58
59impl<T: Indexable, Params: SplitParameters> Default for Rtree<T, Params> {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65impl<T: Indexable + Clone, Params: SplitParameters> Clone for Rtree<T, Params> {
66 fn clone(&self) -> Self {
67 Self {
68 root: self.root.clone(),
69 len: self.len,
70 height: self.height,
71 _params: PhantomData,
72 }
73 }
74}
75
76impl<T: Indexable, Params: SplitParameters> Rtree<T, Params> {
77 #[must_use]
79 pub fn new() -> Self {
80 Self {
81 root: Node::Leaf(Vec::new()),
82 len: 0,
83 height: 1,
84 _params: PhantomData,
85 }
86 }
87
88 #[must_use]
90 pub fn len(&self) -> usize {
91 self.len
92 }
93
94 #[must_use]
96 pub fn is_empty(&self) -> bool {
97 self.len == 0
98 }
99
100 #[must_use]
104 pub fn height(&self) -> usize {
105 self.height
106 }
107
108 #[must_use]
111 #[inline]
112 pub fn bounds(&self) -> Option<Bounds> {
113 self.root.bounds()
114 }
115
116 #[must_use]
118 #[inline]
119 pub fn iter(&self) -> Values<'_, T> {
120 Values::new(
121 &self.root,
122 self.len,
123 self.height,
124 Params::BRANCH_MAX.max(Params::BULK_BRANCH_MAX),
125 )
126 }
127
128 pub fn clear(&mut self) {
130 self.root = Node::Leaf(Vec::new());
131 self.len = 0;
132 self.height = 1;
133 }
134
135 pub fn insert(&mut self, value: T) {
141 self.len += 1;
142 if let Some((b1, n1, b2, n2)) = insert_into::<T, Params>(&mut self.root, value) {
143 self.root = Node::Branch(Vec::from([(b1, n1), (b2, n2)]));
147 self.height += 1;
148 }
149 }
150
151 #[must_use]
157 pub fn query(&self, predicate: Predicate) -> Vec<&T> {
158 self.query_iter(predicate).collect()
159 }
160
161 #[must_use]
165 pub fn query_with<P: QueryPredicate<T>>(&self, predicate: P) -> Vec<&T> {
166 self.query_iter_with(predicate).collect()
167 }
168
169 #[must_use]
191 pub fn query_iter(&self, predicate: Predicate) -> QueryIter<'_, T> {
192 QueryIter::new(
193 &self.root,
194 predicate,
195 self.height(),
196 Params::BRANCH_MAX.max(Params::BULK_BRANCH_MAX),
197 )
198 }
199
200 #[must_use]
206 pub fn query_iter_with<P: QueryPredicate<T>>(&self, predicate: P) -> QueryWithIter<'_, T, P> {
207 QueryWithIter::new(
208 &self.root,
209 predicate,
210 self.height(),
211 Params::BRANCH_MAX.max(Params::BULK_BRANCH_MAX),
212 )
213 }
214
215 #[must_use]
255 pub fn nearest_iter(&self, query: [f64; 2]) -> NearestIter<'_, T> {
256 NearestIter::new(&self.root, query)
257 }
258
259 #[must_use]
269 pub fn nearest_iter_with_inline_capacities<
270 const NODE_INLINE_CAPACITY: usize,
271 const VALUE_INLINE_CAPACITY: usize,
272 >(
273 &self,
274 query: [f64; 2],
275 ) -> NearestIter<'_, T, NODE_INLINE_CAPACITY, VALUE_INLINE_CAPACITY> {
276 NearestIter::new(&self.root, query)
277 }
278
279 #[must_use]
302 pub fn nearest(&self, query: [f64; 2], k: usize) -> Vec<&T> {
303 if k == 0 || self.len == 0 {
304 return Vec::new();
305 }
306 let capacity = k.min(self.len);
307 let mut ranks = NearestBound::new(k, capacity);
308 let mut frontier: SearchFrontier<FrontierNode<'_, T>> = SearchFrontier::new();
309 frontier.push(FrontierNode {
310 dist: 0.0,
311 node: &self.root,
312 });
313 while let Some(FrontierNode { dist, node }) = frontier.pop() {
314 if dist.total_cmp(&ranks.bound()).is_ge() {
315 break;
316 }
317 match node {
318 Node::Leaf(values) => admit_nearest_values(values.iter(), query, &mut ranks),
319 Node::Branch(children) => {
320 let bound = ranks.bound();
321 for (b, child) in children {
322 let dist = b.comparable_min_distance_to(query);
323 if dist.total_cmp(&bound).is_lt() {
324 frontier.push(FrontierNode { dist, node: child });
325 }
326 }
327 }
328 }
329 }
330 ranks.into_values()
331 }
332
333 #[must_use]
339 pub fn count(&self, value: &T) -> usize
340 where
341 T: PartialEq,
342 {
343 self.query_iter(Predicate::Intersects(value.bounds()))
344 .filter(|candidate| *candidate == value)
345 .count()
346 }
347
348 #[must_use]
356 pub fn remove(&mut self, value: &T) -> usize
357 where
358 T: PartialEq,
359 {
360 let mut orphans = Vec::new();
361 if !remove_from::<T, Params>(&mut self.root, value, &value.bounds(), &mut orphans) {
362 return 0;
363 }
364
365 self.len -= 1;
366 self.collapse_root();
367 for orphan in orphans {
368 self.insert_without_len(orphan);
369 }
370 debug_assert_eq!(self.root.value_count(), self.len);
371 debug_assert_eq!(self.root.height(), self.height);
372 1
373 }
374
375 #[must_use]
381 pub fn remove_all<'a, I>(&mut self, values: I) -> usize
382 where
383 T: PartialEq + 'a,
384 I: IntoIterator<Item = &'a T>,
385 {
386 values.into_iter().map(|value| self.remove(value)).sum()
387 }
388
389 fn insert_without_len(&mut self, value: T) {
390 if let Some((b1, n1, b2, n2)) = insert_into::<T, Params>(&mut self.root, value) {
391 self.root = Node::Branch(Vec::from([(b1, n1), (b2, n2)]));
392 self.height += 1;
393 }
394 }
395
396 fn collapse_root(&mut self) {
397 loop {
398 let replacement = match &mut self.root {
399 Node::Branch(children) if children.is_empty() => Some(Node::Leaf(Vec::new())),
400 Node::Branch(children) if children.len() == 1 => {
401 Some(children.pop().expect("one root child").1)
402 }
403 Node::Leaf(_) | Node::Branch(_) => None,
404 };
405 let Some(replacement) = replacement else {
406 break;
407 };
408 self.root = replacement;
409 self.height = self.root.height();
410 }
411 if self.len == 0 {
412 self.root = Node::Leaf(Vec::new());
413 self.height = 1;
414 }
415 }
416}
417
418fn remove_from<T, Params>(
419 node: &mut Node<T>,
420 value: &T,
421 value_bounds: &Bounds,
422 orphans: &mut Vec<T>,
423) -> bool
424where
425 T: Indexable + PartialEq,
426 Params: SplitParameters,
427{
428 match node {
429 Node::Leaf(values) => {
430 let Some(index) = values.iter().position(|candidate| candidate == value) else {
431 return false;
432 };
433 values.swap_remove(index);
434 true
435 }
436 Node::Branch(children) => {
437 for index in 0..children.len() {
438 if !children[index].0.contains(value_bounds) {
439 continue;
440 }
441 if !remove_from::<T, Params>(&mut children[index].1, value, value_bounds, orphans) {
442 continue;
443 }
444
445 let underfull = match &children[index].1 {
446 Node::Leaf(values) => values.len() < Params::LEAF_MIN,
447 Node::Branch(grandchildren) => grandchildren.len() < Params::BRANCH_MIN,
448 };
449 if underfull {
450 let (_, removed) = children.swap_remove(index);
451 append_values(removed, orphans);
452 } else {
453 children[index].0 = children[index]
454 .1
455 .bounds()
456 .expect("a retained child is non-empty");
457 }
458 return true;
459 }
460 false
461 }
462 }
463}
464
465fn append_values<T>(node: Node<T>, values: &mut Vec<T>) {
466 match node {
467 Node::Leaf(mut leaf) => values.append(&mut leaf),
468 Node::Branch(children) => {
469 for (_, child) in children {
470 append_values(child, values);
471 }
472 }
473 }
474}
475
476fn admit_nearest_values<'a, T: Indexable>(
477 values: impl Iterator<Item = &'a T>,
478 query: [f64; 2],
479 ranks: &mut NearestBound<&'a T>,
480) {
481 for value in values {
482 let dist = value.bounds().comparable_min_distance_to(query);
483 if dist.total_cmp(&ranks.bound()).is_lt() {
484 ranks.admit_better(dist, value);
485 }
486 }
487}
488
489impl<T: Indexable, Params: SplitParameters> FromIterator<T> for Rtree<T, Params> {
490 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
496 let values: Vec<T> = iter.into_iter().collect();
497 let len = values.len();
498 assert!(
499 Params::BULK_LEAF_MAX > 0,
500 "bulk leaf capacity must be non-zero"
501 );
502 assert!(
503 Params::BULK_BRANCH_MAX >= 2,
504 "bulk branch capacity must be at least two"
505 );
506 if len <= Params::BULK_LEAF_MAX {
507 return Self {
508 root: Node::Leaf(values),
509 len,
510 height: 1,
511 _params: PhantomData,
512 };
513 }
514 let (root, height) = str_pack::<T, Params>(values);
515 Self {
516 root,
517 len,
518 height,
519 _params: PhantomData,
520 }
521 }
522}
523
524impl<T: Indexable, Params: SplitParameters> Extend<T> for Rtree<T, Params> {
525 fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
526 for value in iter {
527 self.insert(value);
528 }
529 }
530}
531
532impl<'a, T: Indexable, Params: SplitParameters> IntoIterator for &'a Rtree<T, Params> {
533 type Item = &'a T;
534 type IntoIter = Values<'a, T>;
535
536 fn into_iter(self) -> Self::IntoIter {
537 self.iter()
538 }
539}
540
541struct FrontierNode<'a, T> {
544 dist: f64,
545 node: &'a Node<T>,
546}
547
548impl<T> PartialEq for FrontierNode<'_, T> {
549 fn eq(&self, other: &Self) -> bool {
550 self.dist.total_cmp(&other.dist).is_eq()
551 }
552}
553
554impl<T> Eq for FrontierNode<'_, T> {}
555
556impl<T> PartialOrd for FrontierNode<'_, T> {
557 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
558 Some(self.cmp(other))
559 }
560}
561
562impl<T> Ord for FrontierNode<'_, T> {
563 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
566 other.dist.total_cmp(&self.dist)
567 }
568}
569
570type Split<T> = (Bounds, Node<T>, Bounds, Node<T>);
573
574fn insert_into<T: Indexable, Params: SplitParameters>(
575 node: &mut Node<T>,
576 value: T,
577) -> Option<Split<T>> {
578 match node {
579 Node::Leaf(leaf) => {
580 leaf.push(value);
581 if leaf.len() > Params::LEAF_MAX {
582 Some(split_leaf::<T, Params>(leaf))
583 } else {
584 None
585 }
586 }
587 Node::Branch(children) => {
588 let vb = value.bounds();
590 let choice = choose_child(children, &vb);
591 let (_, child) = &mut children[choice];
592 let split = insert_into::<T, Params>(child, value);
593
594 if let Some((b1, n1, b2, n2)) = split {
595 children[choice] = (b1, n1);
596 children.push((b2, n2));
597 if children.len() > Params::BRANCH_MAX {
598 return Some(split_branch::<T, Params>(children));
599 }
600 } else {
601 children[choice].0 = children[choice].0.union(&vb);
605 }
606 None
607 }
608 }
609}
610
611#[allow(
614 clippy::float_cmp,
615 reason = "exact tie-break between equal enlargements, as Boost's choose_next_node does"
616)]
617fn choose_child<T>(children: &[(Bounds, Node<T>)], vb: &Bounds) -> usize {
618 let mut best = 0;
619 let mut best_enl = f64::INFINITY;
620 let mut best_area = f64::INFINITY;
621 for (i, (b, _)) in children.iter().enumerate() {
622 let enl = b.enlargement(vb);
623 let area = b.area();
624 if enl < best_enl || (enl == best_enl && area < best_area) {
625 best = i;
626 best_enl = enl;
627 best_area = area;
628 }
629 }
630 best
631}
632
633fn split_leaf<T: Indexable, Params: SplitParameters>(leaf: &mut Vec<T>) -> Split<T> {
635 let taken = core::mem::take(leaf);
636 let boxes: Vec<Bounds> = taken.iter().map(Indexable::bounds).collect();
637 let (g1, g2) = Params::split_leaf(&boxes);
638
639 let mut in_g1 = alloc::vec![false; taken.len()];
642 for &i in &g1 {
643 in_g1[i] = true;
644 }
645 let mut v1: Vec<T> = Vec::new();
646 let mut v2: Vec<T> = Vec::new();
647 for (i, v) in taken.into_iter().enumerate() {
648 if in_g1[i] {
649 v1.push(v);
650 } else {
651 v2.push(v);
652 }
653 }
654 debug_assert_eq!(v1.len(), g1.len());
655 debug_assert_eq!(v2.len(), g2.len());
656
657 let b1 = v1
658 .iter()
659 .map(Indexable::bounds)
660 .reduce(|a, b| a.union(&b))
661 .expect("split group is non-empty by MIN invariant");
662 let b2 = v2
663 .iter()
664 .map(Indexable::bounds)
665 .reduce(|a, b| a.union(&b))
666 .expect("split group is non-empty by MIN invariant");
667 (b1, Node::Leaf(v1), b2, Node::Leaf(v2))
668}
669
670fn split_branch<T: Indexable, Params: SplitParameters>(
672 children: &mut Vec<(Bounds, Node<T>)>,
673) -> Split<T> {
674 let taken = core::mem::take(children);
675 let boxes: Vec<Bounds> = taken.iter().map(|(b, _)| *b).collect();
676 let (g1, _g2) = Params::split_branch(&boxes);
677
678 let mut in_g1 = alloc::vec![false; taken.len()];
679 for &i in &g1 {
680 in_g1[i] = true;
681 }
682 let mut c1: Vec<(Bounds, Node<T>)> = Vec::new();
683 let mut c2: Vec<(Bounds, Node<T>)> = Vec::new();
684 for (i, c) in taken.into_iter().enumerate() {
685 if in_g1[i] {
686 c1.push(c);
687 } else {
688 c2.push(c);
689 }
690 }
691
692 let b1 = c1
693 .iter()
694 .map(|(b, _)| *b)
695 .reduce(|a, b| a.union(&b))
696 .expect("split group is non-empty by MIN invariant");
697 let b2 = c2
698 .iter()
699 .map(|(b, _)| *b)
700 .reduce(|a, b| a.union(&b))
701 .expect("split group is non-empty by MIN invariant");
702 (b1, Node::Branch(c1), b2, Node::Branch(c2))
703}
704
705fn str_pack<T: Indexable, Params: SplitParameters>(values: Vec<T>) -> (Node<T>, usize) {
707 let mut values: Vec<Option<T>> = values.into_iter().map(Some).collect();
710 let keyed: Vec<([f64; 2], usize)> = values
711 .iter()
712 .enumerate()
713 .map(|(index, value)| {
714 (
715 value
716 .as_ref()
717 .expect("packed value is present")
718 .bounds()
719 .center(),
720 index,
721 )
722 })
723 .collect();
724 let mut height = 1;
725 let mut capacity = Params::BULK_LEAF_MAX;
726 while capacity < keyed.len() {
727 capacity = capacity.saturating_mul(Params::BULK_BRANCH_MAX);
728 height += 1;
729 }
730 (
731 str_pack_height::<T, Params>(keyed, height, &mut values).1,
732 height,
733 )
734}
735
736fn str_pack_height<T: Indexable, Params: SplitParameters>(
740 mut keyed: Vec<([f64; 2], usize)>,
741 height: usize,
742 values: &mut [Option<T>],
743) -> (Bounds, Node<T>) {
744 if height == 1 {
745 debug_assert!(keyed.len() <= Params::BULK_LEAF_MAX);
746 let leaf_values: Vec<T> = keyed
747 .into_iter()
748 .map(|(_, index)| values[index].take().expect("packed value is present"))
749 .collect();
750 let bounds = leaf_values
751 .iter()
752 .map(Indexable::bounds)
753 .reduce(|a, b| a.union(&b))
754 .expect("packed leaf is non-empty");
755 return (bounds, Node::Leaf(leaf_values));
756 }
757
758 let child_capacity = packed_subtree_capacity::<Params>(height - 1);
759 let child_count = keyed.len().div_ceil(child_capacity);
760 debug_assert!(child_count <= Params::BULK_BRANCH_MAX);
761 let column_count = isqrt_ceil(child_count).max(1);
762
763 let mut children = Vec::with_capacity(child_count);
764 let mut remaining_children = child_count;
765 for column in 0..column_count {
766 let children_in_column =
767 child_count / column_count + usize::from(column < child_count % column_count);
768 let base = keyed.len() / remaining_children;
771 let extra = keyed.len() % remaining_children;
772 let take = base * children_in_column + extra.min(children_in_column);
773 let mut strip = take_lowest_by_axis(&mut keyed, take, 0);
774
775 let mut remaining_in_column = children_in_column;
776 while remaining_in_column != 0 {
777 let take = strip.len().div_ceil(remaining_in_column);
778 let tile = take_lowest_by_axis(&mut strip, take, 1);
779 children.push(str_pack_height::<T, Params>(tile, height - 1, values));
780 remaining_in_column -= 1;
781 }
782 remaining_children -= children_in_column;
783 }
784
785 let bounds = children
786 .iter()
787 .map(|(bounds, _)| *bounds)
788 .reduce(|a, b| a.union(&b))
789 .expect("packed branch is non-empty");
790 (bounds, Node::Branch(children))
791}
792
793fn take_lowest_by_axis(
794 values: &mut Vec<([f64; 2], usize)>,
795 take: usize,
796 axis: usize,
797) -> Vec<([f64; 2], usize)> {
798 if take == values.len() {
799 return core::mem::take(values);
800 }
801 values.select_nth_unstable_by(take, |(a, _), (b, _)| a[axis].total_cmp(&b[axis]));
802 let tail = values.split_off(take);
803 core::mem::replace(values, tail)
804}
805
806fn packed_subtree_capacity<Params: SplitParameters>(height: usize) -> usize {
807 let mut capacity = Params::BULK_LEAF_MAX;
808 for _ in 1..height {
809 capacity = capacity.saturating_mul(Params::BULK_BRANCH_MAX);
810 }
811 capacity
812}
813
814fn isqrt_ceil(n: usize) -> usize {
817 if n < 2 {
818 return n;
819 }
820 let mut x = n;
821 let mut y = x.div_ceil(2);
822 while y < x {
823 x = y;
824 y = usize::midpoint(x, n / x);
825 }
826 if x * x == n { x } else { x + 1 }
828}
829
830#[cfg(test)]
831#[allow(clippy::float_cmp, reason = "exact integer-valued point coordinates")]
832mod tests {
833 use super::{FrontierNode, Rtree, isqrt_ceil};
834 use crate::bounds::{Bounds, union_all};
835 use crate::indexable::Indexable;
836 use crate::nearest_bound::{NearestBound, NearestBoundMetrics};
837 use crate::node::Node;
838 use crate::predicate::Predicate;
839 use crate::search_frontier::SearchFrontier;
840 use crate::split::{
841 AsymmetricQuadratic, AsymmetricRStarSplit, Linear, Quadratic, SplitParameters,
842 };
843 use geometry_cs::Cartesian;
844 use geometry_model::Point2D;
845 use geometry_trait::Point as _;
846
847 type P = Point2D<f64, Cartesian>;
848 trait LeafProbe<T> {
849 fn values(&self) -> &[T];
850 }
851
852 impl<T> LeafProbe<T> for Vec<T> {
853 fn values(&self) -> &[T] {
854 self
855 }
856 }
857
858 struct Lcg {
859 state: u64,
860 }
861
862 impl Lcg {
863 fn new() -> Self {
864 Self {
865 state: 0x9E37_79B9_7F4A_7C15,
866 }
867 }
868
869 #[allow(
870 clippy::cast_precision_loss,
871 reason = "state >> 11 keeps 53 bits, exact in f64"
872 )]
873 fn next_f64(&mut self) -> f64 {
874 self.state = self
875 .state
876 .wrapping_mul(6_364_136_223_846_793_005)
877 .wrapping_add(1_442_695_040_888_963_407);
878 (self.state >> 11) as f64 / (1u64 << 53) as f64
879 }
880 }
881
882 #[derive(Debug, Default)]
883 struct BoundedSearchMetrics {
884 frontier_pushes: usize,
885 frontier_pops: usize,
886 frontier_high_water: usize,
887 terminated_by_bound: usize,
888 branch_expansions: usize,
889 child_distance_evaluations: usize,
890 child_order_comparisons: usize,
891 child_pushes: usize,
892 child_pruned: usize,
893 leaf_expansions: usize,
894 reversed_leaf_scans: usize,
895 leaf_group_bound_evaluations: usize,
896 leaf_group_order_comparisons: usize,
897 leaf_groups_scanned: usize,
898 leaf_groups_pruned: usize,
899 value_distance_evaluations: usize,
900 value_bound_passes: usize,
901 value_bound_rejections: usize,
902 rank: NearestBoundMetrics,
903 }
904
905 impl BoundedSearchMetrics {
906 fn add(&mut self, other: &Self) {
907 self.frontier_pushes += other.frontier_pushes;
908 self.frontier_pops += other.frontier_pops;
909 self.frontier_high_water = self.frontier_high_water.max(other.frontier_high_water);
910 self.terminated_by_bound += other.terminated_by_bound;
911 self.branch_expansions += other.branch_expansions;
912 self.child_distance_evaluations += other.child_distance_evaluations;
913 self.child_order_comparisons += other.child_order_comparisons;
914 self.child_pushes += other.child_pushes;
915 self.child_pruned += other.child_pruned;
916 self.leaf_expansions += other.leaf_expansions;
917 self.reversed_leaf_scans += other.reversed_leaf_scans;
918 self.leaf_group_bound_evaluations += other.leaf_group_bound_evaluations;
919 self.leaf_group_order_comparisons += other.leaf_group_order_comparisons;
920 self.leaf_groups_scanned += other.leaf_groups_scanned;
921 self.leaf_groups_pruned += other.leaf_groups_pruned;
922 self.value_distance_evaluations += other.value_distance_evaluations;
923 self.value_bound_passes += other.value_bound_passes;
924 self.value_bound_rejections += other.value_bound_rejections;
925 self.rank.calls += other.rank.calls;
926 self.rank.partition_comparisons += other.rank.partition_comparisons;
927 self.rank.admissions += other.rank.admissions;
928 self.rank.replacements += other.rank.replacements;
929 self.rank.shifted_ranks += other.rank.shifted_ranks;
930 }
931 }
932
933 fn nearest_with_metrics<T: Indexable, Params: SplitParameters>(
934 tree: &Rtree<T, Params>,
935 query: [f64; 2],
936 k: usize,
937 nearer_y_end_first: bool,
938 leaf_group_size: usize,
939 center_out: bool,
940 leaf_bvh_terminal_size: usize,
941 ) -> (Vec<&T>, BoundedSearchMetrics) {
942 if k == 0 || tree.len == 0 {
943 return (Vec::new(), BoundedSearchMetrics::default());
944 }
945 let capacity = k.min(tree.len);
946 let mut ranks = NearestBound::new(k, capacity);
947 let mut frontier: SearchFrontier<FrontierNode<'_, T>> = SearchFrontier::new();
948 let mut metrics = BoundedSearchMetrics::default();
949 frontier.push(FrontierNode {
950 dist: 0.0,
951 node: &tree.root,
952 });
953 while let Some(FrontierNode { dist, node }) = frontier.pop() {
954 if dist.total_cmp(&ranks.bound()).is_ge() {
955 metrics.terminated_by_bound += 1;
956 break;
957 }
958 match node {
959 Node::Leaf(leaf) => {
960 let values = leaf.values();
961 metrics.leaf_expansions += 1;
962 let reverse = nearer_y_end_first
963 && values
964 .first()
965 .zip(values.last())
966 .is_some_and(|(first, last)| {
967 let first_y = first.bounds().center()[1];
968 let last_y = last.bounds().center()[1];
969 (last_y - query[1]).abs() < (first_y - query[1]).abs()
970 });
971 metrics.reversed_leaf_scans += usize::from(reverse);
972 if leaf_bvh_terminal_size != 0 {
973 record_leaf_bvh(
974 values,
975 leaf_bvh_terminal_size,
976 query,
977 &mut ranks,
978 &mut metrics,
979 );
980 } else if center_out && leaf_group_size != 0 {
981 record_center_out_leaf_groups(
982 values,
983 leaf_group_size,
984 query,
985 &mut ranks,
986 &mut metrics,
987 );
988 } else if center_out {
989 metrics.value_distance_evaluations += values.len();
990 record_center_out_leaf(values, query, &mut ranks, &mut metrics);
991 } else if leaf_group_size != 0 {
992 if reverse {
996 for group in values.chunks(leaf_group_size).rev() {
997 record_leaf_group(group, true, query, &mut ranks, &mut metrics);
998 }
999 } else {
1000 for group in values.chunks(leaf_group_size) {
1001 record_leaf_group(group, false, query, &mut ranks, &mut metrics);
1002 }
1003 }
1004 } else if reverse {
1005 metrics.value_distance_evaluations += values.len();
1006 for value in values.iter().rev() {
1007 record_value_candidate(value, query, &mut ranks, &mut metrics);
1008 }
1009 } else {
1010 metrics.value_distance_evaluations += values.len();
1011 for value in values {
1012 record_value_candidate(value, query, &mut ranks, &mut metrics);
1013 }
1014 }
1015 }
1016 Node::Branch(children) => {
1017 metrics.branch_expansions += 1;
1018 metrics.child_distance_evaluations += children.len();
1019 for (bounds, child) in children {
1020 let dist = bounds.comparable_min_distance_to(query);
1021 if dist.total_cmp(&ranks.bound()).is_lt() {
1022 metrics.child_pushes += 1;
1023 frontier.push(FrontierNode { dist, node: child });
1024 } else {
1025 metrics.child_pruned += 1;
1026 }
1027 }
1028 }
1029 }
1030 }
1031 let frontier_metrics = frontier.metrics();
1032 metrics.frontier_pushes = frontier_metrics.pushes;
1033 metrics.frontier_pops = frontier_metrics.pops;
1034 metrics.frontier_high_water = frontier_metrics.high_water;
1035 metrics.rank = ranks.metrics();
1036 (ranks.into_values(), metrics)
1037 }
1038
1039 fn nearest_distance_ordered_groups_with_metrics<T: Indexable, Params: SplitParameters>(
1040 tree: &Rtree<T, Params>,
1041 query: [f64; 2],
1042 k: usize,
1043 group_size: usize,
1044 ) -> (Vec<&T>, BoundedSearchMetrics) {
1045 if k == 0 || tree.len == 0 {
1046 return (Vec::new(), BoundedSearchMetrics::default());
1047 }
1048 let mut ranks = NearestBound::new(k, k.min(tree.len));
1049 let mut frontier: SearchFrontier<FrontierNode<'_, T>> = SearchFrontier::new();
1050 let mut metrics = BoundedSearchMetrics::default();
1051 frontier.push(FrontierNode {
1052 dist: 0.0,
1053 node: &tree.root,
1054 });
1055 while let Some(FrontierNode { dist, node }) = frontier.pop() {
1056 if dist.total_cmp(&ranks.bound()).is_ge() {
1057 metrics.terminated_by_bound += 1;
1058 break;
1059 }
1060 match node {
1061 Node::Leaf(leaf) => {
1062 metrics.leaf_expansions += 1;
1063 record_distance_ordered_leaf_groups(
1064 leaf.values(),
1065 group_size,
1066 query,
1067 &mut ranks,
1068 &mut metrics,
1069 );
1070 }
1071 Node::Branch(children) => {
1072 metrics.branch_expansions += 1;
1073 metrics.child_distance_evaluations += children.len();
1074 for (bounds, child) in children {
1075 let dist = bounds.comparable_min_distance_to(query);
1076 if dist.total_cmp(&ranks.bound()).is_lt() {
1077 metrics.child_pushes += 1;
1078 frontier.push(FrontierNode { dist, node: child });
1079 } else {
1080 metrics.child_pruned += 1;
1081 }
1082 }
1083 }
1084 }
1085 }
1086 let frontier_metrics = frontier.metrics();
1087 metrics.frontier_pushes = frontier_metrics.pushes;
1088 metrics.frontier_pops = frontier_metrics.pops;
1089 metrics.frontier_high_water = frontier_metrics.high_water;
1090 metrics.rank = ranks.metrics();
1091 (ranks.into_values(), metrics)
1092 }
1093
1094 fn record_distance_ordered_leaf_groups<'a, T: Indexable>(
1095 values: &'a [T],
1096 group_size: usize,
1097 query: [f64; 2],
1098 ranks: &mut NearestBound<&'a T>,
1099 metrics: &mut BoundedSearchMetrics,
1100 ) {
1101 let mut ordered: Vec<(f64, usize)> = values
1102 .chunks(group_size)
1103 .enumerate()
1104 .map(|(index, group)| {
1105 let bounds = group
1106 .iter()
1107 .map(Indexable::bounds)
1108 .reduce(|a, b| a.union(&b))
1109 .expect("chunks are non-empty");
1110 metrics.leaf_group_bound_evaluations += 1;
1111 (bounds.comparable_min_distance_to(query), index)
1112 })
1113 .collect();
1114 ordered.sort_unstable_by(|a, b| {
1115 metrics.leaf_group_order_comparisons += 1;
1116 a.0.total_cmp(&b.0)
1117 });
1118 for (group_dist, group_index) in ordered {
1119 if group_dist.total_cmp(&ranks.bound()).is_ge() {
1120 metrics.leaf_groups_pruned += 1;
1121 continue;
1122 }
1123 metrics.leaf_groups_scanned += 1;
1124 let start = group_index * group_size;
1125 let group = &values[start..(start + group_size).min(values.len())];
1126 metrics.value_distance_evaluations += group.len();
1127 let reverse = group
1128 .first()
1129 .zip(group.last())
1130 .is_some_and(|(first, last)| {
1131 let first_y = first.bounds().center()[1];
1132 let last_y = last.bounds().center()[1];
1133 (last_y - query[1]).abs() < (first_y - query[1]).abs()
1134 });
1135 if reverse {
1136 for value in group.iter().rev() {
1137 record_value_candidate(value, query, ranks, metrics);
1138 }
1139 } else {
1140 for value in group {
1141 record_value_candidate(value, query, ranks, metrics);
1142 }
1143 }
1144 }
1145 }
1146
1147 fn nearest_depth_first_with_metrics<T: Indexable, Params: SplitParameters>(
1148 tree: &Rtree<T, Params>,
1149 query: [f64; 2],
1150 k: usize,
1151 ) -> (Vec<&T>, BoundedSearchMetrics) {
1152 if k == 0 || tree.len == 0 {
1153 return (Vec::new(), BoundedSearchMetrics::default());
1154 }
1155 let mut ranks = NearestBound::new(k, k.min(tree.len));
1156 let mut metrics = BoundedSearchMetrics::default();
1157 record_depth_first_node(&tree.root, query, true, &mut ranks, &mut metrics);
1158 metrics.rank = ranks.metrics();
1159 (ranks.into_values(), metrics)
1160 }
1161
1162 fn record_depth_first_node<'a, T: Indexable>(
1163 node: &'a Node<T>,
1164 query: [f64; 2],
1165 nearer_y_end_first: bool,
1166 ranks: &mut NearestBound<&'a T>,
1167 metrics: &mut BoundedSearchMetrics,
1168 ) {
1169 match node {
1170 Node::Leaf(leaf) => {
1171 let values = leaf.values();
1172 metrics.leaf_expansions += 1;
1173 metrics.value_distance_evaluations += values.len();
1174 let reverse = nearer_y_end_first
1175 && values
1176 .first()
1177 .zip(values.last())
1178 .is_some_and(|(first, last)| {
1179 let first_y = first.bounds().center()[1];
1180 let last_y = last.bounds().center()[1];
1181 (last_y - query[1]).abs() < (first_y - query[1]).abs()
1182 });
1183 metrics.reversed_leaf_scans += usize::from(reverse);
1184 if reverse {
1185 for value in values.iter().rev() {
1186 record_value_candidate(value, query, ranks, metrics);
1187 }
1188 } else {
1189 for value in values {
1190 record_value_candidate(value, query, ranks, metrics);
1191 }
1192 }
1193 }
1194 Node::Branch(children) => {
1195 metrics.branch_expansions += 1;
1196 metrics.child_distance_evaluations += children.len();
1197 let mut ordered: Vec<FrontierNode<'_, T>> = children
1198 .iter()
1199 .map(|(bounds, child)| FrontierNode {
1200 dist: bounds.comparable_min_distance_to(query),
1201 node: child,
1202 })
1203 .collect();
1204 ordered.sort_unstable_by(|a, b| {
1205 metrics.child_order_comparisons += 1;
1206 a.dist.total_cmp(&b.dist)
1207 });
1208 for (index, FrontierNode { dist, node }) in ordered.iter().enumerate() {
1209 if dist.total_cmp(&ranks.bound()).is_ge() {
1210 metrics.child_pruned += ordered.len() - index;
1211 break;
1212 }
1213 metrics.child_pushes += 1;
1214 record_depth_first_node(node, query, nearer_y_end_first, ranks, metrics);
1215 }
1216 }
1217 }
1218 }
1219
1220 fn record_center_out_leaf_groups<'a, T: Indexable>(
1221 values: &'a [T],
1222 group_size: usize,
1223 query: [f64; 2],
1224 ranks: &mut NearestBound<&'a T>,
1225 metrics: &mut BoundedSearchMetrics,
1226 ) {
1227 let group_bounds: Vec<Bounds> = values
1228 .chunks(group_size)
1229 .map(|group| {
1230 group
1231 .iter()
1232 .map(Indexable::bounds)
1233 .reduce(|a, b| a.union(&b))
1234 .expect("chunks are non-empty")
1235 })
1236 .collect();
1237 let mut upper = group_bounds.partition_point(|bounds| bounds.center()[1] < query[1]);
1238 let mut lower = upper;
1239 while lower != 0 || upper != group_bounds.len() {
1240 let take_lower = if lower == 0 {
1241 false
1242 } else if upper == group_bounds.len() {
1243 true
1244 } else {
1245 let lower_y = group_bounds[lower - 1].center()[1];
1246 let upper_y = group_bounds[upper].center()[1];
1247 (query[1] - lower_y).abs() <= (upper_y - query[1]).abs()
1248 };
1249 let group_index = if take_lower {
1250 lower -= 1;
1251 lower
1252 } else {
1253 let group_index = upper;
1254 upper += 1;
1255 group_index
1256 };
1257 metrics.leaf_group_bound_evaluations += 1;
1258 let group_dist = group_bounds[group_index].comparable_min_distance_to(query);
1259 if group_dist.total_cmp(&ranks.bound()).is_ge() {
1260 metrics.leaf_groups_pruned += 1;
1261 continue;
1262 }
1263 metrics.leaf_groups_scanned += 1;
1264 let start = group_index * group_size;
1265 let end = (start + group_size).min(values.len());
1266 let group = &values[start..end];
1267 metrics.value_distance_evaluations += group.len();
1268 record_center_out_leaf(group, query, ranks, metrics);
1269 }
1270 }
1271
1272 fn record_center_out_leaf<'a, T: Indexable>(
1273 values: &'a [T],
1274 query: [f64; 2],
1275 ranks: &mut NearestBound<&'a T>,
1276 metrics: &mut BoundedSearchMetrics,
1277 ) {
1278 let mut upper = values.partition_point(|value| value.bounds().center()[1] < query[1]);
1279 let mut lower = upper;
1280 while lower != 0 || upper != values.len() {
1281 let take_lower = if lower == 0 {
1282 false
1283 } else if upper == values.len() {
1284 true
1285 } else {
1286 let lower_y = values[lower - 1].bounds().center()[1];
1287 let upper_y = values[upper].bounds().center()[1];
1288 (query[1] - lower_y).abs() <= (upper_y - query[1]).abs()
1289 };
1290 let value = if take_lower {
1291 lower -= 1;
1292 &values[lower]
1293 } else {
1294 let value = &values[upper];
1295 upper += 1;
1296 value
1297 };
1298 record_value_candidate(value, query, ranks, metrics);
1299 }
1300 }
1301
1302 fn record_leaf_bvh<'a, T: Indexable>(
1303 values: &'a [T],
1304 terminal_size: usize,
1305 query: [f64; 2],
1306 ranks: &mut NearestBound<&'a T>,
1307 metrics: &mut BoundedSearchMetrics,
1308 ) {
1309 if values.len() <= terminal_size {
1310 metrics.leaf_groups_scanned += 1;
1311 metrics.value_distance_evaluations += values.len();
1312 let reverse = values
1313 .first()
1314 .zip(values.last())
1315 .is_some_and(|(first, last)| {
1316 let first_y = first.bounds().center()[1];
1317 let last_y = last.bounds().center()[1];
1318 (last_y - query[1]).abs() < (first_y - query[1]).abs()
1319 });
1320 if reverse {
1321 for value in values.iter().rev() {
1322 record_value_candidate(value, query, ranks, metrics);
1323 }
1324 } else {
1325 for value in values {
1326 record_value_candidate(value, query, ranks, metrics);
1327 }
1328 }
1329 return;
1330 }
1331
1332 let middle = values.len() / 2;
1333 let (lower, upper) = values.split_at(middle);
1334 let child_bounds = [lower, upper].map(|child| {
1335 child
1336 .iter()
1337 .map(Indexable::bounds)
1338 .reduce(|a, b| a.union(&b))
1339 .expect("BVH children are non-empty")
1340 });
1341 metrics.leaf_group_bound_evaluations += 2;
1342 metrics.leaf_group_order_comparisons += 1;
1343 let distances = child_bounds.map(|bounds| bounds.comparable_min_distance_to(query));
1344 let order = if distances[0].total_cmp(&distances[1]).is_le() {
1345 [0, 1]
1346 } else {
1347 [1, 0]
1348 };
1349 let children = [lower, upper];
1350 for index in order {
1351 if distances[index].total_cmp(&ranks.bound()).is_lt() {
1352 record_leaf_bvh(children[index], terminal_size, query, ranks, metrics);
1353 } else {
1354 metrics.leaf_groups_pruned += 1;
1355 }
1356 }
1357 }
1358
1359 fn record_leaf_group<'a, T: Indexable>(
1360 group: &'a [T],
1361 reverse: bool,
1362 query: [f64; 2],
1363 ranks: &mut NearestBound<&'a T>,
1364 metrics: &mut BoundedSearchMetrics,
1365 ) {
1366 metrics.leaf_group_bound_evaluations += 1;
1367 let bounds = group
1368 .iter()
1369 .map(Indexable::bounds)
1370 .reduce(|a, b| a.union(&b))
1371 .expect("chunks are non-empty");
1372 let group_dist = bounds.comparable_min_distance_to(query);
1373 if group_dist.total_cmp(&ranks.bound()).is_ge() {
1374 metrics.leaf_groups_pruned += 1;
1375 return;
1376 }
1377 metrics.leaf_groups_scanned += 1;
1378 metrics.value_distance_evaluations += group.len();
1379 if reverse {
1380 for value in group.iter().rev() {
1381 record_value_candidate(value, query, ranks, metrics);
1382 }
1383 } else {
1384 for value in group {
1385 record_value_candidate(value, query, ranks, metrics);
1386 }
1387 }
1388 }
1389
1390 fn record_value_candidate<'a, T: Indexable>(
1391 value: &'a T,
1392 query: [f64; 2],
1393 ranks: &mut NearestBound<&'a T>,
1394 metrics: &mut BoundedSearchMetrics,
1395 ) {
1396 let dist = value.bounds().comparable_min_distance_to(query);
1397 if dist.total_cmp(&ranks.bound()).is_lt() {
1398 metrics.value_bound_passes += 1;
1399 ranks.admit_better(dist, value);
1400 } else {
1401 metrics.value_bound_rejections += 1;
1402 }
1403 }
1404
1405 #[test]
1406 fn empty_tree() {
1407 let t: Rtree<P> = Rtree::new();
1408 assert!(t.is_empty());
1409 assert_eq!(t.len(), 0);
1410 }
1411
1412 #[test]
1413 fn private_metric_helpers_handle_empty_queries_and_small_integer_roots() {
1414 assert_eq!(isqrt_ceil(0), 0);
1415 assert_eq!(isqrt_ceil(1), 1);
1416 assert_eq!(isqrt_ceil(2), 2);
1417 assert_eq!(isqrt_ceil(4), 2);
1418
1419 let ordered_values: Vec<P> = (0..12).map(|x| P::new(f64::from(x), 0.0)).collect();
1420 let mut ranks = NearestBound::new(1, 1);
1421 let mut metrics = BoundedSearchMetrics::default();
1422 record_distance_ordered_leaf_groups(
1423 &ordered_values,
1424 2,
1425 [0.0, 0.0],
1426 &mut ranks,
1427 &mut metrics,
1428 );
1429 assert!(metrics.leaf_group_order_comparisons > 0);
1430 assert!(metrics.leaf_groups_pruned > 0);
1431
1432 let tree = Rtree::<P>::new();
1433 assert!(
1434 nearest_with_metrics(&tree, [0.0, 0.0], 0, false, 8, false, 8)
1435 .0
1436 .is_empty()
1437 );
1438 assert!(
1439 nearest_distance_ordered_groups_with_metrics(&tree, [0.0, 0.0], 0, 8)
1440 .0
1441 .is_empty()
1442 );
1443 assert!(
1444 nearest_depth_first_with_metrics(&tree, [0.0, 0.0], 0)
1445 .0
1446 .is_empty()
1447 );
1448 }
1449
1450 #[test]
1452 fn default_tree_is_empty() {
1453 let t: Rtree<P> = Rtree::default();
1454 assert!(t.is_empty());
1455 assert_eq!(t.len(), 0);
1456 }
1457
1458 #[test]
1461 fn frontier_node_eq_compares_distance() {
1462 let mut t: Rtree<P> = Rtree::new();
1463 t.insert(P::new(0.0, 0.0));
1464 let a = FrontierNode {
1465 dist: 1.5,
1466 node: &t.root,
1467 };
1468 let b = FrontierNode {
1469 dist: 1.5,
1470 node: &t.root,
1471 };
1472 let c = FrontierNode {
1473 dist: 2.5,
1474 node: &t.root,
1475 };
1476 assert!(a == b);
1477 assert!(a != c);
1478 }
1479
1480 #[test]
1481 fn insert_many_points_keeps_len() {
1482 let mut t: Rtree<P> = Rtree::new();
1483 for i in 0..1000 {
1484 let x = f64::from(i % 100);
1485 let y = f64::from(i / 100);
1486 t.insert(P::new(x, y));
1487 }
1488 assert_eq!(t.len(), 1000);
1489 assert!(
1490 t.height() >= 2,
1491 "1000 points should build a multi-level tree"
1492 );
1493 }
1494
1495 #[test]
1496 fn query_intersects_finds_the_point() {
1497 let mut t: Rtree<P> = Rtree::new();
1498 for i in 0..200 {
1499 t.insert(P::new(f64::from(i), 0.0));
1500 }
1501 let hits = t.query(Predicate::Intersects(Bounds::new([9.5, -1.0], [10.5, 1.0])));
1502 assert_eq!(hits.len(), 1);
1503 }
1504
1505 #[test]
1506 fn query_within_a_window() {
1507 let mut t: Rtree<P> = Rtree::new();
1508 for x in 0..10 {
1509 for y in 0..10 {
1510 t.insert(P::new(f64::from(x), f64::from(y)));
1511 }
1512 }
1513 let hits = t.query(Predicate::CoveredBy(Bounds::new([2.0, 2.0], [5.0, 5.0])));
1515 assert_eq!(hits.len(), 16);
1516 }
1517
1518 #[test]
1519 fn nearest_returns_closest_first() {
1520 let mut t: Rtree<P> = Rtree::new();
1521 for i in 0..100 {
1522 t.insert(P::new(f64::from(i), 0.0));
1523 }
1524 let near = t.nearest([10.2, 0.0], 3);
1525 assert_eq!(near.len(), 3);
1526 let mut xs: Vec<f64> = near.iter().map(|p| p.get::<0>()).collect();
1528 xs.sort_by(|a, b| a.partial_cmp(b).unwrap());
1529 assert_eq!(xs, [9.0, 10.0, 11.0]);
1530 }
1531
1532 #[test]
1533 fn linear_split_also_works() {
1534 let mut t: Rtree<P, Linear<8, 3>> = Rtree::new();
1535 for i in 0..500 {
1536 t.insert(P::new(f64::from(i % 25), f64::from(i / 25)));
1537 }
1538 assert_eq!(t.len(), 500);
1539 let hits = t.query(Predicate::Intersects(Bounds::new([0.0, 0.0], [3.0, 3.0])));
1540 assert!(!hits.is_empty());
1541 }
1542
1543 fn uniform_points(n: usize) -> Vec<P> {
1544 let mut lcg = Lcg::new();
1545 (0..n)
1546 .map(|_| {
1547 let x = lcg.next_f64() * 50_000.0;
1548 let y = lcg.next_f64() * 50_000.0;
1549 P::new(x, y)
1550 })
1551 .collect()
1552 }
1553
1554 fn clustered_points(n: usize) -> Vec<P> {
1555 const CLUSTER_COUNT: usize = 16;
1556 const CLUSTER_RADIUS: f64 = 100.0;
1557 const FIELD: f64 = 50_000.0;
1558
1559 let mut lcg = Lcg::new();
1560 let centers: Vec<[f64; 2]> = (0..CLUSTER_COUNT)
1561 .map(|_| [lcg.next_f64() * FIELD, lcg.next_f64() * FIELD])
1562 .collect();
1563 (0..n)
1564 .map(|i| {
1565 let center = centers[i % CLUSTER_COUNT];
1566 P::new(
1567 center[0] + lcg.next_f64() * 2.0 * CLUSTER_RADIUS - CLUSTER_RADIUS,
1568 center[1] + lcg.next_f64() * 2.0 * CLUSTER_RADIUS - CLUSTER_RADIUS,
1569 )
1570 })
1571 .collect()
1572 }
1573
1574 fn profile_queries(q: usize) -> Vec<[f64; 2]> {
1575 let mut lcg = Lcg::new();
1576 (0..q)
1577 .map(|_| {
1578 let x = lcg.next_f64() * 50_000.0;
1579 lcg.next_f64();
1580 let y = lcg.next_f64() * 50_000.0;
1581 [x, y]
1582 })
1583 .collect()
1584 }
1585
1586 fn report_bounded_metrics(
1587 construction: &str,
1588 distribution: &str,
1589 leaf_order: &str,
1590 expected_results: usize,
1591 total: &BoundedSearchMetrics,
1592 ) {
1593 eprintln!(
1594 "[rtree-bounded-shape] construction={construction} distribution={distribution} leaf_order={leaf_order} expected_results={expected_results} frontier_pushes={} frontier_pops={} frontier_high_water={} terminated_by_bound={} branch_expansions={} child_distance_evaluations={} child_order_comparisons={} child_pushes={} child_pruned={} leaf_expansions={} reversed_leaf_scans={} leaf_group_bound_evaluations={} leaf_group_order_comparisons={} leaf_groups_scanned={} leaf_groups_pruned={} value_distance_evaluations={} value_bound_passes={} value_bound_rejections={} rank_calls={} rank_partition_comparisons={} rank_admissions={} rank_replacements={} rank_shifted_ranks={}",
1595 total.frontier_pushes,
1596 total.frontier_pops,
1597 total.frontier_high_water,
1598 total.terminated_by_bound,
1599 total.branch_expansions,
1600 total.child_distance_evaluations,
1601 total.child_order_comparisons,
1602 total.child_pushes,
1603 total.child_pruned,
1604 total.leaf_expansions,
1605 total.reversed_leaf_scans,
1606 total.leaf_group_bound_evaluations,
1607 total.leaf_group_order_comparisons,
1608 total.leaf_groups_scanned,
1609 total.leaf_groups_pruned,
1610 total.value_distance_evaluations,
1611 total.value_bound_passes,
1612 total.value_bound_rejections,
1613 total.rank.calls,
1614 total.rank.partition_comparisons,
1615 total.rank.admissions,
1616 total.rank.replacements,
1617 total.rank.shifted_ranks,
1618 );
1619 }
1620
1621 #[test]
1622 fn records_bounded_search_shape() {
1623 const N: usize = 50_000;
1624 const Q: usize = 100;
1625 const K: usize = 8;
1626
1627 for (construction, distribution, points) in [
1628 ("bulk", "uniform", uniform_points(N)),
1629 ("bulk", "clustered", clustered_points(N)),
1630 ("inserted", "uniform", uniform_points(N)),
1631 ("inserted", "clustered", clustered_points(N)),
1632 ] {
1633 let tree: Rtree<P> = if construction == "bulk" {
1634 points.into_iter().collect()
1635 } else {
1636 let mut tree = Rtree::new();
1637 for point in points {
1638 tree.insert(point);
1639 }
1640 tree
1641 };
1642 let mut forward_total = BoundedSearchMetrics::default();
1643 let mut nearer_y_total = BoundedSearchMetrics::default();
1644 for query in profile_queries(Q) {
1645 let expected = tree.nearest(query, K);
1646 let (forward, metrics) = nearest_with_metrics(&tree, query, K, false, 0, false, 0);
1647 assert_eq!(forward, expected);
1648 forward_total.add(&metrics);
1649 let (nearer_y, metrics) = nearest_with_metrics(&tree, query, K, true, 0, false, 0);
1650 assert_eq!(nearer_y, expected);
1651 nearer_y_total.add(&metrics);
1652 }
1653 report_bounded_metrics(construction, distribution, "forward", Q * K, &forward_total);
1654 report_bounded_metrics(
1655 construction,
1656 distribution,
1657 "nearer-y-end",
1658 Q * K,
1659 &nearer_y_total,
1660 );
1661 }
1662 }
1663
1664 fn record_inserted_parameter_shape<Params: SplitParameters>(
1665 parameters: &str,
1666 distribution: &str,
1667 points: &[P],
1668 ) {
1669 const Q: usize = 100;
1670 const K: usize = 8;
1671
1672 let tree = insert_built::<Params>(points);
1673 let mut total = BoundedSearchMetrics::default();
1674 for query in profile_queries(Q) {
1675 let (_, metrics) = nearest_with_metrics(&tree, query, K, false, 0, false, 0);
1676 total.add(&metrics);
1677 }
1678 report_bounded_metrics(parameters, distribution, "forward", Q * K, &total);
1679 }
1680
1681 fn record_bulk_parameter_shape<Params: SplitParameters>(
1682 parameters: &str,
1683 distribution: &str,
1684 points: &[P],
1685 ) {
1686 const Q: usize = 100;
1687 const K: usize = 8;
1688
1689 let tree: Rtree<P, Params> = points.iter().copied().collect();
1690 let mut total = BoundedSearchMetrics::default();
1691 for query in profile_queries(Q) {
1692 let expected = tree.nearest(query, K);
1693 let (observed, metrics) = nearest_with_metrics(&tree, query, K, true, 0, false, 0);
1694 assert_eq!(observed, expected);
1695 total.add(&metrics);
1696 }
1697 report_bounded_metrics(parameters, distribution, "nearer-y-end", Q * K, &total);
1698 }
1699
1700 fn record_bulk_group_shape(group_size: usize, distribution: &str, points: &[P]) {
1701 const Q: usize = 100;
1702 const K: usize = 8;
1703
1704 let tree: Rtree<P> = points.iter().copied().collect();
1705 let mut total = BoundedSearchMetrics::default();
1706 for query in profile_queries(Q) {
1707 let expected = tree.nearest(query, K);
1708 let (observed, metrics) =
1709 nearest_with_metrics(&tree, query, K, true, group_size, false, 0);
1710 assert_eq!(observed, expected);
1711 total.add(&metrics);
1712 }
1713 report_bounded_metrics(
1714 &alloc::format!("bulk-group{group_size}"),
1715 distribution,
1716 "nearer-y-end",
1717 Q * K,
1718 &total,
1719 );
1720 }
1721
1722 fn record_bulk_center_out_shape(distribution: &str, points: &[P]) {
1723 const Q: usize = 100;
1724 const K: usize = 8;
1725
1726 let tree: Rtree<P> = points.iter().copied().collect();
1727 let mut total = BoundedSearchMetrics::default();
1728 for query in profile_queries(Q) {
1729 let expected = tree.nearest(query, K);
1730 let (observed, metrics) = nearest_with_metrics(&tree, query, K, false, 0, true, 0);
1731 assert_eq!(observed, expected);
1732 total.add(&metrics);
1733 }
1734 report_bounded_metrics("bulk-center-out", distribution, "center-out", Q * K, &total);
1735 }
1736
1737 fn record_bulk_center_out_group_shape(group_size: usize, distribution: &str, points: &[P]) {
1738 const Q: usize = 100;
1739 const K: usize = 8;
1740
1741 let tree: Rtree<P> = points.iter().copied().collect();
1742 let mut total = BoundedSearchMetrics::default();
1743 for query in profile_queries(Q) {
1744 let expected = tree.nearest(query, K);
1745 let (observed, metrics) =
1746 nearest_with_metrics(&tree, query, K, false, group_size, true, 0);
1747 assert_eq!(observed, expected);
1748 total.add(&metrics);
1749 }
1750 report_bounded_metrics(
1751 &alloc::format!("bulk-center-group{group_size}"),
1752 distribution,
1753 "center-out-groups",
1754 Q * K,
1755 &total,
1756 );
1757 }
1758
1759 fn record_bulk_depth_first_shape(distribution: &str, points: &[P]) {
1760 const Q: usize = 100;
1761 const K: usize = 8;
1762
1763 let tree: Rtree<P> = points.iter().copied().collect();
1764 let mut total = BoundedSearchMetrics::default();
1765 for query in profile_queries(Q) {
1766 let expected = tree.nearest(query, K);
1767 let (observed, metrics) = nearest_depth_first_with_metrics(&tree, query, K);
1768 assert_eq!(observed, expected);
1769 total.add(&metrics);
1770 }
1771 report_bounded_metrics(
1772 "bulk-depth-first",
1773 distribution,
1774 "nearer-y-end",
1775 Q * K,
1776 &total,
1777 );
1778 }
1779
1780 fn record_bulk_distance_group_shape(group_size: usize, distribution: &str, points: &[P]) {
1781 const Q: usize = 100;
1782 const K: usize = 8;
1783
1784 let tree: Rtree<P> = points.iter().copied().collect();
1785 let mut total = BoundedSearchMetrics::default();
1786 for query in profile_queries(Q) {
1787 let expected = tree.nearest(query, K);
1788 let (observed, metrics) =
1789 nearest_distance_ordered_groups_with_metrics(&tree, query, K, group_size);
1790 assert_eq!(observed, expected);
1791 total.add(&metrics);
1792 }
1793 report_bounded_metrics(
1794 &alloc::format!("bulk-distance-group{group_size}"),
1795 distribution,
1796 "distance-ordered-groups",
1797 Q * K,
1798 &total,
1799 );
1800 }
1801
1802 fn record_bulk_leaf_bvh_shape(terminal_size: usize, distribution: &str, points: &[P]) {
1803 const Q: usize = 100;
1804 const K: usize = 8;
1805
1806 let tree: Rtree<P> = points.iter().copied().collect();
1807 let mut total = BoundedSearchMetrics::default();
1808 for query in profile_queries(Q) {
1809 let expected = tree.nearest(query, K);
1810 let (observed, metrics) =
1811 nearest_with_metrics(&tree, query, K, false, 0, false, terminal_size);
1812 assert_eq!(observed, expected);
1813 total.add(&metrics);
1814 }
1815 report_bounded_metrics(
1816 &alloc::format!("bulk-leaf-bvh{terminal_size}"),
1817 distribution,
1818 "distance-ordered-bvh",
1819 Q * K,
1820 &total,
1821 );
1822 }
1823
1824 #[test]
1825 fn records_bulk_leaf_bvh_shape() {
1826 const N: usize = 50_000;
1827
1828 for (distribution, points) in [
1829 ("uniform", uniform_points(N)),
1830 ("clustered", clustered_points(N)),
1831 ] {
1832 for terminal_size in [2, 4, 8] {
1833 record_bulk_leaf_bvh_shape(terminal_size, distribution, &points);
1834 }
1835 }
1836 }
1837
1838 #[test]
1839 fn records_bulk_bounded_distance_group_shape() {
1840 const N: usize = 50_000;
1841
1842 for (distribution, points) in [
1843 ("uniform", uniform_points(N)),
1844 ("clustered", clustered_points(N)),
1845 ] {
1846 for group_size in [4, 8] {
1847 record_bulk_distance_group_shape(group_size, distribution, &points);
1848 }
1849 }
1850 }
1851
1852 #[test]
1853 fn records_bulk_bounded_depth_first_shape() {
1854 const N: usize = 50_000;
1855
1856 for (distribution, points) in [
1857 ("uniform", uniform_points(N)),
1858 ("clustered", clustered_points(N)),
1859 ] {
1860 record_bulk_depth_first_shape(distribution, &points);
1861 }
1862 }
1863
1864 #[test]
1865 fn records_bulk_bounded_center_out_group_shape() {
1866 const N: usize = 50_000;
1867
1868 for (distribution, points) in [
1869 ("uniform", uniform_points(N)),
1870 ("clustered", clustered_points(N)),
1871 ] {
1872 for group_size in [2, 4, 8] {
1873 record_bulk_center_out_group_shape(group_size, distribution, &points);
1874 }
1875 }
1876 }
1877
1878 #[test]
1879 fn records_bulk_bounded_center_out_shape() {
1880 const N: usize = 50_000;
1881
1882 for (distribution, points) in [
1883 ("uniform", uniform_points(N)),
1884 ("clustered", clustered_points(N)),
1885 ] {
1886 record_bulk_center_out_shape(distribution, &points);
1887 }
1888 }
1889
1890 #[test]
1891 fn records_bulk_bounded_group_shape() {
1892 const N: usize = 50_000;
1893
1894 for (distribution, points) in [
1895 ("uniform", uniform_points(N)),
1896 ("clustered", clustered_points(N)),
1897 ] {
1898 for group_size in [2, 4, 8, 16] {
1899 record_bulk_group_shape(group_size, distribution, &points);
1900 }
1901 }
1902 }
1903
1904 #[test]
1905 fn records_bulk_bounded_parameter_shape() {
1906 const N: usize = 50_000;
1907
1908 for (distribution, points) in [
1909 ("uniform", uniform_points(N)),
1910 ("clustered", clustered_points(N)),
1911 ] {
1912 record_bulk_parameter_shape::<AsymmetricRStarSplit<4, 2, 4, 2>>(
1913 "bulk-b4-l4",
1914 distribution,
1915 &points,
1916 );
1917 record_bulk_parameter_shape::<AsymmetricRStarSplit<6, 2, 4, 2>>(
1918 "bulk-b6-l4",
1919 distribution,
1920 &points,
1921 );
1922 record_bulk_parameter_shape::<AsymmetricRStarSplit<8, 3, 4, 2>>(
1923 "bulk-b8-l4",
1924 distribution,
1925 &points,
1926 );
1927 record_bulk_parameter_shape::<AsymmetricRStarSplit<12, 4, 4, 2>>(
1928 "bulk-b12-l4",
1929 distribution,
1930 &points,
1931 );
1932 record_bulk_parameter_shape::<AsymmetricRStarSplit<6, 2, 6, 2>>(
1933 "bulk-b6-l6",
1934 distribution,
1935 &points,
1936 );
1937 record_bulk_parameter_shape::<AsymmetricRStarSplit<4, 2, 8, 3>>(
1938 "bulk-b4-l8",
1939 distribution,
1940 &points,
1941 );
1942 record_bulk_parameter_shape::<AsymmetricRStarSplit<6, 2, 8, 3>>(
1943 "bulk-b6-l8",
1944 distribution,
1945 &points,
1946 );
1947 record_bulk_parameter_shape::<AsymmetricRStarSplit<8, 3, 8, 3>>(
1948 "bulk-b8-l8",
1949 distribution,
1950 &points,
1951 );
1952 record_bulk_parameter_shape::<AsymmetricRStarSplit<8, 3, 12, 4>>(
1953 "bulk-b8-l12",
1954 distribution,
1955 &points,
1956 );
1957 record_bulk_parameter_shape::<AsymmetricRStarSplit<8, 3, 16, 4>>(
1958 "bulk-b8-l16",
1959 distribution,
1960 &points,
1961 );
1962 record_bulk_parameter_shape::<AsymmetricRStarSplit<8, 3, 24, 7>>(
1963 "bulk-b8-l24",
1964 distribution,
1965 &points,
1966 );
1967 record_bulk_parameter_shape::<AsymmetricRStarSplit<8, 3, 32, 9>>(
1968 "bulk-b8-l32",
1969 distribution,
1970 &points,
1971 );
1972 record_bulk_parameter_shape::<AsymmetricRStarSplit<12, 4, 8, 3>>(
1973 "bulk-b12-l8",
1974 distribution,
1975 &points,
1976 );
1977 record_bulk_parameter_shape::<AsymmetricRStarSplit<16, 4, 8, 3>>(
1978 "bulk-b16-l8",
1979 distribution,
1980 &points,
1981 );
1982 record_bulk_parameter_shape::<AsymmetricRStarSplit<32, 9, 8, 3>>(
1983 "bulk-b32-l8",
1984 distribution,
1985 &points,
1986 );
1987 record_bulk_parameter_shape::<AsymmetricRStarSplit<12, 4, 16, 4>>(
1988 "bulk-b12-l16",
1989 distribution,
1990 &points,
1991 );
1992 record_bulk_parameter_shape::<AsymmetricRStarSplit<16, 4, 16, 4>>(
1993 "bulk-b16-l16",
1994 distribution,
1995 &points,
1996 );
1997 record_bulk_parameter_shape::<AsymmetricRStarSplit<32, 9, 16, 4>>(
1998 "bulk-b32-l16",
1999 distribution,
2000 &points,
2001 );
2002 }
2003 }
2004
2005 #[test]
2006 #[allow(clippy::too_many_lines)]
2007 fn records_inserted_bounded_parameter_shape() {
2008 const N: usize = 50_000;
2009
2010 for (distribution, points) in [
2011 ("uniform", uniform_points(N)),
2012 ("clustered", clustered_points(N)),
2013 ] {
2014 record_inserted_parameter_shape::<AsymmetricRStarSplit<4, 2, 4, 2>>(
2015 "inserted-rstar-split-a4-4",
2016 distribution,
2017 &points,
2018 );
2019 record_inserted_parameter_shape::<AsymmetricRStarSplit<4, 2, 8, 3>>(
2020 "inserted-rstar-split-a4-8",
2021 distribution,
2022 &points,
2023 );
2024 record_inserted_parameter_shape::<AsymmetricRStarSplit<4, 2, 16, 4>>(
2025 "inserted-rstar-split-a4-16",
2026 distribution,
2027 &points,
2028 );
2029 record_inserted_parameter_shape::<AsymmetricRStarSplit<4, 2, 32, 9>>(
2030 "inserted-rstar-split-a4-32",
2031 distribution,
2032 &points,
2033 );
2034 record_inserted_parameter_shape::<AsymmetricRStarSplit<6, 2, 8, 3>>(
2035 "inserted-rstar-split-a6-8",
2036 distribution,
2037 &points,
2038 );
2039 record_inserted_parameter_shape::<AsymmetricRStarSplit<6, 2, 10, 3>>(
2040 "inserted-rstar-split-a6-10",
2041 distribution,
2042 &points,
2043 );
2044 record_inserted_parameter_shape::<AsymmetricRStarSplit<6, 2, 12, 4>>(
2045 "inserted-rstar-split-a6-12",
2046 distribution,
2047 &points,
2048 );
2049 record_inserted_parameter_shape::<AsymmetricRStarSplit<6, 2, 14, 4>>(
2050 "inserted-rstar-split-a6-14",
2051 distribution,
2052 &points,
2053 );
2054 record_inserted_parameter_shape::<AsymmetricRStarSplit<6, 2, 16, 4>>(
2055 "inserted-rstar-split-a6-16",
2056 distribution,
2057 &points,
2058 );
2059 record_inserted_parameter_shape::<AsymmetricRStarSplit<6, 2, 32, 9>>(
2060 "inserted-rstar-split-a6-32",
2061 distribution,
2062 &points,
2063 );
2064 record_inserted_parameter_shape::<AsymmetricRStarSplit<8, 3, 8, 3>>(
2065 "inserted-rstar-split-a8-8",
2066 distribution,
2067 &points,
2068 );
2069 record_inserted_parameter_shape::<AsymmetricRStarSplit<8, 3, 10, 3>>(
2070 "inserted-rstar-split-a8-10",
2071 distribution,
2072 &points,
2073 );
2074 record_inserted_parameter_shape::<AsymmetricRStarSplit<8, 3, 12, 4>>(
2075 "inserted-rstar-split-a8-12",
2076 distribution,
2077 &points,
2078 );
2079 record_inserted_parameter_shape::<AsymmetricRStarSplit<8, 3, 16, 4>>(
2080 "inserted-rstar-split-a8-16",
2081 distribution,
2082 &points,
2083 );
2084 record_inserted_parameter_shape::<AsymmetricRStarSplit<12, 4, 16, 4>>(
2085 "inserted-rstar-split-a12-16",
2086 distribution,
2087 &points,
2088 );
2089 record_inserted_parameter_shape::<AsymmetricRStarSplit<12, 4, 32, 9>>(
2090 "inserted-rstar-split-a12-32",
2091 distribution,
2092 &points,
2093 );
2094 record_inserted_parameter_shape::<Quadratic<6, 2>>(
2095 "inserted-q6-6",
2096 distribution,
2097 &points,
2098 );
2099 record_inserted_parameter_shape::<Quadratic<8, 3>>(
2100 "inserted-q8-8",
2101 distribution,
2102 &points,
2103 );
2104 record_inserted_parameter_shape::<Quadratic<16, 4>>(
2105 "inserted-q16-16",
2106 distribution,
2107 &points,
2108 );
2109 record_inserted_parameter_shape::<Quadratic<32, 9>>(
2110 "inserted-q32-32",
2111 distribution,
2112 &points,
2113 );
2114 record_inserted_parameter_shape::<AsymmetricQuadratic<8, 3, 16, 4>>(
2115 "inserted-a8-16",
2116 distribution,
2117 &points,
2118 );
2119 record_inserted_parameter_shape::<AsymmetricQuadratic<8, 3, 32, 9>>(
2120 "inserted-a8-32",
2121 distribution,
2122 &points,
2123 );
2124 record_inserted_parameter_shape::<AsymmetricQuadratic<12, 4, 32, 9>>(
2125 "inserted-a12-32",
2126 distribution,
2127 &points,
2128 );
2129 record_inserted_parameter_shape::<AsymmetricRStarSplit<8, 3, 32, 9>>(
2130 "inserted-rstar-split-a8-32",
2131 distribution,
2132 &points,
2133 );
2134 }
2135 }
2136
2137 fn insert_built<Params: SplitParameters>(points: &[P]) -> Rtree<P, Params> {
2138 let mut tree: Rtree<P, Params> = Rtree::new();
2139 for p in points {
2140 tree.insert(*p);
2141 }
2142 tree
2143 }
2144
2145 fn checked_subtree_union(node: &Node<P>) -> Bounds {
2146 match node {
2147 Node::Leaf(leaf) => union_all(
2148 &leaf
2149 .values()
2150 .iter()
2151 .map(Indexable::bounds)
2152 .collect::<Vec<_>>(),
2153 ),
2154 Node::Branch(children) => {
2155 for (b, child) in children {
2156 assert_eq!(*b, checked_subtree_union(child));
2157 }
2158 union_all(&children.iter().map(|(b, _)| *b).collect::<Vec<_>>())
2159 }
2160 }
2161 }
2162
2163 fn assert_fill_and_depth<Params: SplitParameters>(
2164 tree: &Rtree<P, Params>,
2165 inserted_len: usize,
2166 ) {
2167 fn walk<Params: SplitParameters>(
2168 node: &Node<P>,
2169 depth: usize,
2170 leaf_depths: &mut Vec<usize>,
2171 ) {
2172 match node {
2173 Node::Leaf(leaf) => {
2174 assert!(leaf.len() <= Params::LEAF_MAX);
2175 leaf_depths.push(depth);
2176 }
2177 Node::Branch(children) => {
2178 assert!(children.len() <= Params::BRANCH_MAX);
2179 for (_, child) in children {
2180 walk::<Params>(child, depth + 1, leaf_depths);
2181 }
2182 }
2183 }
2184 }
2185 let mut leaf_depths = Vec::new();
2186 walk::<Params>(&tree.root, 1, &mut leaf_depths);
2187 assert!(leaf_depths.iter().all(|&d| d == leaf_depths[0]));
2188 assert_eq!(tree.height(), leaf_depths[0]);
2189 assert_eq!(tree.height(), tree.root.height());
2190 assert_eq!(tree.root.value_count(), tree.len());
2191 assert_eq!(tree.len(), inserted_len);
2192 }
2193
2194 fn adversarial_bulk_inputs() -> [Vec<P>; 4] {
2195 let sorted_by_x: Vec<P> = (0..5_000i32)
2196 .map(|i| P::new(f64::from(i), f64::from(i % 71)))
2197 .collect();
2198 let reverse_sorted_by_x: Vec<P> = sorted_by_x.iter().copied().rev().collect();
2199 let one_point: Vec<P> = core::iter::repeat_n(P::new(123.0, 456.0), 5_000).collect();
2200 let vertical_line: Vec<P> = (0..5_000i32).map(|i| P::new(7.0, f64::from(i))).collect();
2201 [sorted_by_x, reverse_sorted_by_x, one_point, vertical_line]
2202 }
2203
2204 fn adversarial_str_invariant_case<Params: SplitParameters>() {
2205 for points in adversarial_bulk_inputs() {
2206 let bulk: Rtree<P, Params> = points.clone().into_iter().collect();
2207 checked_subtree_union(&bulk.root);
2208 assert_fill_and_depth(&bulk, points.len());
2209 }
2210 }
2211
2212 #[test]
2213 fn invariants_hold_on_adversarial_bulk_inputs_max6() {
2214 adversarial_str_invariant_case::<Quadratic<6, 2>>();
2215 }
2216
2217 #[test]
2218 fn invariants_hold_on_adversarial_bulk_inputs_max8() {
2219 adversarial_str_invariant_case::<Quadratic<8, 3>>();
2220 }
2221
2222 #[test]
2223 fn invariants_hold_on_adversarial_bulk_inputs_max16() {
2224 adversarial_str_invariant_case::<Quadratic<16, 4>>();
2225 }
2226
2227 #[test]
2228 fn invariants_hold_on_adversarial_bulk_inputs_max32() {
2229 adversarial_str_invariant_case::<Quadratic<32, 9>>();
2230 }
2231
2232 fn structural_invariant_case<Params: SplitParameters>() {
2233 let points = uniform_points(10_000);
2234 let tree = insert_built::<Params>(&points);
2235 checked_subtree_union(&tree.root);
2236 assert_fill_and_depth(&tree, points.len());
2237 let bulk: Rtree<P, Params> = points.clone().into_iter().collect();
2238 checked_subtree_union(&bulk.root);
2239 assert_fill_and_depth(&bulk, points.len());
2240 }
2241
2242 #[test]
2243 fn invariant_bounds_fill_and_depth_max6() {
2244 structural_invariant_case::<Quadratic<6, 2>>();
2245 }
2246
2247 #[test]
2248 fn invariant_bounds_fill_and_depth_max8() {
2249 structural_invariant_case::<Quadratic<8, 3>>();
2250 }
2251
2252 #[test]
2253 fn invariant_bounds_fill_and_depth_max16() {
2254 structural_invariant_case::<Quadratic<16, 4>>();
2255 }
2256
2257 #[test]
2258 fn invariant_bounds_fill_and_depth_max32() {
2259 structural_invariant_case::<Quadratic<32, 9>>();
2260 }
2261
2262 #[test]
2263 fn invariant_bounds_fill_and_depth_asymmetric_8_32() {
2264 structural_invariant_case::<AsymmetricQuadratic<8, 3, 32, 9>>();
2265 }
2266
2267 #[test]
2268 fn query_of_an_exact_leaf_box_matches_scan() {
2269 fn collect_leaf_boxes(node: &Node<P>, boxes: &mut Vec<Bounds>) {
2270 match node {
2271 Node::Leaf(values) => boxes.push(
2272 values
2273 .iter()
2274 .map(Indexable::bounds)
2275 .reduce(|left, right| left.union(&right))
2276 .expect("bulk leaves are non-empty"),
2277 ),
2278 Node::Branch(children) => {
2279 for (_, child) in children {
2280 collect_leaf_boxes(child, boxes);
2281 }
2282 }
2283 }
2284 }
2285
2286 let points = uniform_points(40);
2287 let tree: Rtree<P> = points.clone().into_iter().collect();
2288 let mut leaf_boxes = Vec::new();
2289 collect_leaf_boxes(&tree.root, &mut leaf_boxes);
2290 for leaf_box in leaf_boxes {
2291 let mut expected: Vec<[f64; 2]> = points
2292 .iter()
2293 .filter(|p| {
2294 p.get::<0>() >= leaf_box.min[0]
2295 && p.get::<0>() <= leaf_box.max[0]
2296 && p.get::<1>() >= leaf_box.min[1]
2297 && p.get::<1>() <= leaf_box.max[1]
2298 })
2299 .map(|p| [p.get::<0>(), p.get::<1>()])
2300 .collect();
2301 expected.sort_by(coordinate_order);
2302 for predicate in [
2303 Predicate::Intersects(leaf_box),
2304 Predicate::CoveredBy(leaf_box),
2305 ] {
2306 let mut got: Vec<[f64; 2]> = tree
2307 .query(predicate)
2308 .iter()
2309 .map(|p| [p.get::<0>(), p.get::<1>()])
2310 .collect();
2311 got.sort_by(coordinate_order);
2312 assert_eq!(
2313 got, expected,
2314 "query of an exact leaf box diverges from the scan for {predicate:?}"
2315 );
2316 }
2317 }
2318 }
2319
2320 fn coordinate_order(a: &[f64; 2], b: &[f64; 2]) -> core::cmp::Ordering {
2321 a[0].total_cmp(&b[0]).then(a[1].total_cmp(&b[1]))
2322 }
2323
2324 #[test]
2325 fn bulk_load_balances() {
2326 let points: Vec<P> = (0..10_000)
2327 .map(|i| P::new(f64::from(i % 100), f64::from(i / 100)))
2328 .collect();
2329 let t: Rtree<P> = points.into_iter().collect();
2330 assert_eq!(t.len(), 10_000);
2331 assert_eq!(t.height(), 7);
2334 }
2335}