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