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