1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum DigitOrder {
25 RowMajor,
27 Morton,
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum PadPolicy {
34 MixedRadix,
36 Pow2,
38}
39
40#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct Layout {
43 pub dims: Vec<usize>,
45 pub padded: Vec<usize>,
47 pub modes: Vec<usize>,
49 pub strides: Vec<usize>,
51 pub order: DigitOrder,
53 pub pad: PadPolicy,
55}
56
57fn smooth_factors(mut p: usize) -> Option<Vec<usize>> {
58 let mut f = Vec::new();
59 for q in [7usize, 5, 3, 2] {
60 while p.is_multiple_of(q) {
61 f.push(q);
62 p /= q;
63 }
64 }
65 (p == 1).then_some(f)
66}
67
68pub fn padded_len(n: usize, pad: PadPolicy) -> (usize, Vec<usize>) {
70 assert!(n >= 1);
71 match pad {
72 PadPolicy::Pow2 => {
73 let p = n.next_power_of_two();
74 (p, vec![2; p.trailing_zeros() as usize])
75 }
76 PadPolicy::MixedRadix => {
77 let mut p = n;
78 loop {
79 if let Some(f) = smooth_factors(p) {
80 return (p, f);
83 }
84 p += 1;
85 }
86 }
87 }
88}
89
90impl Layout {
91 pub fn new(dims: &[usize], order: DigitOrder, pad: PadPolicy) -> Self {
93 assert!(!dims.is_empty(), "Layout: empty shape");
94 let mut padded = Vec::new();
95 let mut digits: Vec<Vec<usize>> = Vec::new();
96 for &n in dims {
97 let (p, f) = padded_len(n, pad);
98 padded.push(p);
99 digits.push(f);
100 }
101 let mut axis_stride = vec![1usize; dims.len()];
103 for a in (0..dims.len().saturating_sub(1)).rev() {
104 axis_stride[a] = axis_stride[a + 1] * padded[a + 1];
105 }
106 let digit_weight = |a: usize, j: usize| -> usize { digits[a][j + 1..].iter().product() };
108 let mut modes = Vec::new();
109 let mut strides = Vec::new();
110 match order {
111 DigitOrder::RowMajor => {
112 for a in 0..dims.len() {
113 for j in 0..digits[a].len() {
114 modes.push(digits[a][j]);
115 strides.push(digit_weight(a, j) * axis_stride[a]);
116 }
117 }
118 }
119 DigitOrder::Morton => {
120 let lmax = digits.iter().map(|d| d.len()).max().unwrap_or(0);
121 for level in (1..=lmax).rev() {
122 for a in 0..dims.len() {
123 let l = digits[a].len();
124 if l >= level {
125 let j = l - level;
126 modes.push(digits[a][j]);
127 strides.push(digit_weight(a, j) * axis_stride[a]);
128 }
129 }
130 }
131 }
132 }
133 if modes.is_empty() {
134 modes.push(1);
136 strides.push(1);
137 }
138 Self {
139 dims: dims.to_vec(),
140 padded,
141 modes,
142 strides,
143 order,
144 pad,
145 }
146 }
147
148 pub fn len(&self) -> usize {
150 self.dims.iter().product()
151 }
152
153 pub fn is_empty(&self) -> bool {
155 self.len() == 0
156 }
157
158 pub fn tensor_len(&self) -> usize {
160 self.modes.iter().product()
161 }
162
163 pub fn pad_grid(&self, data: &[f64]) -> Vec<f64> {
165 assert_eq!(data.len(), self.len());
166 if self.dims == self.padded {
167 return data.to_vec();
168 }
169 let nd = self.dims.len();
170 let total: usize = self.padded.iter().product();
171 let mut src_stride = vec![1usize; nd];
172 for a in (0..nd.saturating_sub(1)).rev() {
173 src_stride[a] = src_stride[a + 1] * self.dims[a + 1];
174 }
175 let mut out = Vec::with_capacity(total);
176 let mut idx = vec![0usize; nd];
177 for _ in 0..total {
178 let mut off = 0;
179 for a in 0..nd {
180 off += reflect(idx[a], self.dims[a]) * src_stride[a];
181 }
182 out.push(data[off]);
183 for a in (0..nd).rev() {
184 idx[a] += 1;
185 if idx[a] < self.padded[a] {
186 break;
187 }
188 idx[a] = 0;
189 }
190 }
191 out
192 }
193
194 pub fn to_tensor(&self, padded: &[f64]) -> Vec<f64> {
196 let n = self.tensor_len();
197 assert_eq!(padded.len(), n);
198 if self.is_identity() {
199 return padded.to_vec();
200 }
201 let mut out = Vec::with_capacity(n);
202 self.odometer(|src| out.push(padded[src]));
203 out
204 }
205
206 pub fn from_tensor(&self, tensor: &[f64]) -> Vec<f64> {
208 let n = self.tensor_len();
209 assert_eq!(tensor.len(), n);
210 let grid = if self.is_identity() {
211 tensor.to_vec()
212 } else {
213 let mut g = vec![0.0; n];
214 let mut t = 0;
215 self.odometer(|src| {
216 g[src] = tensor[t];
217 t += 1;
218 });
219 g
220 };
221 self.crop(&grid)
222 }
223
224 fn crop(&self, grid: &[f64]) -> Vec<f64> {
225 if self.dims == self.padded {
226 return grid.to_vec();
227 }
228 let nd = self.dims.len();
229 let mut pstride = vec![1usize; nd];
230 for a in (0..nd.saturating_sub(1)).rev() {
231 pstride[a] = pstride[a + 1] * self.padded[a + 1];
232 }
233 let mut out = Vec::with_capacity(self.len());
234 let mut idx = vec![0usize; nd];
235 for _ in 0..self.len() {
236 let off: usize = (0..nd).map(|a| idx[a] * pstride[a]).sum();
237 out.push(grid[off]);
238 for a in (0..nd).rev() {
239 idx[a] += 1;
240 if idx[a] < self.dims[a] {
241 break;
242 }
243 idx[a] = 0;
244 }
245 }
246 out
247 }
248
249 fn is_identity(&self) -> bool {
250 let mut s = 1usize;
252 for k in (0..self.modes.len()).rev() {
253 if self.strides[k] != s {
254 return false;
255 }
256 s *= self.modes[k];
257 }
258 true
259 }
260
261 fn odometer(&self, mut f: impl FnMut(usize)) {
263 let d = self.modes.len();
264 let n = self.tensor_len();
265 let mut digit = vec![0usize; d];
266 let mut off = 0usize;
267 for _ in 0..n {
268 f(off);
269 let mut k = d;
270 while k > 0 {
271 k -= 1;
272 digit[k] += 1;
273 off += self.strides[k];
274 if digit[k] < self.modes[k] {
275 break;
276 }
277 off -= self.strides[k] * self.modes[k];
278 digit[k] = 0;
279 }
280 }
281 }
282}
283
284fn reflect(i: usize, n: usize) -> usize {
286 if n == 1 {
287 return 0;
288 }
289 let period = 2 * (n - 1);
290 let r = i % period;
291 if r < n {
292 r
293 } else {
294 period - r
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn rowmajor_pow2_is_identity() {
304 let l = Layout::new(&[8, 4], DigitOrder::RowMajor, PadPolicy::Pow2);
305 assert_eq!(l.modes, vec![2; 5]);
306 let x: Vec<f64> = (0..32).map(|i| i as f64).collect();
307 assert_eq!(l.to_tensor(&l.pad_grid(&x)), x);
308 }
309
310 #[test]
311 fn morton_matches_bit_interleave() {
312 let l = Layout::new(&[4, 4], DigitOrder::Morton, PadPolicy::Pow2);
313 let x: Vec<f64> = (0..16).map(|i| i as f64).collect();
314 let t = l.to_tensor(&x);
315 for (ti, &v) in t.iter().enumerate() {
317 let y = ((ti >> 3) & 1) << 1 | ((ti >> 1) & 1);
318 let xx = ((ti >> 2) & 1) << 1 | (ti & 1);
319 assert_eq!(v, (y * 4 + xx) as f64);
320 }
321 }
322
323 #[test]
324 fn roundtrip_all_policies() {
325 for dims in [
326 vec![1000],
327 vec![12, 20],
328 vec![5, 6, 7],
329 vec![11, 3, 9],
330 vec![1],
331 ] {
332 for order in [DigitOrder::RowMajor, DigitOrder::Morton] {
333 for pad in [PadPolicy::MixedRadix, PadPolicy::Pow2] {
334 let l = Layout::new(&dims, order, pad);
335 let n = l.len();
336 let x: Vec<f64> = (0..n).map(|i| (i as f64).sin()).collect();
337 let t = l.to_tensor(&l.pad_grid(&x));
338 assert_eq!(t.len(), l.tensor_len());
339 assert_eq!(l.from_tensor(&t), x, "{dims:?} {order:?} {pad:?}");
340 }
341 }
342 }
343 }
344
345 #[test]
346 fn mixed_radix_lengths() {
347 assert_eq!(padded_len(384, PadPolicy::MixedRadix).0, 384);
348 assert_eq!(padded_len(384, PadPolicy::Pow2).0, 512);
349 assert_eq!(
350 padded_len(500, PadPolicy::MixedRadix).1,
351 vec![5, 5, 5, 2, 2]
352 );
353 assert_eq!(padded_len(11, PadPolicy::MixedRadix).0, 12);
354 assert_eq!(reflect(5, 4), 1);
355 }
356}