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]
1416 fn insert_many_points_keeps_len() {
1417 let mut t: Rtree<P> = Rtree::new();
1418 for i in 0..1000 {
1419 let x = f64::from(i % 100);
1420 let y = f64::from(i / 100);
1421 t.insert(P::new(x, y));
1422 }
1423 assert_eq!(t.len(), 1000);
1424 assert!(
1425 t.height() >= 2,
1426 "1000 points should build a multi-level tree"
1427 );
1428 }
1429
1430 #[test]
1431 fn query_intersects_finds_the_point() {
1432 let mut t: Rtree<P> = Rtree::new();
1433 for i in 0..200 {
1434 t.insert(P::new(f64::from(i), 0.0));
1435 }
1436 let hits = t.query(Predicate::Intersects(Bounds::new([9.5, -1.0], [10.5, 1.0])));
1437 assert_eq!(hits.len(), 1);
1438 }
1439
1440 #[test]
1441 fn query_within_a_window() {
1442 let mut t: Rtree<P> = Rtree::new();
1443 for x in 0..10 {
1444 for y in 0..10 {
1445 t.insert(P::new(f64::from(x), f64::from(y)));
1446 }
1447 }
1448 let hits = t.query(Predicate::Within(Bounds::new([2.0, 2.0], [5.0, 5.0])));
1450 assert_eq!(hits.len(), 16);
1451 }
1452
1453 #[test]
1454 fn nearest_returns_closest_first() {
1455 let mut t: Rtree<P> = Rtree::new();
1456 for i in 0..100 {
1457 t.insert(P::new(f64::from(i), 0.0));
1458 }
1459 let near = t.nearest([10.2, 0.0], 3);
1460 assert_eq!(near.len(), 3);
1461 let mut xs: Vec<f64> = near.iter().map(|p| p.get::<0>()).collect();
1463 xs.sort_by(|a, b| a.partial_cmp(b).unwrap());
1464 assert_eq!(xs, [9.0, 10.0, 11.0]);
1465 }
1466
1467 #[test]
1468 fn linear_split_also_works() {
1469 let mut t: Rtree<P, Linear<8, 3>> = Rtree::new();
1470 for i in 0..500 {
1471 t.insert(P::new(f64::from(i % 25), f64::from(i / 25)));
1472 }
1473 assert_eq!(t.len(), 500);
1474 let hits = t.query(Predicate::Intersects(Bounds::new([0.0, 0.0], [3.0, 3.0])));
1475 assert!(!hits.is_empty());
1476 }
1477
1478 fn uniform_points(n: usize) -> Vec<P> {
1479 let mut lcg = Lcg::new();
1480 (0..n)
1481 .map(|_| {
1482 let x = lcg.next_f64() * 50_000.0;
1483 let y = lcg.next_f64() * 50_000.0;
1484 P::new(x, y)
1485 })
1486 .collect()
1487 }
1488
1489 fn clustered_points(n: usize) -> Vec<P> {
1490 const CLUSTER_COUNT: usize = 16;
1491 const CLUSTER_RADIUS: f64 = 100.0;
1492 const FIELD: f64 = 50_000.0;
1493
1494 let mut lcg = Lcg::new();
1495 let centers: Vec<[f64; 2]> = (0..CLUSTER_COUNT)
1496 .map(|_| [lcg.next_f64() * FIELD, lcg.next_f64() * FIELD])
1497 .collect();
1498 (0..n)
1499 .map(|i| {
1500 let center = centers[i % CLUSTER_COUNT];
1501 P::new(
1502 center[0] + lcg.next_f64() * 2.0 * CLUSTER_RADIUS - CLUSTER_RADIUS,
1503 center[1] + lcg.next_f64() * 2.0 * CLUSTER_RADIUS - CLUSTER_RADIUS,
1504 )
1505 })
1506 .collect()
1507 }
1508
1509 fn profile_queries(q: usize) -> Vec<[f64; 2]> {
1510 let mut lcg = Lcg::new();
1511 (0..q)
1512 .map(|_| {
1513 let x = lcg.next_f64() * 50_000.0;
1514 lcg.next_f64();
1515 let y = lcg.next_f64() * 50_000.0;
1516 [x, y]
1517 })
1518 .collect()
1519 }
1520
1521 fn report_bounded_metrics(
1522 construction: &str,
1523 distribution: &str,
1524 leaf_order: &str,
1525 expected_results: usize,
1526 total: &BoundedSearchMetrics,
1527 ) {
1528 eprintln!(
1529 "[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={}",
1530 total.frontier_pushes,
1531 total.frontier_pops,
1532 total.frontier_high_water,
1533 total.terminated_by_bound,
1534 total.branch_expansions,
1535 total.child_distance_evaluations,
1536 total.child_order_comparisons,
1537 total.child_pushes,
1538 total.child_pruned,
1539 total.leaf_expansions,
1540 total.reversed_leaf_scans,
1541 total.leaf_group_bound_evaluations,
1542 total.leaf_group_order_comparisons,
1543 total.leaf_groups_scanned,
1544 total.leaf_groups_pruned,
1545 total.value_distance_evaluations,
1546 total.value_bound_passes,
1547 total.value_bound_rejections,
1548 total.rank.calls,
1549 total.rank.partition_comparisons,
1550 total.rank.admissions,
1551 total.rank.replacements,
1552 total.rank.shifted_ranks,
1553 );
1554 }
1555
1556 #[test]
1557 fn records_bounded_search_shape() {
1558 const N: usize = 50_000;
1559 const Q: usize = 100;
1560 const K: usize = 8;
1561
1562 for (construction, distribution, points) in [
1563 ("bulk", "uniform", uniform_points(N)),
1564 ("bulk", "clustered", clustered_points(N)),
1565 ("inserted", "uniform", uniform_points(N)),
1566 ("inserted", "clustered", clustered_points(N)),
1567 ] {
1568 let tree: Rtree<P> = if construction == "bulk" {
1569 points.into_iter().collect()
1570 } else {
1571 let mut tree = Rtree::new();
1572 for point in points {
1573 tree.insert(point);
1574 }
1575 tree
1576 };
1577 let mut forward_total = BoundedSearchMetrics::default();
1578 let mut nearer_y_total = BoundedSearchMetrics::default();
1579 for query in profile_queries(Q) {
1580 let expected = tree.nearest(query, K);
1581 let (forward, metrics) = nearest_with_metrics(&tree, query, K, false, 0, false, 0);
1582 assert_eq!(forward, expected);
1583 forward_total.add(&metrics);
1584 let (nearer_y, metrics) = nearest_with_metrics(&tree, query, K, true, 0, false, 0);
1585 assert_eq!(nearer_y, expected);
1586 nearer_y_total.add(&metrics);
1587 }
1588 report_bounded_metrics(construction, distribution, "forward", Q * K, &forward_total);
1589 report_bounded_metrics(
1590 construction,
1591 distribution,
1592 "nearer-y-end",
1593 Q * K,
1594 &nearer_y_total,
1595 );
1596 }
1597 }
1598
1599 fn record_inserted_parameter_shape<Params: SplitParameters>(
1600 parameters: &str,
1601 distribution: &str,
1602 points: &[P],
1603 ) {
1604 const Q: usize = 100;
1605 const K: usize = 8;
1606
1607 let tree = insert_built::<Params>(points);
1608 let mut total = BoundedSearchMetrics::default();
1609 for query in profile_queries(Q) {
1610 let (_, metrics) = nearest_with_metrics(&tree, query, K, false, 0, false, 0);
1611 total.add(&metrics);
1612 }
1613 report_bounded_metrics(parameters, distribution, "forward", Q * K, &total);
1614 }
1615
1616 fn record_bulk_parameter_shape<Params: SplitParameters>(
1617 parameters: &str,
1618 distribution: &str,
1619 points: &[P],
1620 ) {
1621 const Q: usize = 100;
1622 const K: usize = 8;
1623
1624 let tree: Rtree<P, Params> = points.iter().copied().collect();
1625 let mut total = BoundedSearchMetrics::default();
1626 for query in profile_queries(Q) {
1627 let expected = tree.nearest(query, K);
1628 let (observed, metrics) = nearest_with_metrics(&tree, query, K, true, 0, false, 0);
1629 assert_eq!(observed, expected);
1630 total.add(&metrics);
1631 }
1632 report_bounded_metrics(parameters, distribution, "nearer-y-end", Q * K, &total);
1633 }
1634
1635 fn record_bulk_group_shape(group_size: usize, distribution: &str, points: &[P]) {
1636 const Q: usize = 100;
1637 const K: usize = 8;
1638
1639 let tree: Rtree<P> = points.iter().copied().collect();
1640 let mut total = BoundedSearchMetrics::default();
1641 for query in profile_queries(Q) {
1642 let expected = tree.nearest(query, K);
1643 let (observed, metrics) =
1644 nearest_with_metrics(&tree, query, K, true, group_size, false, 0);
1645 assert_eq!(observed, expected);
1646 total.add(&metrics);
1647 }
1648 report_bounded_metrics(
1649 &alloc::format!("bulk-group{group_size}"),
1650 distribution,
1651 "nearer-y-end",
1652 Q * K,
1653 &total,
1654 );
1655 }
1656
1657 fn record_bulk_center_out_shape(distribution: &str, points: &[P]) {
1658 const Q: usize = 100;
1659 const K: usize = 8;
1660
1661 let tree: Rtree<P> = points.iter().copied().collect();
1662 let mut total = BoundedSearchMetrics::default();
1663 for query in profile_queries(Q) {
1664 let expected = tree.nearest(query, K);
1665 let (observed, metrics) = nearest_with_metrics(&tree, query, K, false, 0, true, 0);
1666 assert_eq!(observed, expected);
1667 total.add(&metrics);
1668 }
1669 report_bounded_metrics("bulk-center-out", distribution, "center-out", Q * K, &total);
1670 }
1671
1672 fn record_bulk_center_out_group_shape(group_size: usize, distribution: &str, points: &[P]) {
1673 const Q: usize = 100;
1674 const K: usize = 8;
1675
1676 let tree: Rtree<P> = points.iter().copied().collect();
1677 let mut total = BoundedSearchMetrics::default();
1678 for query in profile_queries(Q) {
1679 let expected = tree.nearest(query, K);
1680 let (observed, metrics) =
1681 nearest_with_metrics(&tree, query, K, false, group_size, true, 0);
1682 assert_eq!(observed, expected);
1683 total.add(&metrics);
1684 }
1685 report_bounded_metrics(
1686 &alloc::format!("bulk-center-group{group_size}"),
1687 distribution,
1688 "center-out-groups",
1689 Q * K,
1690 &total,
1691 );
1692 }
1693
1694 fn record_bulk_depth_first_shape(distribution: &str, points: &[P]) {
1695 const Q: usize = 100;
1696 const K: usize = 8;
1697
1698 let tree: Rtree<P> = points.iter().copied().collect();
1699 let mut total = BoundedSearchMetrics::default();
1700 for query in profile_queries(Q) {
1701 let expected = tree.nearest(query, K);
1702 let (observed, metrics) = nearest_depth_first_with_metrics(&tree, query, K);
1703 assert_eq!(observed, expected);
1704 total.add(&metrics);
1705 }
1706 report_bounded_metrics(
1707 "bulk-depth-first",
1708 distribution,
1709 "nearer-y-end",
1710 Q * K,
1711 &total,
1712 );
1713 }
1714
1715 fn record_bulk_distance_group_shape(group_size: usize, distribution: &str, points: &[P]) {
1716 const Q: usize = 100;
1717 const K: usize = 8;
1718
1719 let tree: Rtree<P> = points.iter().copied().collect();
1720 let mut total = BoundedSearchMetrics::default();
1721 for query in profile_queries(Q) {
1722 let expected = tree.nearest(query, K);
1723 let (observed, metrics) =
1724 nearest_distance_ordered_groups_with_metrics(&tree, query, K, group_size);
1725 assert_eq!(observed, expected);
1726 total.add(&metrics);
1727 }
1728 report_bounded_metrics(
1729 &alloc::format!("bulk-distance-group{group_size}"),
1730 distribution,
1731 "distance-ordered-groups",
1732 Q * K,
1733 &total,
1734 );
1735 }
1736
1737 fn record_bulk_packed_frontier_shape(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 = PackedFrontierMetrics::default();
1743 for query in profile_queries(Q) {
1744 let expected = tree.nearest(query, K);
1745 let (observed, metrics) = nearest_packed_frontier_with_metrics(&tree, query, K);
1746 assert_eq!(observed, expected);
1747 total.add(&metrics);
1748 }
1749 eprintln!(
1750 "[rtree-packed-frontier] distribution={distribution} expected_results={} pushes={} pops={} high_water={} branch_expansions={} leaf_expansions={} group_pushes={} group_pops={} value_pushes={} value_pops={}",
1751 Q * K,
1752 total.pushes,
1753 total.pops,
1754 total.high_water,
1755 total.branch_expansions,
1756 total.leaf_expansions,
1757 total.group_pushes,
1758 total.group_pops,
1759 total.value_pushes,
1760 total.value_pops,
1761 );
1762 }
1763
1764 fn record_bulk_bounded_group_frontier_shape(distribution: &str, points: &[P]) {
1765 const Q: usize = 100;
1766 const K: usize = 8;
1767
1768 let tree: Rtree<P> = points.iter().copied().collect();
1769 let mut total = BoundedSearchMetrics::default();
1770 for query in profile_queries(Q) {
1771 let expected = tree.nearest(query, K);
1772 let (observed, metrics) = nearest_bounded_group_frontier_with_metrics(&tree, query, K);
1773 assert_eq!(observed, expected);
1774 total.add(&metrics);
1775 }
1776 report_bounded_metrics(
1777 "bulk-group-frontier",
1778 distribution,
1779 "bounded-group-frontier",
1780 Q * K,
1781 &total,
1782 );
1783 }
1784
1785 fn record_bulk_leaf_bvh_shape(terminal_size: usize, distribution: &str, points: &[P]) {
1786 const Q: usize = 100;
1787 const K: usize = 8;
1788
1789 let tree: Rtree<P> = points.iter().copied().collect();
1790 let mut total = BoundedSearchMetrics::default();
1791 for query in profile_queries(Q) {
1792 let expected = tree.nearest(query, K);
1793 let (observed, metrics) =
1794 nearest_with_metrics(&tree, query, K, false, 0, false, terminal_size);
1795 assert_eq!(observed, expected);
1796 total.add(&metrics);
1797 }
1798 report_bounded_metrics(
1799 &alloc::format!("bulk-leaf-bvh{terminal_size}"),
1800 distribution,
1801 "distance-ordered-bvh",
1802 Q * K,
1803 &total,
1804 );
1805 }
1806
1807 #[test]
1808 fn records_bulk_leaf_bvh_shape() {
1809 const N: usize = 50_000;
1810
1811 for (distribution, points) in [
1812 ("uniform", uniform_points(N)),
1813 ("clustered", clustered_points(N)),
1814 ] {
1815 for terminal_size in [2, 4, 8] {
1816 record_bulk_leaf_bvh_shape(terminal_size, distribution, &points);
1817 }
1818 }
1819 }
1820
1821 #[test]
1822 fn records_bulk_packed_frontier_shape() {
1823 const N: usize = 50_000;
1824
1825 for (distribution, points) in [
1826 ("uniform", uniform_points(N)),
1827 ("clustered", clustered_points(N)),
1828 ] {
1829 record_bulk_packed_frontier_shape(distribution, &points);
1830 }
1831 }
1832
1833 #[test]
1834 fn records_bulk_bounded_group_frontier_shape() {
1835 const N: usize = 50_000;
1836
1837 for (distribution, points) in [
1838 ("uniform", uniform_points(N)),
1839 ("clustered", clustered_points(N)),
1840 ] {
1841 record_bulk_bounded_group_frontier_shape(distribution, &points);
1842 }
1843 }
1844
1845 #[test]
1846 fn records_bulk_bounded_distance_group_shape() {
1847 const N: usize = 50_000;
1848
1849 for (distribution, points) in [
1850 ("uniform", uniform_points(N)),
1851 ("clustered", clustered_points(N)),
1852 ] {
1853 for group_size in [4, 8] {
1854 record_bulk_distance_group_shape(group_size, distribution, &points);
1855 }
1856 }
1857 }
1858
1859 #[test]
1860 fn records_bulk_bounded_depth_first_shape() {
1861 const N: usize = 50_000;
1862
1863 for (distribution, points) in [
1864 ("uniform", uniform_points(N)),
1865 ("clustered", clustered_points(N)),
1866 ] {
1867 record_bulk_depth_first_shape(distribution, &points);
1868 }
1869 }
1870
1871 #[test]
1872 fn records_bulk_bounded_center_out_group_shape() {
1873 const N: usize = 50_000;
1874
1875 for (distribution, points) in [
1876 ("uniform", uniform_points(N)),
1877 ("clustered", clustered_points(N)),
1878 ] {
1879 for group_size in [2, 4, 8] {
1880 record_bulk_center_out_group_shape(group_size, distribution, &points);
1881 }
1882 }
1883 }
1884
1885 #[test]
1886 fn records_bulk_bounded_center_out_shape() {
1887 const N: usize = 50_000;
1888
1889 for (distribution, points) in [
1890 ("uniform", uniform_points(N)),
1891 ("clustered", clustered_points(N)),
1892 ] {
1893 record_bulk_center_out_shape(distribution, &points);
1894 }
1895 }
1896
1897 #[test]
1898 fn records_bulk_bounded_group_shape() {
1899 const N: usize = 50_000;
1900
1901 for (distribution, points) in [
1902 ("uniform", uniform_points(N)),
1903 ("clustered", clustered_points(N)),
1904 ] {
1905 for group_size in [2, 4, 8, 16] {
1906 record_bulk_group_shape(group_size, distribution, &points);
1907 }
1908 }
1909 }
1910
1911 #[test]
1912 fn records_bulk_bounded_parameter_shape() {
1913 const N: usize = 50_000;
1914
1915 for (distribution, points) in [
1916 ("uniform", uniform_points(N)),
1917 ("clustered", clustered_points(N)),
1918 ] {
1919 record_bulk_parameter_shape::<AsymmetricRStarSplit<4, 2, 4, 2>>(
1920 "bulk-b4-l4",
1921 distribution,
1922 &points,
1923 );
1924 record_bulk_parameter_shape::<AsymmetricRStarSplit<6, 2, 4, 2>>(
1925 "bulk-b6-l4",
1926 distribution,
1927 &points,
1928 );
1929 record_bulk_parameter_shape::<AsymmetricRStarSplit<8, 3, 4, 2>>(
1930 "bulk-b8-l4",
1931 distribution,
1932 &points,
1933 );
1934 record_bulk_parameter_shape::<AsymmetricRStarSplit<12, 4, 4, 2>>(
1935 "bulk-b12-l4",
1936 distribution,
1937 &points,
1938 );
1939 record_bulk_parameter_shape::<AsymmetricRStarSplit<6, 2, 6, 2>>(
1940 "bulk-b6-l6",
1941 distribution,
1942 &points,
1943 );
1944 record_bulk_parameter_shape::<AsymmetricRStarSplit<4, 2, 8, 3>>(
1945 "bulk-b4-l8",
1946 distribution,
1947 &points,
1948 );
1949 record_bulk_parameter_shape::<AsymmetricRStarSplit<6, 2, 8, 3>>(
1950 "bulk-b6-l8",
1951 distribution,
1952 &points,
1953 );
1954 record_bulk_parameter_shape::<AsymmetricRStarSplit<8, 3, 8, 3>>(
1955 "bulk-b8-l8",
1956 distribution,
1957 &points,
1958 );
1959 record_bulk_parameter_shape::<AsymmetricRStarSplit<8, 3, 12, 4>>(
1960 "bulk-b8-l12",
1961 distribution,
1962 &points,
1963 );
1964 record_bulk_parameter_shape::<AsymmetricRStarSplit<8, 3, 16, 4>>(
1965 "bulk-b8-l16",
1966 distribution,
1967 &points,
1968 );
1969 record_bulk_parameter_shape::<AsymmetricRStarSplit<8, 3, 24, 7>>(
1970 "bulk-b8-l24",
1971 distribution,
1972 &points,
1973 );
1974 record_bulk_parameter_shape::<AsymmetricRStarSplit<8, 3, 32, 9>>(
1975 "bulk-b8-l32",
1976 distribution,
1977 &points,
1978 );
1979 record_bulk_parameter_shape::<AsymmetricRStarSplit<12, 4, 8, 3>>(
1980 "bulk-b12-l8",
1981 distribution,
1982 &points,
1983 );
1984 record_bulk_parameter_shape::<AsymmetricRStarSplit<16, 4, 8, 3>>(
1985 "bulk-b16-l8",
1986 distribution,
1987 &points,
1988 );
1989 record_bulk_parameter_shape::<AsymmetricRStarSplit<32, 9, 8, 3>>(
1990 "bulk-b32-l8",
1991 distribution,
1992 &points,
1993 );
1994 record_bulk_parameter_shape::<AsymmetricRStarSplit<12, 4, 16, 4>>(
1995 "bulk-b12-l16",
1996 distribution,
1997 &points,
1998 );
1999 record_bulk_parameter_shape::<AsymmetricRStarSplit<16, 4, 16, 4>>(
2000 "bulk-b16-l16",
2001 distribution,
2002 &points,
2003 );
2004 record_bulk_parameter_shape::<AsymmetricRStarSplit<32, 9, 16, 4>>(
2005 "bulk-b32-l16",
2006 distribution,
2007 &points,
2008 );
2009 }
2010 }
2011
2012 #[test]
2013 #[allow(clippy::too_many_lines)]
2014 fn records_inserted_bounded_parameter_shape() {
2015 const N: usize = 50_000;
2016
2017 for (distribution, points) in [
2018 ("uniform", uniform_points(N)),
2019 ("clustered", clustered_points(N)),
2020 ] {
2021 record_inserted_parameter_shape::<AsymmetricRStarSplit<4, 2, 4, 2>>(
2022 "inserted-rstar-split-a4-4",
2023 distribution,
2024 &points,
2025 );
2026 record_inserted_parameter_shape::<AsymmetricRStarSplit<4, 2, 8, 3>>(
2027 "inserted-rstar-split-a4-8",
2028 distribution,
2029 &points,
2030 );
2031 record_inserted_parameter_shape::<AsymmetricRStarSplit<4, 2, 16, 4>>(
2032 "inserted-rstar-split-a4-16",
2033 distribution,
2034 &points,
2035 );
2036 record_inserted_parameter_shape::<AsymmetricRStarSplit<4, 2, 32, 9>>(
2037 "inserted-rstar-split-a4-32",
2038 distribution,
2039 &points,
2040 );
2041 record_inserted_parameter_shape::<AsymmetricRStarSplit<6, 2, 8, 3>>(
2042 "inserted-rstar-split-a6-8",
2043 distribution,
2044 &points,
2045 );
2046 record_inserted_parameter_shape::<AsymmetricRStarSplit<6, 2, 10, 3>>(
2047 "inserted-rstar-split-a6-10",
2048 distribution,
2049 &points,
2050 );
2051 record_inserted_parameter_shape::<AsymmetricRStarSplit<6, 2, 12, 4>>(
2052 "inserted-rstar-split-a6-12",
2053 distribution,
2054 &points,
2055 );
2056 record_inserted_parameter_shape::<AsymmetricRStarSplit<6, 2, 14, 4>>(
2057 "inserted-rstar-split-a6-14",
2058 distribution,
2059 &points,
2060 );
2061 record_inserted_parameter_shape::<AsymmetricRStarSplit<6, 2, 16, 4>>(
2062 "inserted-rstar-split-a6-16",
2063 distribution,
2064 &points,
2065 );
2066 record_inserted_parameter_shape::<AsymmetricRStarSplit<6, 2, 32, 9>>(
2067 "inserted-rstar-split-a6-32",
2068 distribution,
2069 &points,
2070 );
2071 record_inserted_parameter_shape::<AsymmetricRStarSplit<8, 3, 8, 3>>(
2072 "inserted-rstar-split-a8-8",
2073 distribution,
2074 &points,
2075 );
2076 record_inserted_parameter_shape::<AsymmetricRStarSplit<8, 3, 10, 3>>(
2077 "inserted-rstar-split-a8-10",
2078 distribution,
2079 &points,
2080 );
2081 record_inserted_parameter_shape::<AsymmetricRStarSplit<8, 3, 12, 4>>(
2082 "inserted-rstar-split-a8-12",
2083 distribution,
2084 &points,
2085 );
2086 record_inserted_parameter_shape::<AsymmetricRStarSplit<8, 3, 16, 4>>(
2087 "inserted-rstar-split-a8-16",
2088 distribution,
2089 &points,
2090 );
2091 record_inserted_parameter_shape::<AsymmetricRStarSplit<12, 4, 16, 4>>(
2092 "inserted-rstar-split-a12-16",
2093 distribution,
2094 &points,
2095 );
2096 record_inserted_parameter_shape::<AsymmetricRStarSplit<12, 4, 32, 9>>(
2097 "inserted-rstar-split-a12-32",
2098 distribution,
2099 &points,
2100 );
2101 record_inserted_parameter_shape::<Quadratic<6, 2>>(
2102 "inserted-q6-6",
2103 distribution,
2104 &points,
2105 );
2106 record_inserted_parameter_shape::<Quadratic<8, 3>>(
2107 "inserted-q8-8",
2108 distribution,
2109 &points,
2110 );
2111 record_inserted_parameter_shape::<Quadratic<16, 4>>(
2112 "inserted-q16-16",
2113 distribution,
2114 &points,
2115 );
2116 record_inserted_parameter_shape::<Quadratic<32, 9>>(
2117 "inserted-q32-32",
2118 distribution,
2119 &points,
2120 );
2121 record_inserted_parameter_shape::<AsymmetricQuadratic<8, 3, 16, 4>>(
2122 "inserted-a8-16",
2123 distribution,
2124 &points,
2125 );
2126 record_inserted_parameter_shape::<AsymmetricQuadratic<8, 3, 32, 9>>(
2127 "inserted-a8-32",
2128 distribution,
2129 &points,
2130 );
2131 record_inserted_parameter_shape::<AsymmetricQuadratic<12, 4, 32, 9>>(
2132 "inserted-a12-32",
2133 distribution,
2134 &points,
2135 );
2136 record_inserted_parameter_shape::<AsymmetricRStarSplit<8, 3, 32, 9>>(
2137 "inserted-rstar-split-a8-32",
2138 distribution,
2139 &points,
2140 );
2141 }
2142 }
2143
2144 fn insert_built<Params: SplitParameters>(points: &[P]) -> Rtree<P, Params> {
2145 let mut tree: Rtree<P, Params> = Rtree::new();
2146 for p in points {
2147 tree.insert(*p);
2148 }
2149 tree
2150 }
2151
2152 fn checked_subtree_union(node: &Node<P>) -> Bounds {
2153 match node {
2154 Node::Leaf(leaf) => union_all(
2155 &leaf
2156 .values()
2157 .iter()
2158 .map(Indexable::bounds)
2159 .collect::<Vec<_>>(),
2160 ),
2161 Node::Branch(children) => {
2162 for (b, child) in children {
2163 assert_eq!(*b, checked_subtree_union(child));
2164 }
2165 union_all(&children.iter().map(|(b, _)| *b).collect::<Vec<_>>())
2166 }
2167 }
2168 }
2169
2170 fn assert_fill_and_depth<Params: SplitParameters>(
2171 tree: &Rtree<P, Params>,
2172 inserted_len: usize,
2173 ) {
2174 fn walk<Params: SplitParameters>(
2175 node: &Node<P>,
2176 depth: usize,
2177 leaf_depths: &mut Vec<usize>,
2178 ) {
2179 match node {
2180 Node::Leaf(leaf) => {
2181 assert!(leaf.len() <= Params::LEAF_MAX);
2182 leaf_depths.push(depth);
2183 }
2184 Node::Branch(children) => {
2185 assert!(children.len() <= Params::BRANCH_MAX);
2186 for (_, child) in children {
2187 walk::<Params>(child, depth + 1, leaf_depths);
2188 }
2189 }
2190 }
2191 }
2192 let mut leaf_depths = Vec::new();
2193 walk::<Params>(&tree.root, 1, &mut leaf_depths);
2194 assert!(leaf_depths.iter().all(|&d| d == leaf_depths[0]));
2195 assert_eq!(tree.height(), leaf_depths[0]);
2196 assert_eq!(tree.height(), tree.root.height());
2197 assert_eq!(tree.root.value_count(), tree.len());
2198 assert_eq!(tree.len(), inserted_len);
2199 }
2200
2201 fn adversarial_bulk_inputs() -> [Vec<P>; 4] {
2202 let sorted_by_x: Vec<P> = (0..5_000i32)
2203 .map(|i| P::new(f64::from(i), f64::from(i % 71)))
2204 .collect();
2205 let reverse_sorted_by_x: Vec<P> = sorted_by_x.iter().copied().rev().collect();
2206 let one_point: Vec<P> = core::iter::repeat_n(P::new(123.0, 456.0), 5_000).collect();
2207 let vertical_line: Vec<P> = (0..5_000i32).map(|i| P::new(7.0, f64::from(i))).collect();
2208 [sorted_by_x, reverse_sorted_by_x, one_point, vertical_line]
2209 }
2210
2211 fn adversarial_str_invariant_case<Params: SplitParameters>() {
2212 for points in adversarial_bulk_inputs() {
2213 let bulk: Rtree<P, Params> = points.clone().into_iter().collect();
2214 checked_subtree_union(&bulk.root);
2215 assert_fill_and_depth(&bulk, points.len());
2216 }
2217 }
2218
2219 #[test]
2220 fn invariants_hold_on_adversarial_bulk_inputs_max6() {
2221 adversarial_str_invariant_case::<Quadratic<6, 2>>();
2222 }
2223
2224 #[test]
2225 fn invariants_hold_on_adversarial_bulk_inputs_max8() {
2226 adversarial_str_invariant_case::<Quadratic<8, 3>>();
2227 }
2228
2229 #[test]
2230 fn invariants_hold_on_adversarial_bulk_inputs_max16() {
2231 adversarial_str_invariant_case::<Quadratic<16, 4>>();
2232 }
2233
2234 #[test]
2235 fn invariants_hold_on_adversarial_bulk_inputs_max32() {
2236 adversarial_str_invariant_case::<Quadratic<32, 9>>();
2237 }
2238
2239 fn structural_invariant_case<Params: SplitParameters>() {
2240 let points = uniform_points(10_000);
2241 let tree = insert_built::<Params>(&points);
2242 checked_subtree_union(&tree.root);
2243 assert_fill_and_depth(&tree, points.len());
2244 let bulk: Rtree<P, Params> = points.clone().into_iter().collect();
2245 checked_subtree_union(&bulk.root);
2246 assert_fill_and_depth(&bulk, points.len());
2247 }
2248
2249 #[test]
2250 fn invariant_bounds_fill_and_depth_max6() {
2251 structural_invariant_case::<Quadratic<6, 2>>();
2252 }
2253
2254 #[test]
2255 fn invariant_bounds_fill_and_depth_max8() {
2256 structural_invariant_case::<Quadratic<8, 3>>();
2257 }
2258
2259 #[test]
2260 fn invariant_bounds_fill_and_depth_max16() {
2261 structural_invariant_case::<Quadratic<16, 4>>();
2262 }
2263
2264 #[test]
2265 fn invariant_bounds_fill_and_depth_max32() {
2266 structural_invariant_case::<Quadratic<32, 9>>();
2267 }
2268
2269 #[test]
2270 fn invariant_bounds_fill_and_depth_asymmetric_8_32() {
2271 structural_invariant_case::<AsymmetricQuadratic<8, 3, 32, 9>>();
2272 }
2273
2274 #[test]
2275 fn query_of_an_exact_leaf_box_matches_scan() {
2276 fn collect_leaf_boxes(node: &Node<P>, boxes: &mut Vec<Bounds>) {
2277 match node {
2278 Node::Leaf(values) => boxes.push(
2279 values
2280 .iter()
2281 .map(Indexable::bounds)
2282 .reduce(|left, right| left.union(&right))
2283 .expect("bulk leaves are non-empty"),
2284 ),
2285 Node::Branch(children) => {
2286 for (_, child) in children {
2287 collect_leaf_boxes(child, boxes);
2288 }
2289 }
2290 }
2291 }
2292
2293 let points = uniform_points(40);
2294 let tree: Rtree<P> = points.clone().into_iter().collect();
2295 let mut leaf_boxes = Vec::new();
2296 collect_leaf_boxes(&tree.root, &mut leaf_boxes);
2297 for leaf_box in leaf_boxes {
2298 let mut expected: Vec<[f64; 2]> = points
2299 .iter()
2300 .filter(|p| {
2301 p.get::<0>() >= leaf_box.min[0]
2302 && p.get::<0>() <= leaf_box.max[0]
2303 && p.get::<1>() >= leaf_box.min[1]
2304 && p.get::<1>() <= leaf_box.max[1]
2305 })
2306 .map(|p| [p.get::<0>(), p.get::<1>()])
2307 .collect();
2308 expected.sort_by(coordinate_order);
2309 for predicate in [Predicate::Intersects(leaf_box), Predicate::Within(leaf_box)] {
2310 let mut got: Vec<[f64; 2]> = tree
2311 .query(predicate)
2312 .iter()
2313 .map(|p| [p.get::<0>(), p.get::<1>()])
2314 .collect();
2315 got.sort_by(coordinate_order);
2316 assert_eq!(
2317 got, expected,
2318 "query of an exact leaf box diverges from the scan for {predicate:?}"
2319 );
2320 }
2321 }
2322 }
2323
2324 fn coordinate_order(a: &[f64; 2], b: &[f64; 2]) -> core::cmp::Ordering {
2325 a[0].total_cmp(&b[0]).then(a[1].total_cmp(&b[1]))
2326 }
2327
2328 #[test]
2329 fn bulk_load_balances() {
2330 let points: Vec<P> = (0..10_000)
2331 .map(|i| P::new(f64::from(i % 100), f64::from(i / 100)))
2332 .collect();
2333 let t: Rtree<P> = points.into_iter().collect();
2334 assert_eq!(t.len(), 10_000);
2335 assert_eq!(t.height(), 7);
2338 }
2339}