Skip to main content

hurray_core/layout/
hilbert.rs

1//! Hilbert curve layout descriptor.
2//!
3//! Tag `0x40`. Tier 2. Carries curve order and rank.
4//! See `docs/spec/layouts/hilbert.md`.
5
6use crate::{Error, Result};
7
8/// Descriptor for the Hilbert space-filling curve layout.
9///
10/// Elements are ordered according to a Hilbert curve, which provides better
11/// spatial locality than Morton (Z-order): consecutive Hilbert indices always
12/// differ by exactly 1 in exactly one coordinate (L∞ distance = 1).
13///
14/// Constraints (enforced by [`HilbertLayout::new`]):
15/// - `hilbert_order > 0`
16/// - `hilbert_rank >= 2`
17/// - Every tensor dimension must equal `2^hilbert_order`
18///   (validated at shape-binding time via
19///   [`LayoutDescriptor::validate_against_shape`])
20///
21/// The normative index mapping is the Skilling (2004) algorithm; see
22/// `docs/spec/layouts/hilbert.md § Normative Index Mapping`.
23///
24/// # Examples
25///
26/// ```
27/// use hurray_core::layout::{LayoutDescriptor, HilbertLayout};
28///
29/// // 2-D Hilbert curve, order 2 (4×4 tensor).
30/// let layout = LayoutDescriptor::Hilbert(HilbertLayout::new(2, 2).unwrap());
31/// assert_eq!(layout.tag(), 0x40);
32/// ```
33#[derive(Debug, Clone, PartialEq, Eq, Hash)]
34#[non_exhaustive]
35pub struct HilbertLayout {
36    /// Order of the Hilbert curve. MUST be > 0.
37    /// Each tensor dimension must equal `2^hilbert_order`.
38    pub hilbert_order: u32,
39
40    /// Number of curve dimensions. MUST equal the tensor rank. MUST be >= 2.
41    pub hilbert_rank: u32,
42}
43
44impl HilbertLayout {
45    /// Creates a new [`HilbertLayout`], validating that `hilbert_order > 0`
46    /// and `hilbert_rank >= 2`.
47    ///
48    /// Shape consistency (`shape[k] == 2^hilbert_order` for all `k`) is
49    /// deferred to [`LayoutDescriptor::validate_against_shape`] because
50    /// the layout descriptor does not carry the shape.
51    ///
52    /// # Errors
53    ///
54    /// Returns [`Error::InvalidLayout`] if `hilbert_order == 0` or
55    /// `hilbert_rank < 2`.
56    ///
57    /// # Examples
58    ///
59    /// ```
60    /// use hurray_core::layout::HilbertLayout;
61    ///
62    /// let h = HilbertLayout::new(3, 2).unwrap(); // 8×8 tensor
63    /// assert_eq!(h.hilbert_order, 3);
64    /// assert_eq!(h.hilbert_rank, 2);
65    ///
66    /// assert!(HilbertLayout::new(0, 2).is_err()); // order must be > 0
67    /// assert!(HilbertLayout::new(2, 1).is_err()); // rank must be >= 2
68    /// ```
69    pub fn new(hilbert_order: u32, hilbert_rank: u32) -> Result<Self> {
70        if hilbert_order == 0 {
71            return Err(Error::InvalidLayout(
72                "hilbert_order must be > 0".to_string(),
73            ));
74        }
75        if hilbert_rank < 2 {
76            return Err(Error::InvalidLayout(format!(
77                "hilbert_rank must be >= 2, got {hilbert_rank}"
78            )));
79        }
80        Ok(Self {
81            hilbert_order,
82            hilbert_rank,
83        })
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use crate::layout::LayoutDescriptor;
91
92    #[test]
93    fn hilbert_tag_is_0x40() {
94        let layout = LayoutDescriptor::Hilbert(HilbertLayout::new(2, 2).unwrap());
95        assert_eq!(layout.tag(), 0x40);
96    }
97
98    #[test]
99    fn hilbert_buffer_count_is_1() {
100        use std::num::NonZeroU8;
101        let layout = LayoutDescriptor::Hilbert(HilbertLayout::new(2, 2).unwrap());
102        assert_eq!(layout.buffer_count(), Some(NonZeroU8::new(1).unwrap()));
103    }
104
105    #[test]
106    fn rejects_zero_order() {
107        assert!(matches!(
108            HilbertLayout::new(0, 2),
109            Err(Error::InvalidLayout(_))
110        ));
111    }
112
113    #[test]
114    fn rejects_rank_one() {
115        assert!(matches!(
116            HilbertLayout::new(2, 1),
117            Err(Error::InvalidLayout(_))
118        ));
119    }
120
121    #[test]
122    fn rejects_rank_zero() {
123        assert!(matches!(
124            HilbertLayout::new(2, 0),
125            Err(Error::InvalidLayout(_))
126        ));
127    }
128}