hurray_core/layout/addressing/
morton.rs1use crate::layout::MortonLayout;
6use crate::{Error, Result, Shape};
7
8use super::{validate_index, ElementAddress};
9
10impl ElementAddress for MortonLayout {
11 fn element_offset(&self, index: &[u64], shape: &Shape) -> Result<u64> {
12 validate_index(index, shape)?;
13 if self.morton_bits.len() != shape.rank() {
14 return Err(Error::IndexRankMismatch {
15 index_rank: self.morton_bits.len(),
16 shape_rank: shape.rank(),
17 });
18 }
19
20 let rank = index.len();
21 let total_bits: u32 = self.morton_bits.iter().sum();
22 if total_bits > 64 {
23 return Err(Error::IndexArithmeticOverflow);
24 }
25
26 let max_bits = self.morton_bits.iter().copied().max().unwrap_or(0);
27 let mut morton_code: u64 = 0;
28
29 for b in 0..max_bits {
32 for (d, (&idx_val, &bits_for_dim)) in
33 index.iter().zip(self.morton_bits.iter()).enumerate()
34 {
35 if b < bits_for_dim {
36 let bit = (idx_val >> b) & 1;
37 let shift = b * rank as u32 + d as u32;
38 if shift >= 64 {
39 return Err(Error::IndexArithmeticOverflow);
40 }
41 morton_code |= bit << shift;
42 }
43 }
44 }
45 Ok(morton_code)
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use crate::{Error, Shape};
53
54 fn layout_2d_2bits() -> (MortonLayout, Shape) {
62 let layout = MortonLayout::new(vec![2, 2]).unwrap();
63 let shape = Shape::new(vec![4, 4]).unwrap();
64 (layout, shape)
65 }
66
67 #[test]
68 fn morton_4x4_full_table() {
69 let table: &[([u64; 2], u64)] = &[
72 ([0, 0], 0),
73 ([1, 0], 1),
74 ([0, 1], 2),
75 ([1, 1], 3),
76 ([2, 0], 4),
77 ([3, 0], 5),
78 ([2, 1], 6),
79 ([3, 1], 7),
80 ([0, 2], 8),
81 ([1, 2], 9),
82 ([0, 3], 10),
83 ([1, 3], 11),
84 ([2, 2], 12),
85 ([3, 2], 13),
86 ([2, 3], 14),
87 ([3, 3], 15),
88 ];
89 let (layout, shape) = layout_2d_2bits();
90 for &(index, expected) in table {
91 let got = layout.element_offset(&index, &shape).unwrap();
92 assert_eq!(
93 got, expected,
94 "morton[{},{}]: expected {expected}, got {got}",
95 index[0], index[1]
96 );
97 }
98 }
99
100 #[test]
105 fn morton_4x4_spec_example_2_3() {
106 let (layout, shape) = layout_2d_2bits();
107 assert_eq!(layout.element_offset(&[2, 3], &shape).unwrap(), 14);
108 }
109
110 #[test]
114 fn morton_2x2x2_full_table() {
115 let layout = MortonLayout::new(vec![1, 1, 1]).unwrap();
116 let shape = Shape::new(vec![2, 2, 2]).unwrap();
117 let table: &[([u64; 3], u64)] = &[
118 ([0, 0, 0], 0),
119 ([1, 0, 0], 1),
120 ([0, 1, 0], 2),
121 ([1, 1, 0], 3),
122 ([0, 0, 1], 4),
123 ([1, 0, 1], 5),
124 ([0, 1, 1], 6),
125 ([1, 1, 1], 7),
126 ];
127 for &(index, expected) in table {
128 let got = layout.element_offset(&index, &shape).unwrap();
129 assert_eq!(
130 got, expected,
131 "morton[{},{},{}]: expected {expected}, got {got}",
132 index[0], index[1], index[2]
133 );
134 }
135 }
136
137 #[test]
140 fn morton_overflow_total_bits_exceeds_64() {
141 let layout = MortonLayout::new(vec![33, 33]).unwrap();
142 let _shape = Shape::new(vec![crate::DYNAMIC, crate::DYNAMIC]).unwrap();
145 let shape2 = Shape::new(vec![8_589_934_592u64, 8_589_934_592u64]).unwrap();
150 let err = layout.element_offset(&[0, 0], &shape2).unwrap_err();
151 assert!(
152 matches!(err, Error::IndexArithmeticOverflow),
153 "expected IndexArithmeticOverflow, got {err:?}"
154 );
155 }
156
157 #[test]
159 fn morton_rank_mismatch() {
160 let (layout, shape) = layout_2d_2bits();
161 let err = layout.element_offset(&[1], &shape).unwrap_err();
162 assert!(
163 matches!(err, Error::IndexRankMismatch { .. }),
164 "expected IndexRankMismatch, got {err:?}"
165 );
166 }
167
168 #[test]
170 fn morton_index_out_of_range() {
171 let (layout, shape) = layout_2d_2bits();
172 let err = layout.element_offset(&[4, 0], &shape).unwrap_err();
174 assert!(
175 matches!(err, Error::IndexOutOfRange { dim: 0, .. }),
176 "expected IndexOutOfRange, got {err:?}"
177 );
178 }
179}