Skip to main content

geometry_rtree/
rtree.rs

1//! The [`Rtree`] itself — insert, spatial query, nearest-neighbour, and
2//! Sort-Tile-Recursive bulk load.
3//!
4//! Mirrors `boost/geometry/index/rtree.hpp` and the visitor family under
5//! `index/detail/rtree/visitors/`. Insert is the recursive
6//! least-enlargement descent of `visitors/insert.hpp`; query is the
7//! pruning walk of `visitors/spatial_query.hpp`; nearest is the
8//! best-first search of `visitors/distance_query.hpp`.
9
10use 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/// A spatial index over `Indexable` values, parameterised by a split
24/// strategy.
25///
26/// Mirrors `boost::geometry::index::rtree<Value, Parameters>`
27/// (`index/rtree.hpp`). The default uses six-child branches and
28/// 12-value leaves for insertion, with four-child branches and
29/// four-value leaves for bulk packing, via [`AsymmetricRStarSplit`]; pass a symmetric
30/// [`RStarSplit`](crate::split::RStarSplit),
31/// [`Quadratic`](crate::split::Quadratic), or
32/// [`Linear`](crate::split::Linear) as `Params` for a different
33/// trade-off. Most users should retain the default; the [`split`](crate::split)
34/// module explains the parameter order, validity constraints, tuning process,
35/// and benchmark evidence.
36///
37/// # Examples
38///
39/// ```
40/// use geometry_cs::Cartesian;
41/// use geometry_model::Point2D;
42/// use geometry_rtree::Rtree;
43///
44/// type P = Point2D<f64, Cartesian>;
45/// let mut tree: Rtree<P> = Rtree::new();
46/// tree.insert(P::new(1.0, 1.0));
47/// tree.insert(P::new(5.0, 5.0));
48/// assert_eq!(tree.len(), 2);
49/// ```
50#[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    /// An empty tree.
66    #[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    /// Number of values in the tree.
77    #[must_use]
78    pub fn len(&self) -> usize {
79        self.len
80    }
81
82    /// Whether the tree holds no values.
83    #[must_use]
84    pub fn is_empty(&self) -> bool {
85        self.len == 0
86    }
87
88    /// The height of the tree (a single-leaf tree is height 1).
89    ///
90    /// This is cached and returned in constant time.
91    #[must_use]
92    pub fn height(&self) -> usize {
93        self.height
94    }
95
96    /// Insert one value.
97    ///
98    /// Descends by least-enlargement to a leaf, inserts, and splits and
99    /// propagates upward if a node overflows. Mirrors
100    /// `visitors/insert.hpp`.
101    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            // The root split into two nodes n1/n2 (which already hold all
105            // the old root's entries); grow a new root one level taller
106            // over them.
107            self.root = Node::Branch(Vec::from([(b1, n1), (b2, n2)]));
108            self.height += 1;
109        }
110    }
111
112    /// Every value whose bounds satisfy `predicate`.
113    ///
114    /// [`query_iter`](Self::query_iter) collected — the lazy walk is
115    /// the crate's single query implementation. Mirrors
116    /// `visitors/spatial_query.hpp`.
117    #[must_use]
118    pub fn query(&self, predicate: Predicate) -> Vec<&T> {
119        self.query_iter(predicate).collect()
120    }
121
122    /// Lazily iterate the values whose bounds satisfy `predicate`.
123    ///
124    /// The pruning walk of `visitors/spatial_query.hpp` as a lazy
125    /// iterator: stopping early performs no traversal past the value
126    /// stopped at, and folding stores no output.
127    /// [`query`](Self::query) is this walk collected.
128    ///
129    /// # Examples
130    ///
131    /// Fold without collecting:
132    ///
133    /// ```
134    /// use geometry_rtree::{Bounds, Predicate, Rtree};
135    ///
136    /// let tree: Rtree<(Bounds, u32)> = (0..100u32)
137    ///     .map(|i| (Bounds::point([f64::from(i), 0.0]), i))
138    ///     .collect();
139    /// let window = Predicate::Intersects(Bounds::new([10.0, -1.0], [19.0, 1.0]));
140    /// let id_sum: u32 = tree.query_iter(window).map(|(_, id)| id).sum();
141    /// assert_eq!(id_sum, (10..=19).sum());
142    /// ```
143    #[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    /// Lazily iterate ALL values, nearest to `query` first — an
149    /// unbounded ordered stream over the entire tree.
150    ///
151    /// The consumer supplies its own bound via
152    /// [`take`](Iterator::take); with no `k` up front nothing can be
153    /// pruned, so a caller who knows `k` and wants maximum pruning
154    /// calls [`nearest`](Self::nearest) instead. Distances are compared
155    /// SQUARED, the same ordering [`nearest`](Self::nearest) uses.
156    ///
157    /// # Examples
158    ///
159    /// Nearest-one:
160    ///
161    /// ```
162    /// use geometry_rtree::{Bounds, Rtree};
163    ///
164    /// let tree: Rtree<(Bounds, u32)> = (0..100u32)
165    ///     .map(|i| (Bounds::point([f64::from(i), 0.0]), i))
166    ///     .collect();
167    /// let (_, nearest_id) = tree.nearest_iter([41.7, 0.0]).next().unwrap();
168    /// assert_eq!(*nearest_id, 42);
169    /// ```
170    ///
171    /// Over-fetch and re-rank: take more than needed by box distance,
172    /// re-rank by a finer key, keep the best:
173    ///
174    /// ```
175    /// use geometry_rtree::{Bounds, Rtree};
176    ///
177    /// let tree: Rtree<(Bounds, u32)> = (0..100u32)
178    ///     .map(|i| (Bounds::point([f64::from(i), 0.0]), i))
179    ///     .collect();
180    /// let mut candidates: Vec<&(Bounds, u32)> =
181    ///     tree.nearest_iter([50.2, 0.0]).take(8).collect();
182    /// candidates.sort_by_key(|(_, id)| *id);
183    /// candidates.truncate(2);
184    /// let ids: Vec<u32> = candidates.iter().map(|(_, id)| *id).collect();
185    /// assert_eq!(ids, [47, 48]);
186    /// ```
187    #[must_use]
188    pub fn nearest_iter(&self, query: [f64; 2]) -> NearestIter<'_, T> {
189        NearestIter::new(&self.root, query)
190    }
191
192    /// Lazily iterate all values nearest-first with caller-selected
193    /// inline capacities for the node and value frontiers.
194    ///
195    /// Entries beyond either capacity spill that frontier to an
196    /// allocated binary heap. Smaller capacities reduce the iterator's
197    /// stack footprint and initialization cost; larger capacities avoid
198    /// spills on wider searches. Prefer [`nearest_iter`](Self::nearest_iter)
199    /// unless measurements for the caller's tree and query distribution
200    /// justify a different pair.
201    #[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    /// The `k` values nearest to the query point, closest first.
213    ///
214    /// Best-first search over node bounding boxes by SQUARED minimum
215    /// possible distance (same ordering as true distance, no square
216    /// roots). A stack-first frontier (`SearchFrontier`) holds
217    /// unexpanded NODES only, popped nearest-first; candidate values
218    /// never enter it. Each candidate instead goes through a bounded
219    /// max-heap (`NearestBound`) whose entries pair each distance with
220    /// its value. Collecting the final values reuses that heap's
221    /// allocation in place, so the whole search performs one
222    /// `min(k, len)`-sized heap allocation unless the frontier spills.
223    ///
224    /// Termination: a child's box is contained in its parent's, so
225    /// frontier pops ascend in minimum possible distance. When a popped
226    /// node's distance reaches the k-th-best value distance, every
227    /// value in every unvisited subtree is at least that far away and
228    /// the held ranks are final; equality only ties distances already
229    /// held. Mirrors `visitors/distance_query.hpp`.
230    ///
231    /// This bounded implementation stays dedicated: its best-k pruning
232    /// needs `k` up front, which the unbounded
233    /// [`nearest_iter`](Self::nearest_iter) stream cannot have.
234    #[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    /// Bulk-load with top-down Sort-Tile-Recursive packing: recursively
282    /// partition cached centroids into balanced x/y tiles until they fit
283    /// the configured bulk leaf capacity. Produces a balanced tree, the
284    /// analogue of Boost's `pack_create`
285    /// (`index/detail/rtree/pack_create.hpp`).
286    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
315/// A frontier entry of the best-first nearest search: an unexpanded
316/// node keyed by the minimum possible distance of anything inside it.
317struct 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    /// Reversed so the max-first [`SearchFrontier`] pops the SMALLEST
338    /// distance first.
339    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
340        other.dist.total_cmp(&self.dist)
341    }
342}
343
344/// Recursively insert `value` into `node`. Returns `Some((b1,n1,b2,n2))`
345/// if `node` split, giving the caller the two replacement children.
346type 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            // Choose the child that needs the least enlargement.
363            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                // No split: the child now holds its old contents plus
376                // `value`, so its box is the old box grown by `vb` —
377                // O(1), no subtree walk.
378                children[choice].0 = children[choice].0.union(&vb);
379            }
380            None
381        }
382    }
383}
384
385/// Index of the child whose box enlarges least to admit `vb` (ties
386/// broken by smaller area).
387#[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
407/// Split an overflowing leaf's values into two leaves.
408fn 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    // Partition `taken` into the two index groups. Walk once, routing by
414    // membership in g1.
415    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
444/// Split an overflowing branch's children into two branches.
445fn 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
479/// Sort-Tile-Recursive packing of `values` into a balanced tree.
480fn str_pack<T: Indexable, Params: SplitParameters>(values: Vec<T>) -> (Node<T>, usize) {
481    // Cache sort keys once: recursive spatial partitioning otherwise
482    // calls `bounds()` throughout every sort comparator.
483    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
510/// Top-down STR partitioning at one known tree height. Each child gets
511/// a balanced spatial tile small enough for the remaining subtree
512/// capacity, avoiding cross-strip grouping at upper levels.
513fn 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
589/// Ceil of the integer square root, without floating point (MSRV
590/// predates `usize::isqrt`). Integer Newton iteration.
591fn 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    // `x` is floor(sqrt(n)); round up if it is not exact.
602    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                        // Model precomputed bounds over contiguous STR-y groups.
840                        // Computing the bounds here is test-only instrumentation;
841                        // the counters describe query work if they were stored.
842                        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        // The window [2,5]×[2,5] contains a 4×4 block of points.
1449        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        // The three closest to x=10.2 are x=10, 11, 9 in some order.
1462        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        // Four-way bulk packing needs seven levels to cover 10k values
2336        // (4^6 values beneath a height-7 root).
2337        assert_eq!(t.height(), 7);
2338    }
2339}