Skip to main content

hurray_core/layout/
morton.rs

1//! Morton (Z-order curve) layout descriptor.
2//!
3//! Tag `0x05`. Carries per-dimension bit counts for the Morton encoding.
4//! See `docs/spec/layouts/morton.md`.
5
6use crate::{Error, Result};
7
8/// Descriptor for the Morton (Z-order curve) layout.
9///
10/// Elements are stored by interleaving the bits of their dimension indices in
11/// round-robin order (LSB of dimension 0 first), producing a linear order with
12/// good spatial locality for multi-dimensional access patterns.
13///
14/// For each dimension `k`, `shape[k]` MUST satisfy `shape[k] <= 2^morton_bits[k]`.
15/// The buffer must hold exactly `2^(sum(morton_bits))` elements.
16///
17/// # Examples
18///
19/// ```
20/// use hurray_core::layout::{LayoutDescriptor, MortonLayout};
21///
22/// // 4×4 tensor: 2 bits per dimension.
23/// let layout = LayoutDescriptor::Morton(MortonLayout::new(vec![2, 2]).unwrap());
24/// assert_eq!(layout.tag(), 0x05);
25/// ```
26#[derive(Debug, Clone, PartialEq, Eq, Hash)]
27#[non_exhaustive]
28pub struct MortonLayout {
29    /// Number of bits used per dimension in the Morton encoding.
30    /// Every value MUST be > 0.
31    pub morton_bits: Vec<u32>,
32}
33
34impl MortonLayout {
35    /// Creates a new [`MortonLayout`], validating that every entry in
36    /// `morton_bits` is greater than 0.
37    ///
38    /// # Errors
39    ///
40    /// Returns [`Error::InvalidLayout`] if any `morton_bits[k]` is 0, or if
41    /// `morton_bits` is empty.
42    ///
43    /// # Examples
44    ///
45    /// ```
46    /// use hurray_core::layout::MortonLayout;
47    ///
48    /// let m = MortonLayout::new(vec![2, 2]).unwrap();
49    /// assert_eq!(m.morton_bits, [2, 2]);
50    ///
51    /// // Zero bits is invalid.
52    /// assert!(MortonLayout::new(vec![2, 0]).is_err());
53    /// ```
54    pub fn new(morton_bits: Vec<u32>) -> Result<Self> {
55        if morton_bits.is_empty() {
56            return Err(Error::InvalidLayout(
57                "morton_bits must not be empty".to_string(),
58            ));
59        }
60        for (k, &bits) in morton_bits.iter().enumerate() {
61            if bits == 0 {
62                return Err(Error::InvalidLayout(format!(
63                    "morton_bits[{k}] must be > 0, got 0"
64                )));
65            }
66        }
67        Ok(Self { morton_bits })
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use crate::layout::LayoutDescriptor;
75
76    #[test]
77    fn morton_tag_is_0x05() {
78        let layout = LayoutDescriptor::Morton(MortonLayout::new(vec![2, 2]).unwrap());
79        assert_eq!(layout.tag(), 0x05);
80    }
81
82    #[test]
83    fn morton_buffer_count_is_1() {
84        use std::num::NonZeroU8;
85        let layout = LayoutDescriptor::Morton(MortonLayout::new(vec![2, 2]).unwrap());
86        assert_eq!(layout.buffer_count(), Some(NonZeroU8::new(1).unwrap()));
87    }
88
89    #[test]
90    fn rejects_zero_bits() {
91        assert!(matches!(
92            MortonLayout::new(vec![2, 0]),
93            Err(Error::InvalidLayout(_))
94        ));
95    }
96
97    #[test]
98    fn rejects_empty_morton_bits() {
99        assert!(matches!(
100            MortonLayout::new(vec![]),
101            Err(Error::InvalidLayout(_))
102        ));
103    }
104}