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}