paradis_core/
record_index.rs

1use crate::internal::Sealed;
2use std::hash::Hash;
3
4/// A type suitable for use as an index into a collection of records.
5///
6/// This trait is currently sealed in order to make changes more easily, and because
7/// there currently is no use case for implementing it outside this crate. If you have a
8/// use case for this, please file an issue.
9///
10/// # Safety
11///
12/// Consumers of this trait must be able to rely on the correctness of the provided methods
13/// in `unsafe` contexts.
14///
15/// All auxiliary traits required by this trait, such as `Eq`, `Ord` or `Hash`,
16/// *must* be implemented correctly.
17///
18/// If two indices compare unequal, then they must not access the same record in a collection.
19pub unsafe trait RecordIndex: Sealed + Eq + Copy + Send + Sync + Ord + Hash {
20    // fn bounds_overlap(bounds1: &Bounds<Self>, bounds2: &Bounds<Self>) -> bool;
21
22    /// Determine if a set of bounds contains another set of bounds.
23    fn contains_bounds(container: &Bounds<Self>, bounds: &Bounds<Self>) -> bool;
24
25    /// Determine if this index is contained inside the provided bounds.
26    fn in_bounds(&self, bounds: &Bounds<Self>) -> bool;
27
28    /// Expand these bounds to include the given index.
29    fn enclose_index(bounds: &mut Bounds<Self>, index: Self);
30
31    /// Returns a set of bounds that are empty (zero extent).
32    fn empty_bounds() -> Bounds<Self>;
33
34    /// Returns a set of bounds that exactly contain only the provided index.
35    fn bounds_for_index(index: Self) -> Bounds<Self>;
36}
37
38/// Bounds associated with an index type.
39///
40/// This is a description of the bounds of a multidimensional array, through the definition of
41/// an *offset* and an *extent*. [`Bounds`] can also be used to describe the bounds of an
42/// index list, which can be used to ensure that a collection of indices are *all* in bounds,
43/// and therefore bounds checks can be eliminated up front, prior to iteration.
44///
45/// In general, [`Bounds`] is expected to be used with either integers like `usize` for
46/// one-dimensional arrays or tuples like `(usize, usize)` for multidimensional arrays.
47/// For data structures, the offset is typically zero along each dimension, and the `extent`
48/// describes the "length" of the array along each dimension.
49///
50/// *Offset* primarily exists in order for index lists to more tightly describe their bounds,
51/// which can be used, for example, to ensure that index lists with disjoint bounds contain
52/// disjoint indices.
53/// However, a data structure *can* have non-zero offset.
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct Bounds<I> {
56    /// The offset of the bounds.
57    pub offset: I,
58    /// The extent of the bounds.
59    pub extent: I,
60}
61
62impl<I> Bounds<I> {
63    /// Zips the offset and extent of two [`Bounds`] instances.
64    pub fn zip<I2>(self, other: Bounds<I2>) -> Bounds<(I, I2)> {
65        Bounds {
66            offset: (self.offset, other.offset),
67            extent: (self.extent, other.extent),
68        }
69    }
70}
71
72impl<I: RecordIndex> Bounds<I> {
73    /// Check if these bounds contain `other`.
74    pub fn contains_bounds(&self, other: &Bounds<I>) -> bool {
75        I::contains_bounds(self, other)
76    }
77
78    /// Check if these bounds contain the given index.
79    pub fn contains_index(&self, index: I) -> bool {
80        index.in_bounds(self)
81    }
82
83    /// Expand these bounds — if needed — so that the given index is contained in the
84    /// updated bounds.
85    pub fn enclose_index(&mut self, index: I) {
86        I::enclose_index(self, index)
87    }
88
89    /// Constructs empty bounds (zero extent along each dimension).
90    pub fn new_empty() -> Self {
91        I::empty_bounds()
92    }
93
94    /// Constructs bounds large enough to hold only exactly the given index.
95    pub fn bounds_for_index(index: I) -> Self {
96        I::bounds_for_index(index)
97    }
98}
99
100macro_rules! impl_single_dim_index {
101    ($ty:ty) => {
102        unsafe impl RecordIndex for $ty {
103            #[inline]
104            fn contains_bounds(container: &Bounds<Self>, bounds: &Bounds<Self>) -> bool {
105                let left_contained = container.offset <= bounds.offset;
106                let right_contained =
107                    bounds.offset + bounds.extent <= container.offset + container.extent;
108                left_contained && right_contained
109            }
110
111            #[inline]
112            fn in_bounds(&self, bounds: &Bounds<Self>) -> bool {
113                let Bounds { offset, extent } = *bounds;
114                let i = *self;
115                offset <= i && i < (offset + extent)
116            }
117
118            #[inline]
119            fn enclose_index(bounds: &mut Bounds<Self>, index: Self) {
120                let new_offset = Self::min(bounds.offset, index);
121                bounds.offset = new_offset;
122                bounds.extent = Self::max(bounds.extent, index - new_offset + 1)
123            }
124
125            #[inline]
126            fn empty_bounds() -> Bounds<Self> {
127                Bounds {
128                    offset: 0,
129                    extent: 0,
130                }
131            }
132
133            #[inline]
134            fn bounds_for_index(index: Self) -> Bounds<Self> {
135                Bounds {
136                    offset: index,
137                    extent: 1,
138                }
139            }
140        }
141    };
142}
143
144impl_single_dim_index!(usize);
145
146#[cfg(any(target_pointer_width = "32", target_pointer_width = "64",))]
147impl_single_dim_index!(u32);
148
149#[cfg(any(target_pointer_width = "64"))]
150impl_single_dim_index!(u64);
151
152/// Joins the provided list of expressions with the given separator
153macro_rules! join_expressions {
154    ($separator:tt; $token_head:expr, $($token_tail:expr),*) => {
155        $token_head $($separator $token_tail)*
156    }
157}
158
159/// Implement the RecordIndex trait for tuples
160macro_rules! impl_tuple_index {
161    (($($idx_type:tt),*), ($($idx:tt),*)) => {
162        unsafe impl<$($idx_type: RecordIndex),*> RecordIndex for ($($idx_type),*) {
163            #[inline]
164            fn contains_bounds(container: &Bounds<Self>, bounds: &Bounds<Self>) -> bool {
165                // First construct 1D bounds
166                let container_bounds = (
167                    $(Bounds { offset: container.offset.$idx, extent: container.extent.$idx }),*
168                );
169                let bounds = (
170                    $(Bounds { offset: bounds.offset.$idx, extent: bounds.extent.$idx }),*
171                );
172                // For tuples, we return true if for each one-dimensional tuple element pair,
173                // the bound is contained in the container (i.e., separately for each axis)
174                join_expressions!(
175                    &&;
176                    $($idx_type::contains_bounds(&container_bounds.$idx, &bounds.$idx)),*
177                )
178            }
179
180            #[inline]
181            fn in_bounds(&self, bounds: &Bounds<Self>) -> bool {
182                // First construct 1D bounds
183                let bounds = (
184                    $(Bounds { offset: bounds.offset.$idx, extent: bounds.extent.$idx }),*
185                );
186                // For tuples, we return true if the index is in bounds along every dimension
187                join_expressions!(
188                    &&;
189                    $(self.$idx.in_bounds(&bounds.$idx)),*
190                )
191            }
192
193            #[inline]
194            fn enclose_index(bounds: &mut Bounds<Self>, index: Self) {
195                // First create 1D bounds
196                let mut bounds_1d = (
197                    $(Bounds { offset: bounds.offset.$idx, extent: bounds.extent.$idx }),*
198                );
199                // Update along each axis
200                $(bounds_1d.$idx.enclose_index(index.$idx);)*
201
202                // Store the results back in tuple bounds
203                $(bounds.offset.$idx = bounds_1d.$idx.offset;)*
204                $(bounds.extent.$idx = bounds_1d.$idx.extent;)*
205            }
206
207            #[inline]
208            fn empty_bounds() -> Bounds<Self> {
209                // First create 1D bounds
210                let bounds_1d = ($($idx_type::empty_bounds()),*);
211
212                // Then merge
213                Bounds {
214                    offset: ($(bounds_1d.$idx.offset),*),
215                    extent: ($(bounds_1d.$idx.offset),*)
216                }
217            }
218
219            #[inline]
220            fn bounds_for_index(index: Self) -> Bounds<Self> {
221                // // First create 1D bounds
222                let bounds_1d = ($($idx_type::bounds_for_index(index.$idx)),*);
223
224                // Then merge
225                Bounds {
226                    offset: ($(bounds_1d.$idx.offset),*),
227                    extent: ($(bounds_1d.$idx.offset),*)
228                }
229            }
230        }
231    };
232}
233
234impl_tuple_index!((I0, I1), (0, 1));
235impl_tuple_index!((I0, I1, I2), (0, 1, 2));
236impl_tuple_index!((I0, I1, I2, I3), (0, 1, 2, 3));
237impl_tuple_index!((I0, I1, I2, I3, I4), (0, 1, 2, 3, 4));
238
239#[cfg(test)]
240mod tests {
241    use crate::{Bounds, RecordIndex};
242
243    #[rustfmt::skip]
244    #[test]
245    fn usize_in_bounds() {
246        // Positive tests
247        assert!(0usize.in_bounds(&Bounds { offset: 0, extent: 1 }));
248        assert!(1usize.in_bounds(&Bounds { offset: 1, extent: 1 }));
249        assert!(1usize.in_bounds(&Bounds { offset: 1, extent: 1 }));
250        assert!(1usize.in_bounds(&Bounds { offset: 0, extent: 2 }));
251
252        // Negative tests
253        assert!(!0usize.in_bounds(&Bounds { offset: 0, extent: 0 }));
254        assert!(!1usize.in_bounds(&Bounds { offset: 0, extent: 0 }));
255        assert!(!1usize.in_bounds(&Bounds { offset: 0, extent: 1 }));
256    }
257
258    #[rustfmt::skip]
259    #[test]
260    fn usize_2dim_in_bounds() {
261        // Zero extent
262        let bounds_zero_extent = Bounds { offset: (0, 0), extent: (0, 0) };
263        assert!(!(0usize, 0usize).in_bounds(&bounds_zero_extent)); // Point at the origin
264        assert!(!(1usize, 1usize).in_bounds(&bounds_zero_extent)); // Any other point
265
266        // Non-zero extent
267        let bounds_normal = Bounds { offset: (0, 0), extent: (2, 2) };
268        assert!((0usize, 0usize).in_bounds(&bounds_normal)); // Inside bounds
269        assert!((1usize, 1usize).in_bounds(&bounds_normal)); // Inside bounds
270        assert!(!(2usize, 2usize).in_bounds(&bounds_normal)); // Outside bounds
271
272        // Bigger bounds with offset
273        let bounds_offset = Bounds { offset: (1, 1), extent: (2, 2) };
274        assert!(!(0usize, 0usize).in_bounds(&bounds_offset)); // Outside bounds
275        assert!((1usize, 1usize).in_bounds(&bounds_offset)); // Edge of bounds
276        assert!((2usize, 2usize).in_bounds(&bounds_offset)); // Inside bounds
277        assert!(!(3usize, 3usize).in_bounds(&bounds_offset)); // Outside bounds
278
279        // Bounds covering a larger area
280        let bounds_large = Bounds { offset: (0, 0), extent: (5, 5) };
281        assert!((0usize, 0usize).in_bounds(&bounds_large)); // Inside bounds
282        assert!((4usize, 4usize).in_bounds(&bounds_large)); // Edge of bounds
283        assert!(!(5usize, 5usize).in_bounds(&bounds_large)); // Outside bounds
284    }
285
286    #[rustfmt::skip]
287    #[test]
288    fn usize_3dim_in_bounds() {
289        // Bounds with zero extent
290        let bounds_zero_extent = Bounds { offset: (0, 0, 0), extent: (0, 0, 0) };
291        assert!(!(0usize, 0usize, 0usize).in_bounds(&bounds_zero_extent)); // Origin
292        assert!(!(1usize, 1usize, 1usize).in_bounds(&bounds_zero_extent)); // Any other point
293
294        // Normal bounds
295        let bounds_normal = Bounds { offset: (0, 0, 0), extent: (3, 3, 3) };
296        assert!((0usize, 0usize, 0usize).in_bounds(&bounds_normal)); // Inside at origin
297        assert!((1usize, 1usize, 1usize).in_bounds(&bounds_normal)); // Center point
298        assert!((2usize, 2usize, 2usize).in_bounds(&bounds_normal)); // Edge of bounds
299        assert!(!(3usize, 3usize, 3usize).in_bounds(&bounds_normal)); // Outside bounds
300
301        // Bounds with offset
302        let bounds_offset = Bounds { offset: (1, 1, 1), extent: (2, 2, 2) };
303        assert!(!(0usize, 0usize, 0usize).in_bounds(&bounds_offset)); // Outside bounds
304        assert!((1usize, 1usize, 1usize).in_bounds(&bounds_offset)); // Edge of bounds
305        assert!((2usize, 2usize, 2usize).in_bounds(&bounds_offset)); // Inside bounds
306        assert!(!(3usize, 3usize, 3usize).in_bounds(&bounds_offset)); // Outside bounds
307
308        // Large bounds
309        let bounds_large = Bounds { offset: (0, 0, 0), extent: (5, 5, 5) };
310        assert!((0usize, 0usize, 0usize).in_bounds(&bounds_large)); // Inside at origin
311        assert!((4usize, 4usize, 4usize).in_bounds(&bounds_large)); // Edge of bounds
312        assert!(!(5usize, 5usize, 5usize).in_bounds(&bounds_large)); // Outside bounds
313    }
314
315    #[rustfmt::skip]
316    #[test]
317    fn usize_contains_bounds() {
318        // Identical bounds - should contain
319        assert!(usize::contains_bounds(&Bounds { offset: 0, extent: 1 },
320                                       &Bounds { offset: 0, extent: 1 }));
321
322        // Outer bounds larger than inner bounds - should contain
323        assert!(usize::contains_bounds(&Bounds { offset: 0, extent: 5 },
324                                       &Bounds { offset: 1, extent: 3 }));
325
326        // Inner bounds larger than outer bounds - should not contain
327        assert!(!usize::contains_bounds(&Bounds { offset: 1, extent: 3 },
328                                        &Bounds { offset: 0, extent: 5 }));
329
330        // Partial overlap - should not contain
331        assert!(!usize::contains_bounds(&Bounds { offset: 0, extent: 3 },
332                                        &Bounds { offset: 2, extent: 2 }));
333
334        // No overlap - should not contain
335        assert!(!usize::contains_bounds(&Bounds { offset: 0, extent: 2 },
336                                        &Bounds { offset: 3, extent: 2 }));
337
338        // Inner bounds start at the edge of outer bounds - should not contain
339        assert!(!usize::contains_bounds(&Bounds { offset: 0, extent: 2 },
340                                        &Bounds { offset: 2, extent: 1 }));
341
342        // Inner bounds is exactly inside but offset is different - should contain
343        assert!(usize::contains_bounds(&Bounds { offset: 0, extent: 5 },
344                                       &Bounds { offset: 1, extent: 1 }));
345
346        // Inner bounds touches the boundary edge of outer bounds - should contain
347        assert!(usize::contains_bounds(&Bounds { offset: 0, extent: 4 },
348                                       &Bounds { offset: 0, extent: 4 }));
349    }
350
351    #[rustfmt::skip]
352    #[test]
353    fn usize_2dim_contains_bounds() {
354        // Identical bounds - should contain
355        assert!(<(usize, usize)>::contains_bounds(&Bounds { offset: (0, 0), extent: (0, 0) },
356                                                  &Bounds { offset: (0, 0), extent: (0, 0) }));
357        assert!(<(usize, usize)>::contains_bounds(&Bounds { offset: (0, 0), extent: (1, 1) },
358                                                  &Bounds { offset: (0, 0), extent: (1, 1) }));
359
360        // Outer bounds larger than inner bounds - should contain
361        assert!(<(usize, usize)>::contains_bounds(&Bounds { offset: (0, 0), extent: (3, 3) },
362                                                  &Bounds { offset: (1, 1), extent: (1, 1) }));
363        assert!(<(usize, usize)>::contains_bounds(&Bounds { offset: (0, 0), extent: (5, 5) },
364                                                  &Bounds { offset: (1, 1), extent: (3, 3) }));
365
366        // Inner bounds larger than outer bounds - should not contain
367        assert!(!<(usize, usize)>::contains_bounds(&Bounds { offset: (1, 1), extent: (3, 3) },
368                                                   &Bounds { offset: (0, 0), extent: (2, 2) }));
369
370        // Partial overlap - should not contain
371        assert!(!<(usize, usize)>::contains_bounds(&Bounds { offset: (0, 0), extent: (2, 2) },
372                                                   &Bounds { offset: (1, 1), extent: (2, 2) }));
373
374        // No overlap - should not contain
375        assert!(!<(usize, usize)>::contains_bounds(&Bounds { offset: (0, 0), extent: (1, 1) },
376                                                   &Bounds { offset: (2, 2), extent: (1, 1) }));
377
378        // Inner bounds start at the edge of outer bounds - should not contain
379        assert!(!<(usize, usize)>::contains_bounds(&Bounds { offset: (0, 0), extent: (2, 2) },
380                                                   &Bounds { offset: (2, 2), extent: (1, 1) }));
381    }
382
383    #[rustfmt::skip]
384    #[test]
385    fn usize_3dim_contains_bounds() {
386        // Identical bounds - should contain
387        assert!(<(usize, usize, usize)>::contains_bounds(&Bounds { offset: (0, 0, 0), extent: (0, 0, 0) },
388                                                         &Bounds { offset: (0, 0, 0), extent: (0, 0, 0) }));
389        assert!(<(usize, usize, usize)>::contains_bounds(&Bounds { offset: (0, 0, 0), extent: (1, 1, 1) },
390                                                         &Bounds { offset: (0, 0, 0), extent: (1, 1, 1) }));
391
392        // Outer bounds larger than inner bounds - should contain
393        assert!(<(usize, usize, usize)>::contains_bounds(&Bounds { offset: (0, 0, 0), extent: (3, 3, 3) },
394                                                         &Bounds { offset: (1, 1, 1), extent: (1, 1, 1) }));
395        assert!(<(usize, usize, usize)>::contains_bounds(&Bounds { offset: (0, 0, 0), extent: (5, 5, 5) },
396                                                         &Bounds { offset: (1, 1, 1), extent: (3, 3, 3) }));
397
398        // Inner bounds larger than outer bounds - should not contain
399        assert!(!<(usize, usize, usize)>::contains_bounds(&Bounds { offset: (1, 1, 1), extent: (4, 4, 4) },
400                                                          &Bounds { offset: (0, 0, 0), extent: (3, 3, 3) }));
401
402        // Partial overlap - should not contain
403        assert!(!<(usize, usize, usize)>::contains_bounds(&Bounds { offset: (0, 0, 0), extent: (2, 2, 2) },
404                                                          &Bounds { offset: (1, 1, 1), extent: (2, 2, 2) }));
405
406        // No overlap - should not contain
407        assert!(!<(usize, usize, usize)>::contains_bounds(&Bounds { offset: (0, 0, 0), extent: (1, 1, 1) },
408                                                          &Bounds { offset: (2, 2, 2), extent: (1, 1, 1) }));
409
410        // Inner bounds start at the edge of outer bounds - should not contain
411        assert!(!<(usize, usize, usize)>::contains_bounds(&Bounds { offset: (0, 0, 0), extent: (2, 2, 2) },
412                                                          &Bounds { offset: (2, 2, 2), extent: (1, 1, 1) }));
413    }
414}