simd_intervaltree/
tree.rs

1//! Core interval tree data structure.
2
3use alloc::vec::Vec;
4use core::ops::ControlFlow;
5
6use crate::builder::IntervalTreeBuilder;
7use crate::query::QueryIter;
8use crate::Interval;
9
10/// An immutable interval tree optimized for overlap queries.
11///
12/// The tree is constructed via [`IntervalTreeBuilder`] and is immutable after
13/// construction, making it `Send + Sync` by default.
14///
15/// # Data Layout
16///
17/// Data is laid out contiguously per node for SIMD-friendly scanning:
18/// - Each node's intervals are stored contiguously in `starts`, `ends`, `values`
19/// - Within a node, intervals are sorted by start (ascending)
20/// - `by_end_indices` provides descending-by-end ordering via indirection
21#[derive(Debug, Clone)]
22pub struct IntervalTree<T, V> {
23    /// Start bounds for all intervals (contiguous per node, sorted by start).
24    pub(crate) starts: Vec<T>,
25    /// End bounds for all intervals.
26    pub(crate) ends: Vec<T>,
27    /// Values associated with each interval.
28    pub(crate) values: Vec<V>,
29    /// Node structures.
30    pub(crate) nodes: Vec<Node>,
31    /// Indices sorted by end (descending) for each node.
32    pub(crate) by_end_indices: Vec<usize>,
33    /// End values sorted descending (contiguous per node, for SIMD scanning).
34    pub(crate) ends_desc: Vec<T>,
35}
36
37/// A node in the interval tree.
38#[derive(Debug, Clone)]
39pub(crate) struct Node {
40    /// Index of pivot interval in the data arrays.
41    pub pivot_idx: usize,
42    /// Start index of this node's intervals in data arrays (contiguous, sorted by start).
43    pub data_begin: usize,
44    /// End index (exclusive) of this node's intervals.
45    pub data_end: usize,
46    /// Start index in by_end_indices for descending-by-end ordering.
47    pub by_end_begin: usize,
48    /// End index (exclusive) in by_end_indices.
49    pub by_end_end: usize,
50    /// Index of left child node, or `usize::MAX` if none.
51    pub left: usize,
52    /// Index of right child node, or `usize::MAX` if none.
53    pub right: usize,
54}
55
56impl Node {
57    pub const NULL: usize = usize::MAX;
58
59    #[inline]
60    pub fn has_left(&self) -> bool {
61        self.left != Self::NULL
62    }
63
64    #[inline]
65    pub fn has_right(&self) -> bool {
66        self.right != Self::NULL
67    }
68}
69
70impl<T, V> IntervalTree<T, V> {
71    /// Creates a new builder for constructing an interval tree.
72    #[must_use]
73    pub fn builder() -> IntervalTreeBuilder<T, V> {
74        IntervalTreeBuilder::new()
75    }
76
77    /// Returns the number of intervals in the tree.
78    #[inline]
79    #[must_use]
80    pub fn len(&self) -> usize {
81        self.values.len()
82    }
83
84    /// Returns true if the tree is empty.
85    #[inline]
86    #[must_use]
87    pub fn is_empty(&self) -> bool {
88        self.values.is_empty()
89    }
90
91    /// Returns the number of nodes in the tree.
92    #[inline]
93    #[must_use]
94    pub fn node_count(&self) -> usize {
95        self.nodes.len()
96    }
97}
98
99impl<T: Ord + Copy, V> IntervalTree<T, V> {
100    /// Queries for all intervals overlapping the given range.
101    ///
102    /// Returns an iterator that yields entries without allocation.
103    #[inline]
104    pub fn query<R: Into<Interval<T>>>(&self, range: R) -> QueryIter<'_, T, V> {
105        QueryIter::new(self, range.into())
106    }
107
108    /// Queries with a callback for early termination.
109    ///
110    /// The callback receives each overlapping interval and can return
111    /// `ControlFlow::Break(result)` to stop iteration early.
112    pub fn query_with<R, F, B>(&self, range: R, mut callback: F) -> ControlFlow<B>
113    where
114        R: Into<Interval<T>>,
115        F: FnMut(&Interval<T>, &V) -> ControlFlow<B>,
116    {
117        let query = range.into();
118        self.query_node(0, &query, &mut callback)
119    }
120
121    fn query_node<F, B>(
122        &self,
123        node_idx: usize,
124        query: &Interval<T>,
125        callback: &mut F,
126    ) -> ControlFlow<B>
127    where
128        F: FnMut(&Interval<T>, &V) -> ControlFlow<B>,
129    {
130        if node_idx >= self.nodes.len() {
131            return ControlFlow::Continue(());
132        }
133
134        let node = &self.nodes[node_idx];
135        let pivot = self.starts[node.pivot_idx];
136
137        // Case 1: Query contains pivot - all intervals at node overlap
138        if query.start <= pivot && pivot < query.end {
139            // Yield all intervals at this node (contiguous in data arrays)
140            for i in node.data_begin..node.data_end {
141                let interval = Interval {
142                    start: self.starts[i],
143                    end: self.ends[i],
144                };
145                callback(&interval, &self.values[i])?;
146            }
147
148            // Search both subtrees
149            if node.has_left() {
150                self.query_node(node.left, query, callback)?;
151            }
152            if node.has_right() {
153                self.query_node(node.right, query, callback)?;
154            }
155        }
156        // Case 2: Pivot is left of query - scan by end (descending), go right
157        else if pivot < query.start {
158            // Scan intervals sorted by end (descending) until end <= query.start
159            for pos in node.by_end_begin..node.by_end_end {
160                let i = self.by_end_indices[pos];
161                let end = self.ends[i];
162                if end <= query.start {
163                    break; // No more overlaps possible
164                }
165                let interval = Interval {
166                    start: self.starts[i],
167                    end,
168                };
169                callback(&interval, &self.values[i])?;
170            }
171
172            if node.has_right() {
173                self.query_node(node.right, query, callback)?;
174            }
175        }
176        // Case 3: Pivot is right of query - scan by start (ascending), go left
177        else {
178            // Scan intervals sorted by start (ascending) until start >= query.end
179            // Data is contiguous and sorted - can use SIMD here for i64
180            for i in node.data_begin..node.data_end {
181                let start = self.starts[i];
182                if start >= query.end {
183                    break; // No more overlaps possible
184                }
185                let interval = Interval {
186                    start,
187                    end: self.ends[i],
188                };
189                callback(&interval, &self.values[i])?;
190            }
191
192            if node.has_left() {
193                self.query_node(node.left, query, callback)?;
194            }
195        }
196
197        ControlFlow::Continue(())
198    }
199}
200
201// SIMD-optimized query for i64 intervals
202impl<V> IntervalTree<i64, V> {
203    /// Queries with SIMD acceleration for i64 intervals.
204    pub fn query_simd<R, F, B>(&self, range: R, mut callback: F) -> ControlFlow<B>
205    where
206        R: Into<Interval<i64>>,
207        F: FnMut(&Interval<i64>, &V) -> ControlFlow<B>,
208    {
209        let query = range.into();
210        self.query_node_simd(0, &query, &mut callback)
211    }
212
213    fn query_node_simd<F, B>(
214        &self,
215        node_idx: usize,
216        query: &Interval<i64>,
217        callback: &mut F,
218    ) -> ControlFlow<B>
219    where
220        F: FnMut(&Interval<i64>, &V) -> ControlFlow<B>,
221    {
222        if node_idx >= self.nodes.len() {
223            return ControlFlow::Continue(());
224        }
225
226        let node = &self.nodes[node_idx];
227        let pivot = self.starts[node.pivot_idx];
228
229        if query.start <= pivot && pivot < query.end {
230            // Case 1: Query contains pivot - yield all
231            for i in node.data_begin..node.data_end {
232                let interval = Interval {
233                    start: self.starts[i],
234                    end: self.ends[i],
235                };
236                callback(&interval, &self.values[i])?;
237            }
238
239            if node.has_left() {
240                self.query_node_simd(node.left, query, callback)?;
241            }
242            if node.has_right() {
243                self.query_node_simd(node.right, query, callback)?;
244            }
245        } else if pivot < query.start {
246            // Case 2: Scan by end descending - use SIMD to find cutoff
247            let node_ends = &self.ends_desc[node.by_end_begin..node.by_end_end];
248            let cutoff = crate::simd::find_le_threshold_i64(node_ends, query.start);
249
250            for pos in node.by_end_begin..(node.by_end_begin + cutoff) {
251                let i = self.by_end_indices[pos];
252                let interval = Interval {
253                    start: self.starts[i],
254                    end: self.ends[i],
255                };
256                callback(&interval, &self.values[i])?;
257            }
258
259            if node.has_right() {
260                self.query_node_simd(node.right, query, callback)?;
261            }
262        } else {
263            // Case 3: Scan by start ascending - use SIMD to find cutoff
264            let node_starts = &self.starts[node.data_begin..node.data_end];
265            let cutoff = crate::simd::find_ge_threshold_i64(node_starts, query.end);
266
267            for i in node.data_begin..(node.data_begin + cutoff) {
268                let interval = Interval {
269                    start: self.starts[i],
270                    end: self.ends[i],
271                };
272                callback(&interval, &self.values[i])?;
273            }
274
275            if node.has_left() {
276                self.query_node_simd(node.left, query, callback)?;
277            }
278        }
279
280        ControlFlow::Continue(())
281    }
282}
283
284// Safety: Tree is immutable after construction
285unsafe impl<T: Send, V: Send> Send for IntervalTree<T, V> {}
286unsafe impl<T: Sync, V: Sync> Sync for IntervalTree<T, V> {}