1use alloc::vec;
2use alloc::vec::Vec;
3use burn_std::{Shape, Slice};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct Layout {
11 shape: Shape,
12 strides: Vec<isize>,
14 start_offset: usize,
15}
16
17pub(crate) fn contiguous_strides_usize(shape: &Shape) -> Vec<usize> {
19 let ndims = shape.num_dims();
20 let mut strides = vec![1usize; ndims];
21 for i in (0..ndims.saturating_sub(1)).rev() {
22 strides[i] = strides[i + 1] * shape[i + 1];
23 }
24 strides
25}
26
27pub(crate) fn slice_base_offset(
33 slice_idx: usize,
34 shape: &Shape,
35 strides: &[usize],
36 dim: usize,
37) -> usize {
38 let ndims = shape.num_dims();
39 let mut offset = 0;
40 let mut remaining = slice_idx;
41 for d in (0..ndims).rev() {
42 if d == dim {
43 continue;
44 }
45 let s = shape[d];
46 offset += (remaining % s) * strides[d];
47 remaining /= s;
48 }
49 offset
50}
51
52impl Layout {
53 pub fn contiguous(shape: Shape) -> Self {
55 let strides: Vec<isize> = contiguous_strides_usize(&shape)
56 .into_iter()
57 .map(|s| s as isize)
58 .collect();
59
60 Self {
61 shape,
62 strides,
63 start_offset: 0,
64 }
65 }
66
67 pub fn new(shape: Shape, strides: Vec<isize>, start_offset: usize) -> Self {
69 debug_assert_eq!(shape.num_dims(), strides.len());
70 Self {
71 shape,
72 strides,
73 start_offset,
74 }
75 }
76
77 pub fn shape(&self) -> &Shape {
79 &self.shape
80 }
81
82 pub fn strides(&self) -> &[isize] {
84 &self.strides
85 }
86
87 pub fn start_offset(&self) -> usize {
89 self.start_offset
90 }
91
92 pub fn num_dims(&self) -> usize {
94 self.shape.num_dims()
95 }
96
97 pub fn num_elements(&self) -> usize {
99 self.shape.num_elements()
100 }
101
102 pub fn is_contiguous(&self) -> bool {
104 if self.shape.num_dims() == 0 || self.num_elements() == 0 {
105 return true;
106 }
107
108 if self.strides.iter().any(|&s| s <= 0) {
109 return false;
110 }
111
112 let mut expected_stride = 1isize;
113 for i in (0..self.shape.num_dims()).rev() {
114 if self.shape[i] == 1 {
115 continue;
116 }
117 if self.strides[i] != expected_stride {
118 return false;
119 }
120 match expected_stride.checked_mul(self.shape[i] as isize) {
121 Some(next) => expected_stride = next,
122 None => return false,
123 }
124 }
125 true
126 }
127
128 pub fn is_dense_unique_storage(&self) -> bool {
130 self.is_contiguous()
131 && !self
132 .strides
133 .iter()
134 .zip(self.shape.iter())
135 .any(|(&stride, &dim)| dim > 1 && stride == 0)
136 }
137
138 pub fn contiguous_offsets(&self) -> Option<(usize, usize)> {
140 if self.is_contiguous() {
141 Some((self.start_offset, self.start_offset + self.num_elements()))
142 } else {
143 None
144 }
145 }
146
147 pub fn transpose(&self, dim1: usize, dim2: usize) -> Self {
149 let mut dims = self.shape.to_vec();
150 let mut strides = self.strides.clone();
151 dims.swap(dim1, dim2);
152 strides.swap(dim1, dim2);
153 Self {
154 shape: Shape::from(dims),
155 strides,
156 start_offset: self.start_offset,
157 }
158 }
159
160 pub fn permute(&self, axes: &[usize]) -> Self {
164 debug_assert_eq!(
165 axes.len(),
166 self.num_dims(),
167 "permute: axes length must match number of dimensions"
168 );
169
170 let new_dims: Vec<usize> = axes.iter().map(|&i| self.shape[i]).collect();
171 let new_strides: Vec<isize> = axes.iter().map(|&i| self.strides[i]).collect();
172
173 Self {
174 shape: Shape::from(new_dims),
175 strides: new_strides,
176 start_offset: self.start_offset,
177 }
178 }
179
180 pub fn flip(&self, axes: &[usize]) -> Self {
185 let mut new_strides = self.strides.clone();
186 let mut offset_adjustment: isize = 0;
187
188 for &axis in axes {
189 debug_assert!(
190 axis < self.num_dims(),
191 "flip: axis {} out of bounds for {} dimensions",
192 axis,
193 self.num_dims()
194 );
195
196 let dim_size = self.shape[axis];
197 if dim_size > 1 {
198 offset_adjustment += (dim_size as isize - 1) * self.strides[axis];
200 new_strides[axis] = -new_strides[axis];
202 }
203 }
204
205 let new_start_isize = self.start_offset as isize + offset_adjustment;
206 debug_assert!(new_start_isize >= 0, "flip: negative offset");
207 let new_start = new_start_isize as usize;
208
209 Self {
210 shape: self.shape.clone(),
211 strides: new_strides,
212 start_offset: new_start,
213 }
214 }
215
216 pub fn narrow(&self, dim: usize, start: usize, len: usize) -> Self {
218 debug_assert!(
219 start + len <= self.shape[dim],
220 "narrow: start ({}) + len ({}) exceeds dimension size ({})",
221 start,
222 len,
223 self.shape[dim]
224 );
225 let mut dims = self.shape.to_vec();
226 dims[dim] = len;
227
228 let new_offset_isize = self.start_offset as isize + self.strides[dim] * start as isize;
229 debug_assert!(new_offset_isize >= 0, "narrow: negative offset");
230 let new_offset = new_offset_isize as usize;
231
232 Self {
233 shape: Shape::from(dims),
234 strides: self.strides.clone(),
235 start_offset: new_offset,
236 }
237 }
238
239 pub fn slice(&self, slices: &[Slice]) -> (Self, bool) {
245 let ndims = self.num_dims();
246 let mut new_dims = self.shape.to_vec();
247 let mut new_strides = self.strides.clone();
248 let mut new_offset = self.start_offset as isize;
249
250 for (dim, slice) in slices.iter().enumerate() {
251 if dim >= ndims {
252 break;
253 }
254
255 let dim_size = self.shape[dim] as isize;
256 let stride = self.strides[dim];
257
258 let start = if slice.start < 0 {
260 (dim_size + slice.start).max(0) as usize
261 } else {
262 (slice.start as usize).min(dim_size as usize)
263 };
264
265 let end = match slice.end {
269 Some(e) if e < 0 => (dim_size + e).max(0) as usize,
270 Some(e) => (e as usize).min(dim_size as usize),
271 None => dim_size as usize, };
273
274 let step = slice.step;
275 let abs_step = step.unsigned_abs();
276
277 let len = if end > start {
278 (end - start).div_ceil(abs_step)
279 } else {
280 0
281 };
282 new_dims[dim] = len;
283 new_strides[dim] = stride * step;
284
285 if step > 0 {
286 if len > 0 {
288 new_offset += stride * start as isize;
289 }
290 } else {
291 if len > 0 {
293 new_offset += (end as isize - 1) * stride;
294 }
295 }
296 }
297
298 debug_assert!(new_offset >= 0, "slice: negative offset");
299
300 (
301 Self {
302 shape: Shape::from(new_dims),
303 strides: new_strides,
304 start_offset: new_offset as usize,
305 },
306 false,
307 )
308 }
309
310 pub fn reshape(&self, new_shape: Shape) -> Option<Self> {
315 if !self.is_contiguous() {
316 return None;
317 }
318 debug_assert_eq!(
319 self.num_elements(),
320 new_shape.num_elements(),
321 "reshape must preserve total elements"
322 );
323 let mut layout = Self::contiguous(new_shape);
324 layout.start_offset = self.start_offset;
325 Some(layout)
326 }
327
328 pub fn index(&self, indices: &[usize]) -> usize {
330 debug_assert_eq!(indices.len(), self.num_dims());
331 let mut offset = self.start_offset as isize;
332 for (i, &idx) in indices.iter().enumerate() {
333 offset += idx as isize * self.strides[i];
334 }
335 debug_assert!(offset >= 0, "index: negative offset");
336 offset as usize
337 }
338
339 pub fn inner_stride(&self) -> usize {
343 self.strides.last().map(|s| s.unsigned_abs()).unwrap_or(1)
344 }
345
346 pub fn has_contiguous_inner(&self) -> bool {
349 self.inner_stride() == 1
350 }
351
352 pub fn as_2d_strides(&self) -> Option<(usize, usize, isize, isize)> {
355 if self.num_dims() != 2 {
356 return None;
357 }
358 Some((
359 self.shape[0],
360 self.shape[1],
361 self.strides[0],
362 self.strides[1],
363 ))
364 }
365
366 pub fn has_positive_strides(&self) -> bool {
368 self.strides.iter().all(|&s| s >= 0)
369 }
370
371 pub fn strided_blocks(&self) -> StridedBlocks<'_> {
381 let n = self.num_elements();
382 if n == 0 {
383 return StridedBlocks::Single { start: 0, len: 0 };
384 }
385
386 if self.is_contiguous() {
388 return StridedBlocks::Single {
389 start: self.start_offset,
390 len: n,
391 };
392 }
393
394 let ndims = self.num_dims();
397 let mut block_len = 1usize;
398 let mut expected_stride = 1isize;
399
400 for i in (0..ndims).rev() {
401 if self.strides[i] == expected_stride {
402 block_len *= self.shape[i];
403 expected_stride *= self.shape[i] as isize;
404 } else {
405 break;
406 }
407 }
408
409 if block_len == n {
410 return StridedBlocks::Single {
412 start: self.start_offset,
413 len: n,
414 };
415 }
416
417 let num_blocks = n / block_len;
418 StridedBlocks::Multiple {
419 layout: self,
420 block_len,
421 num_blocks,
422 }
423 }
424}
425
426#[derive(Debug, Clone)]
428pub enum StridedBlocks<'a> {
429 Single { start: usize, len: usize },
431 Multiple {
433 layout: &'a Layout,
434 block_len: usize,
435 num_blocks: usize,
436 },
437}
438
439impl<'a> StridedBlocks<'a> {
440 pub fn block_len(&self) -> usize {
442 match self {
443 Self::Single { len, .. } => *len,
444 Self::Multiple { block_len, .. } => *block_len,
445 }
446 }
447
448 pub fn block_starts(&self) -> BlockStartIter<'_> {
450 match self {
451 Self::Single { start, .. } => BlockStartIter::Single {
452 start: *start,
453 done: false,
454 },
455 Self::Multiple {
456 layout,
457 block_len,
458 num_blocks,
459 } => {
460 let ndims = layout.num_dims();
462 let mut outer_dims = 0;
463 let mut expected_stride = 1isize;
464
465 for i in (0..ndims).rev() {
466 if layout.strides[i] == expected_stride {
467 expected_stride *= layout.shape[i] as isize;
468 } else {
469 outer_dims = i + 1;
470 break;
471 }
472 }
473
474 BlockStartIter::Multiple {
475 layout,
476 multi_index: vec![0; outer_dims],
477 remaining: *num_blocks,
478 block_len: *block_len,
479 }
480 }
481 }
482 }
483}
484
485pub enum BlockStartIter<'a> {
487 Single {
488 start: usize,
489 done: bool,
490 },
491 Multiple {
492 layout: &'a Layout,
493 multi_index: Vec<usize>,
494 remaining: usize,
495 block_len: usize,
496 },
497}
498
499impl Iterator for BlockStartIter<'_> {
500 type Item = usize;
501
502 fn next(&mut self) -> Option<usize> {
503 match self {
504 Self::Single { start, done } => {
505 if *done {
506 None
507 } else {
508 *done = true;
509 Some(*start)
510 }
511 }
512 Self::Multiple {
513 layout,
514 multi_index,
515 remaining,
516 block_len: _,
517 } => {
518 if *remaining == 0 {
519 return None;
520 }
521
522 let outer_dims = multi_index.len();
524 let mut offset = layout.start_offset as isize;
525 for (i, &idx) in multi_index.iter().enumerate() {
526 offset += idx as isize * layout.strides[i];
527 }
528
529 *remaining -= 1;
530
531 let shape = &layout.shape;
533 for d in (0..outer_dims).rev() {
534 multi_index[d] += 1;
535 if multi_index[d] < shape[d] {
536 break;
537 }
538 multi_index[d] = 0;
539 }
540
541 Some(offset as usize)
542 }
543 }
544 }
545
546 fn size_hint(&self) -> (usize, Option<usize>) {
547 let len = match self {
548 Self::Single { done, .. } => {
549 if *done {
550 0
551 } else {
552 1
553 }
554 }
555 Self::Multiple { remaining, .. } => *remaining,
556 };
557 (len, Some(len))
558 }
559}
560
561impl ExactSizeIterator for BlockStartIter<'_> {}
562
563#[cfg(test)]
564mod tests {
565 use super::*;
566
567 #[test]
568 fn test_contiguous_layout() {
569 let layout = Layout::contiguous(Shape::from(vec![2, 3, 4]));
570 assert_eq!(layout.strides(), &[12, 4, 1]);
571 assert!(layout.is_contiguous());
572 }
573
574 #[test]
575 fn test_transpose() {
576 let layout = Layout::contiguous(Shape::from(vec![2, 3]));
577 let transposed = layout.transpose(0, 1);
578 assert_eq!(transposed.shape().to_vec(), vec![3, 2]);
579 assert_eq!(transposed.strides(), &[1, 3]);
580 assert!(!transposed.is_contiguous());
581 }
582
583 #[test]
584 fn test_narrow() {
585 let layout = Layout::contiguous(Shape::from(vec![4, 4]));
586 let narrowed = layout.narrow(0, 1, 2);
587 assert_eq!(narrowed.shape().to_vec(), vec![2, 4]);
588 assert_eq!(narrowed.start_offset(), 4);
589 }
590
591 #[test]
592 fn test_contiguous_offsets() {
593 let layout = Layout::contiguous(Shape::from(vec![2, 3]));
594 assert_eq!(layout.contiguous_offsets(), Some((0, 6)));
595 }
596
597 #[test]
598 fn test_index() {
599 let layout = Layout::contiguous(Shape::from(vec![2, 3]));
600 assert_eq!(layout.index(&[0, 0]), 0);
601 assert_eq!(layout.index(&[0, 2]), 2);
602 assert_eq!(layout.index(&[1, 0]), 3);
603 assert_eq!(layout.index(&[1, 2]), 5);
604 }
605
606 #[test]
607 fn test_flip_1d() {
608 let layout = Layout::contiguous(Shape::from(vec![4]));
611 let flipped = layout.flip(&[0]);
612
613 assert_eq!(flipped.shape().to_vec(), vec![4]);
614 assert_eq!(flipped.strides(), &[-1]);
615 assert_eq!(flipped.start_offset(), 3);
616
617 assert_eq!(flipped.index(&[0]), 3);
619 assert_eq!(flipped.index(&[1]), 2);
620 assert_eq!(flipped.index(&[2]), 1);
621 assert_eq!(flipped.index(&[3]), 0);
622 }
623
624 #[test]
625 fn test_flip_2d_axis0() {
626 let layout = Layout::contiguous(Shape::from(vec![2, 3]));
629 let flipped = layout.flip(&[0]);
630
631 assert_eq!(flipped.strides(), &[-3, 1]);
632 assert_eq!(flipped.start_offset(), 3);
633
634 assert_eq!(flipped.index(&[0, 0]), 3);
636 assert_eq!(flipped.index(&[0, 1]), 4);
637 assert_eq!(flipped.index(&[0, 2]), 5);
638 assert_eq!(flipped.index(&[1, 0]), 0);
640 assert_eq!(flipped.index(&[1, 1]), 1);
641 assert_eq!(flipped.index(&[1, 2]), 2);
642 }
643
644 #[test]
645 fn test_flip_2d_axis1() {
646 let layout = Layout::contiguous(Shape::from(vec![2, 3]));
649 let flipped = layout.flip(&[1]);
650
651 assert_eq!(flipped.strides(), &[3, -1]);
652 assert_eq!(flipped.start_offset(), 2);
653
654 assert_eq!(flipped.index(&[0, 0]), 2);
656 assert_eq!(flipped.index(&[0, 1]), 1);
657 assert_eq!(flipped.index(&[0, 2]), 0);
658 assert_eq!(flipped.index(&[1, 0]), 5);
659 assert_eq!(flipped.index(&[1, 1]), 4);
660 assert_eq!(flipped.index(&[1, 2]), 3);
661 }
662
663 #[test]
664 fn test_flip_both_axes() {
665 let layout = Layout::contiguous(Shape::from(vec![2, 3]));
667 let flipped = layout.flip(&[0, 1]);
668
669 assert_eq!(flipped.strides(), &[-3, -1]);
670 assert_eq!(flipped.start_offset(), 5); assert_eq!(flipped.index(&[0, 0]), 5);
673 assert_eq!(flipped.index(&[0, 1]), 4);
674 assert_eq!(flipped.index(&[0, 2]), 3);
675 assert_eq!(flipped.index(&[1, 0]), 2);
676 assert_eq!(flipped.index(&[1, 1]), 1);
677 assert_eq!(flipped.index(&[1, 2]), 0);
678 }
679}