Skip to main content

rten_tensor/
layout.rs

1//! Layouts which describe the shape and strides of a tensor.
2
3use std::iter::repeat_n;
4use std::ops::Range;
5
6use smallvec::{SmallVec, smallvec};
7
8use crate::errors::{DimensionError, ExpandError, FromDataError, ReshapeError, SliceError};
9use crate::index_iterator::{DynIndices, NdIndices};
10use crate::overlap::{is_contiguous, may_have_internal_overlap};
11use crate::slice_range::{IntoSliceItems, SliceItem};
12use crate::type_num::{OptionalUInt, U0, U1, U2, U3, U4, U5, Unknown};
13
14/// Return true if `permutation` is a valid permutation of dimensions for
15/// a tensor of rank `ndim`.
16pub fn is_valid_permutation(ndim: usize, permutation: &[usize]) -> bool {
17    permutation.len() == ndim
18        && (0..ndim).all(|dim| permutation.iter().filter(|d| **d == dim).count() == 1)
19}
20
21/// Merge dimensions of a layout where possible.
22///
23/// Two dimensions with sizes N1, N2 and strides S1, S2 can be merged into a
24/// single dimension with size N1 * N2 and stride S2 if S1 = N1 * S2;
25///
26/// Returns a vector of `(size, stride)` tuples for the merged dimensions.
27pub(crate) fn merge_axes<S: SizeArray, St: SizeArray>(
28    shape: &S,
29    strides: &St,
30) -> SmallVec<[(usize, usize); 4]> {
31    let last_dim = shape.len().saturating_sub(1);
32    let (Some(prev_size), Some(prev_stride)) = (shape.get(last_dim), strides.get(last_dim)) else {
33        return SmallVec::new();
34    };
35
36    let mut merged: SmallVec<[(usize, usize); 4]> = SmallVec::with_capacity(shape.len());
37    merged.push((prev_size, prev_stride));
38
39    for (outer_size, outer_stride) in shape.iter().zip(strides.iter()).rev().skip(1) {
40        let (inner_size, inner_stride) = merged.last_mut().unwrap();
41        let can_merge = outer_size == 1 || (outer_stride == *inner_stride * *inner_size);
42        if can_merge {
43            *inner_size *= outer_size;
44        } else {
45            merged.push((outer_size, outer_stride));
46        }
47    }
48
49    merged.reverse();
50
51    merged
52}
53
54/// Generate debug assertion that a dimension index is valid for a layout.
55macro_rules! debug_assert_dim_valid {
56    ($layout:ident, $dim:expr) => {
57        debug_assert!(
58            $dim < $layout.ndim(),
59            "dim {} out of bounds for tensor with {} dims",
60            $dim,
61            $layout.ndim()
62        )
63    };
64}
65
66/// Array of dimension sizes or strides.
67///
68/// This trait is an abstraction around slices and arrays of [`usize`]s. Unlike
69/// `AsRef<[usize]>` this does not require the data to actually be stored in
70/// memory. This allows for strides which are computed on-demand or stored in a
71/// smaller data type (eg. `u32`) and expanded to `usize`.
72pub trait SizeArray: Clone + std::fmt::Debug + PartialEq<Self> {
73    /// Return the length of the array.
74    fn len(&self) -> usize;
75
76    fn is_empty(&self) -> bool {
77        self.len() == 0
78    }
79
80    /// Return the `index`'th entry.
81    fn get(&self, index: usize) -> Option<usize>;
82
83    /// Return an iterator over the entries.
84    fn iter(&self) -> impl ExactSizeIterator<Item = usize> + DoubleEndedIterator;
85}
86
87impl<T: Clone + AsRef<[usize]> + std::fmt::Debug + PartialEq<Self>> SizeArray for T {
88    fn len(&self) -> usize {
89        self.as_ref().len()
90    }
91
92    fn get(&self, index: usize) -> Option<usize> {
93        self.as_ref().get(index).copied()
94    }
95
96    fn iter(&self) -> impl ExactSizeIterator<Item = usize> + DoubleEndedIterator {
97        self.as_ref().iter().copied()
98    }
99}
100
101/// Describes the shape and strides of a tensor.
102///
103/// The `Layout` trait provides methods to query the shape of a tensor, i.e. the
104/// number of dimensions and size of each, and the strides which determine the
105/// mapping between logical indices in the tensor and offsets in the data storage.
106///
107/// This trait is implemented for tensor types
108/// ([`TensorBase`](crate::TensorBase)), as well as the underlying layout types
109/// such as [`NdLayout`] (for tensors with a static number of dimensions) and
110/// [`DynLayout`] (for tensors with a dynamic number of dimensions).
111pub trait Layout {
112    /// Type used to represent the layout's shape.
113    type Shape<'a>: SizeArray
114    where
115        Self: 'a;
116
117    /// Type used to represent the layout's strides.
118    type Strides<'a>: SizeArray
119    where
120        Self: 'a;
121
122    /// Type used to represent indices.
123    type Index<'a>: AsRef<[usize]> + Clone + std::fmt::Debug + PartialEq<Self::Index<'a>>;
124
125    /// Iterator over indices in this tensor.
126    type Indices;
127
128    /// Map an index to a storage offset, without checking if it is valid for
129    /// the tensor's shape.
130    ///
131    /// This method is not itself unsafe, because it only computes a storage
132    /// offset but does not access any data. Using the offset to index into
133    /// storage without a bounds check is unsafe however.
134    fn offset_unchecked(&self, index: Self::Index<'_>) -> usize {
135        index
136            .as_ref()
137            .iter()
138            .zip(self.strides().iter())
139            .map(|(idx, stride)| *idx * stride)
140            .sum()
141    }
142
143    /// Map an index to a storage offset, or return `None` if the index is out
144    /// of bounds along any dimension.
145    ///
146    /// Offsets returned by this method must be less than the layout's minimum
147    /// storage length reported by [`min_data_len`](Layout::min_data_len).
148    /// If a layout also implements [`TrustedLayout`] then callers can rely
149    /// on this to avoid subsequent bounds checks.
150    fn offset(&self, index: Self::Index<'_>) -> Option<usize>;
151
152    /// Return the number of dimensions.
153    fn ndim(&self) -> usize;
154
155    /// Returns the number of elements in the array.
156    fn len(&self) -> usize;
157
158    /// Return true if this layout describes a contiguous tensor, where the
159    /// logical order of elements matches the order in which they are stored.
160    fn is_contiguous(&self) -> bool {
161        is_contiguous(&self.shape(), &self.strides())
162    }
163
164    /// Return true if iterating over elements in this layout will visit
165    /// elements multiple times.
166    fn is_broadcast(&self) -> bool {
167        !self.is_empty() && self.strides().iter().any(|s| s == 0)
168    }
169
170    /// Returns true if the array has no elements.
171    fn is_empty(&self) -> bool {
172        self.len() == 0
173    }
174
175    /// Returns an array of the sizes of each dimension.
176    fn shape(&self) -> Self::Shape<'_>;
177
178    /// Returns the size of the dimension `dim`.
179    fn size(&self, dim: usize) -> usize {
180        self.shape().get(dim).expect("invalid dim index")
181    }
182
183    /// Returns an array of the strides of each dimension.
184    fn strides(&self) -> Self::Strides<'_>;
185
186    /// Returns the offset between adjacent indices along dimension `dim`.
187    fn stride(&self, dim: usize) -> usize {
188        self.strides().get(dim).expect("invalid dimension")
189    }
190
191    /// Return an iterator over all valid indices in this tensor.
192    fn indices(&self) -> Self::Indices;
193
194    /// Return true if this layout's shape can be broadcast to the given shape.
195    fn can_broadcast_to(&self, target_shape: &[usize]) -> bool {
196        if self.ndim() > target_shape.len() {
197            return false;
198        }
199
200        // For two shapes to be compatible for broadcasting, each dimension must
201        // either be the same or be 1.
202        //
203        // If the tensor has fewer dimensions, pretend that it was prefixed with
204        // 1-length dimensions to make the dimension counts equal.
205        let target_dims = target_shape[target_shape.len() - self.shape().len()..]
206            .iter()
207            .copied();
208
209        self.shape()
210            .iter()
211            .zip(target_dims)
212            .all(|(a, b)| a == b || a == 1)
213    }
214
215    /// Return true if the tensor/view can be broadcast with another tensor or
216    /// view with a given `shape` as part of a binary operation.
217    ///
218    /// The shape of the result may be larger than either the current shape
219    /// or `shape`. eg. If a tensor of shape `[1, 5]` is broadcast with one
220    /// of size `[2, 1, 1]` the result has shape `[2, 1, 5]`.
221    ///
222    /// See <https://github.com/onnx/onnx/blob/main/docs/Broadcasting.md> for
223    /// conditions in which broadcasting is allowed.
224    fn can_broadcast_with(&self, shape: &[usize]) -> bool {
225        // For two shapes to be compatible for broadcasting, each dimension must
226        // either be the same or be 1.
227        //
228        // If the tensor has fewer dimensions, pretend that it was prefixed with
229        // 1-length dimensions to make the dimension counts equal.
230
231        let a = self.shape();
232        let b = shape;
233
234        let a_pad = b.len().saturating_sub(a.len());
235        let b_pad = a.len().saturating_sub(b.len());
236
237        let a_iter = a.iter().rev().chain(repeat_n(1, a_pad));
238        let b_iter = b.iter().copied().rev().chain(repeat_n(1, b_pad));
239
240        a_iter.zip(b_iter).all(|(a, b)| a == b || a == 1 || b == 1)
241    }
242
243    /// Return the minimum length required for the element data buffer used
244    /// with this layout.
245    fn min_data_len(&self) -> usize {
246        if self.shape().iter().any(|d| d == 0) {
247            return 0;
248        }
249        let max_offset: usize = self
250            .shape()
251            .iter()
252            .zip(self.strides().iter())
253            .map(|(size, stride)| (size - 1) * stride)
254            .sum();
255        max_offset + 1
256    }
257
258    /// Return a new layout formed by reshaping this one to `shape`.
259    ///
260    /// This has the same requirements as
261    /// [`reshaped_for_copy`](Layout::reshaped_for_copy) but also requires
262    /// that the layout is contiguous.
263    fn reshaped_for_view<S: IntoLayout>(&self, shape: S) -> Result<S::Layout, ReshapeError> {
264        if !self.is_contiguous() {
265            return Err(ReshapeError::NotContiguous);
266        }
267        self.reshaped_for_copy(shape)
268    }
269
270    /// Return a new layout formed by reshaping this one to `shape`.
271    fn reshaped_for_copy<S: IntoLayout>(&self, shape: S) -> Result<S::Layout, ReshapeError> {
272        let layout = shape.into_layout();
273        if layout.len() != self.len() {
274            return Err(ReshapeError::LengthMismatch);
275        }
276        Ok(layout)
277    }
278}
279
280/// A layout which upholds guarantees on returned storage offsets.
281///
282/// # Safety
283///
284/// Layouts which implement this trait promise that any offsets returned by
285/// [`offset`](Layout::offset) and
286/// [`offset_unchecked`](Layout::offset_unchecked) are less than the than the
287/// minimum required storage length reported by
288/// [`min_data_len`](Layout::min_data_len). This promise means that the offsets
289/// can be used to access elements in a buffer without a bounds check.
290pub unsafe trait TrustedLayout: Layout {}
291
292/// Extension methods for layouts.
293///
294/// These are separate from the [`Layout`] trait to prevent them from being
295/// overridden.
296pub(crate) trait LayoutExt: Layout {
297    /// Return the offset for an index or panic if invalid.
298    #[inline]
299    fn must_offset(&self, index: Self::Index<'_>) -> usize {
300        self.offset(index.clone()).unwrap_or_else(|| {
301            panic!(
302                "index {:?} out of bounds for shape {:?}",
303                index.as_ref(),
304                self.shape()
305            )
306        })
307    }
308}
309
310impl<L: Layout> LayoutExt for L {}
311
312/// Provides convenience methods for querying the shape and strides of a matrix.
313pub trait MatrixLayout {
314    fn rows(&self) -> usize;
315    fn cols(&self) -> usize;
316    fn row_stride(&self) -> usize;
317    fn col_stride(&self) -> usize;
318}
319
320/// Specifies whether a tensor or view may have an overlapping layout.
321///
322/// An overlapping layout is one in which multiple valid indices map to the same
323/// offset in storage. To comply with Rust's rules for mutable aliases, mutable
324/// tensors/views must disallow overlap.
325pub enum OverlapPolicy {
326    AllowOverlap,
327    DisallowOverlap,
328}
329
330/// Defines the valid indices for an N-dimensional array and how to map them
331/// to offsets in a linear buffer, where N is known at compile time.
332#[derive(Clone, Copy, Debug, PartialEq)]
333pub struct NdLayout<const N: usize> {
334    shape: [usize; N],
335    strides: [usize; N],
336}
337
338impl<const N: usize> Layout for NdLayout<N> {
339    type Shape<'a> = [usize; N];
340    type Strides<'a> = [usize; N];
341    type Index<'a> = [usize; N];
342    type Indices = NdIndices<N>;
343
344    fn ndim(&self) -> usize {
345        N
346    }
347
348    fn len(&self) -> usize {
349        self.shape.iter().product()
350    }
351
352    #[inline]
353    fn offset(&self, index: [usize; N]) -> Option<usize> {
354        if !self.index_valid(index) {
355            return None;
356        }
357        Some(self.offset_unchecked(index))
358    }
359
360    #[inline]
361    fn offset_unchecked(&self, index: [usize; N]) -> usize {
362        let mut offset = 0;
363        for i in 0..N {
364            offset += index[i] * self.strides[i];
365        }
366        offset
367    }
368
369    #[inline]
370    fn shape(&self) -> Self::Shape<'_> {
371        self.shape
372    }
373
374    #[inline]
375    fn strides(&self) -> [usize; N] {
376        self.strides
377    }
378
379    fn indices(&self) -> Self::Indices {
380        NdIndices::from_shape(self.shape)
381    }
382}
383
384unsafe impl<const N: usize> TrustedLayout for NdLayout<N> {}
385
386impl<L: Layout> Layout for &L {
387    type Shape<'b>
388        = L::Shape<'b>
389    where
390        Self: 'b;
391    type Strides<'b>
392        = L::Strides<'b>
393    where
394        Self: 'b;
395    type Index<'b> = L::Index<'b>;
396    type Indices = L::Indices;
397
398    fn ndim(&self) -> usize {
399        (*self).ndim()
400    }
401
402    fn len(&self) -> usize {
403        (*self).len()
404    }
405
406    fn offset(&self, index: Self::Index<'_>) -> Option<usize> {
407        (*self).offset(index)
408    }
409
410    fn offset_unchecked(&self, index: Self::Index<'_>) -> usize {
411        (*self).offset_unchecked(index)
412    }
413
414    fn shape(&self) -> Self::Shape<'_> {
415        (*self).shape()
416    }
417
418    fn strides(&self) -> Self::Strides<'_> {
419        (*self).strides()
420    }
421
422    fn indices(&self) -> Self::Indices {
423        (*self).indices()
424    }
425}
426
427// The `Layout` impl for references proxies to the target, so upholds invariants
428// if the target does.
429unsafe impl<L: TrustedLayout> TrustedLayout for &L {}
430
431impl MatrixLayout for NdLayout<2> {
432    #[inline]
433    fn rows(&self) -> usize {
434        self.size(0)
435    }
436
437    #[inline]
438    fn cols(&self) -> usize {
439        self.size(1)
440    }
441
442    #[inline]
443    fn row_stride(&self) -> usize {
444        self.stride(0)
445    }
446
447    #[inline]
448    fn col_stride(&self) -> usize {
449        self.stride(1)
450    }
451}
452
453/// Compute the shape and strides of a layout after slicing with `range`.
454///
455/// Returns an `(ndim, offset)` tuple for the number of dimensions in the
456/// slice and the offset of the first element in the parent view's data.
457///
458/// This function is generic to allow for specialized variants to be generated
459/// when slicing with statically known input or output shape sizes.
460fn slice_layout(
461    in_shape: impl AsRef<[usize]>,
462    in_strides: impl AsRef<[usize]>,
463    mut out_shape: impl AsMut<[usize]>,
464    mut out_strides: impl AsMut<[usize]>,
465    range: &[SliceItem],
466) -> Result<(usize, usize), SliceError> {
467    let in_shape = in_shape.as_ref();
468    let out_shape = out_shape.as_mut();
469    let out_strides = out_strides.as_mut();
470
471    let mut ndim = 0;
472    let mut offset = 0;
473
474    for (in_dim, (&size, &stride)) in in_shape.iter().zip(in_strides.as_ref()).enumerate() {
475        let (offset_adjust, new_size_stride) = match range.get(in_dim) {
476            Some(&SliceItem::Index(idx)) => {
477                let pos_idx = if idx >= 0 { idx } else { idx + size as isize };
478                if pos_idx < 0 || pos_idx >= size as isize {
479                    return Err(SliceError::InvalidIndex {
480                        axis: in_dim,
481                        index: idx,
482                        size,
483                    });
484                }
485                (stride * pos_idx as usize, None)
486            }
487            Some(SliceItem::Range(range)) => {
488                let resolved = range.resolve(size).ok_or(SliceError::InvalidRange {
489                    axis: in_dim,
490                    range: *range,
491                    size,
492                })?;
493                let step: usize = range
494                    .step()
495                    .try_into()
496                    .map_err(|_| SliceError::InvalidStep {
497                        axis: in_dim,
498                        step: range.step(),
499                    })?;
500                let new_size = if step == 1 {
501                    // Fast path when no custom step is used.
502                    resolved.end - resolved.start
503                } else {
504                    range.index_range(size).steps()
505                };
506                let new_stride = stride * step;
507                (stride * resolved.start, Some((new_size, new_stride)))
508            }
509            None => (0, Some((size, stride))),
510        };
511
512        offset += offset_adjust;
513        if let Some((new_size, new_stride)) = new_size_stride {
514            out_shape[ndim] = new_size;
515            out_strides[ndim] = new_stride;
516            ndim += 1;
517        }
518    }
519
520    if out_shape.contains(&0) {
521        offset = 0;
522    }
523
524    Ok((ndim, offset))
525}
526
527/// Return an iterator over the strides of a layout that broadcasts a view
528/// with shape `from_shape` and strides `from_strides` to `to_shape`.
529fn broadcast_strides<'a>(
530    from_shape: &'a [usize],
531    from_strides: &'a [usize],
532    to_shape: &'a [usize],
533) -> impl Iterator<Item = usize> + 'a {
534    let pad = to_shape.len() - from_shape.len();
535    repeat_n(0, pad).chain(from_shape.iter().zip(from_strides).enumerate().map(
536        move |(i, (size, stride))| {
537            if *size == 1 && to_shape[i + pad] > 1 {
538                0
539            } else {
540                *stride
541            }
542        },
543    ))
544}
545
546impl<const N: usize> NdLayout<N> {
547    /// Convert this layout to one with a dynamic rank.
548    pub fn as_dyn(&self) -> DynLayout {
549        self.into()
550    }
551
552    /// Return true if all components of `index` are in-bounds.
553    fn index_valid(&self, index: [usize; N]) -> bool {
554        let mut valid = true;
555        for i in 0..N {
556            valid = valid && index[i] < self.shape[i]
557        }
558        valid
559    }
560
561    /// Return the strides that a contiguous layout with a given shape would
562    /// have.
563    fn contiguous_strides(shape: [usize; N]) -> [usize; N] {
564        let mut strides = [0; N];
565        for i in 0..N {
566            strides[i] = shape[i + 1..].iter().product();
567        }
568        strides
569    }
570}
571
572impl<'a, const N: usize> TryFrom<&'a DynLayout> for NdLayout<N> {
573    type Error = DimensionError;
574
575    /// Convert a dynamic layout into a static layout with N dims. Fails if
576    /// `value.ndim() != N`.
577    fn try_from(value: &'a DynLayout) -> Result<NdLayout<N>, DimensionError> {
578        let shape = value.shape();
579        let shape: [usize; N] = shape.try_into().map_err(|_| DimensionError {
580            actual: shape.len(),
581            expected: N,
582        })?;
583        let strides = value.strides();
584        let strides: [usize; N] = strides.try_into().map_err(|_| DimensionError {
585            actual: strides.len(),
586            expected: N,
587        })?;
588        Ok(NdLayout { shape, strides })
589    }
590}
591
592/// Defines the valid indices for an N-dimensional array and how to map them
593/// to offsets in a linear buffer, where N can be varied at runtime.
594///
595/// The layout specifies the size of each dimension of the tensor (the _shape_)
596/// and the stride (gap) between offsets in each dimension.
597#[derive(Debug, PartialEq)]
598pub struct DynLayout {
599    /// Array of dimension sizes followed by the corresponding dimension strides.
600    ///
601    /// Since we always have the same number of stride and shape dims, these
602    /// are combined into one array to avoid redundantly storing separate
603    /// lengths for each.
604    shape_and_strides: SmallVec<[usize; 8]>,
605}
606
607impl Clone for DynLayout {
608    fn clone(&self) -> DynLayout {
609        DynLayout {
610            // We implement `Clone` manually here so we can clone
611            // `shape_and_strides` using `SmallVec::from_slice` instead of
612            // `SmallVec::from`. This is faster for `Copy` types.
613            shape_and_strides: SmallVec::from_slice(self.shape_and_strides.as_slice()),
614        }
615    }
616}
617
618impl Layout for DynLayout {
619    type Shape<'a> = &'a [usize];
620    type Strides<'a> = &'a [usize];
621    type Index<'a> = &'a [usize];
622    type Indices = DynIndices;
623
624    /// Return the number of elements in the tensor shape described by this layout.
625    fn len(&self) -> usize {
626        self.shape().iter().product()
627    }
628
629    #[inline]
630    fn offset(&self, index: Self::Index<'_>) -> Option<usize> {
631        let shape = self.shape();
632        let strides = self.strides();
633        let mut valid = index.as_ref().len() == shape.len();
634        let mut offset = 0;
635        for (idx, (size, stride)) in index.as_ref().iter().zip(shape.iter().zip(strides)) {
636            valid = valid && idx < size;
637            offset += idx * stride;
638        }
639        valid.then_some(offset)
640    }
641
642    fn is_empty(&self) -> bool {
643        self.len() == 0
644    }
645
646    /// Return the number of dimensions.
647    #[inline]
648    fn ndim(&self) -> usize {
649        self.shape_and_strides.len() / 2
650    }
651
652    /// Return the sizes of each dimension.
653    #[inline]
654    fn shape(&self) -> &[usize] {
655        &self.shape_and_strides[0..self.ndim()]
656    }
657
658    /// Returns the size of the dimension `dim`.
659    #[inline]
660    fn size(&self, dim: usize) -> usize {
661        debug_assert_dim_valid!(self, dim);
662        self.shape_and_strides[dim]
663    }
664
665    /// Return the stride (offset between elements) in the tensor's element array.
666    #[inline]
667    fn strides(&self) -> Self::Strides<'_> {
668        &self.shape_and_strides[self.ndim()..]
669    }
670
671    /// Return the stride for a specific dimension.
672    #[inline]
673    fn stride(&self, dim: usize) -> usize {
674        debug_assert_dim_valid!(self, dim);
675        self.shape_and_strides[self.ndim() + dim]
676    }
677
678    fn indices(&self) -> DynIndices {
679        DynIndices::from_shape(self.shape())
680    }
681}
682
683unsafe impl TrustedLayout for DynLayout {}
684
685impl DynLayout {
686    pub fn make_contiguous(&mut self) {
687        self.shape_and_strides = Self::contiguous_shape_and_strides(self.shape());
688    }
689
690    fn permute_iter<I: Clone + Iterator<Item = usize>>(&mut self, dims: I) {
691        let shape_iter = dims.clone().map(|dim| self.size(dim));
692        let stride_iter = dims.map(|dim| self.stride(dim));
693        self.shape_and_strides = shape_iter.chain(stride_iter).collect();
694    }
695
696    /// Swap the order of dimensions in this layout to the order described by
697    /// `dims`.
698    fn permute(&mut self, dims: &[usize]) {
699        assert!(
700            is_valid_permutation(self.ndim(), dims),
701            "permutation is invalid"
702        );
703        self.permute_iter(dims.iter().copied());
704    }
705
706    /// Reverse the order of dimensions in this layout.
707    fn transpose(&mut self) {
708        self.permute_iter((0..self.ndim()).rev());
709    }
710
711    /// Create a shape-and-strides array for a contiguous layout.
712    fn contiguous_shape_and_strides(shape: &[usize]) -> SmallVec<[usize; 8]> {
713        let mut strides_and_shape: SmallVec<[usize; 8]> = SmallVec::from_slice(shape);
714        strides_and_shape.resize(shape.len() * 2, 0);
715        let mut stride = 1;
716        for i in (0..shape.len()).rev() {
717            strides_and_shape[shape.len() + i] = stride;
718            stride *= shape[i];
719        }
720        strides_and_shape
721    }
722}
723
724impl<L: Layout> From<&L> for DynLayout {
725    fn from(layout: &L) -> DynLayout {
726        let shape: SmallVec<[usize; 5]> = layout.shape().iter().collect();
727        let strides: SmallVec<[usize; 5]> = layout.strides().iter().collect();
728        DynLayout::from_shape_and_strides(&shape, &strides, OverlapPolicy::AllowOverlap)
729            .expect("invalid layout")
730    }
731}
732
733impl<const N: usize> From<NdLayout<N>> for DynLayout {
734    fn from(value: NdLayout<N>) -> DynLayout {
735        Self::from(&value)
736    }
737}
738
739/// MutLayout extends [`Layout`] with methods for creating, modifying and
740/// transforming layouts.
741///
742/// ## Strides and internal overlap
743///
744/// Rust requires that only one mutable reference can exist for any value. When
745/// creating mutable tensor views or iterators, it is therefore important to
746/// know whether multiple elements in the layout may map to the same offset.
747///
748/// Accurately checking this for arbitrary shape and strides is non-trivial. See
749/// notes in `mem_overlap.c` in the NumPy source. RTen handles this by using
750/// a conservative check for internal overlap when constructing a layout from
751/// arbitrary strides. Specifically it sorts dimensions by decreasing stride and
752/// then verifies that each dimension fully "steps over" the next one. This
753/// allows for layouts which are transposed or have been sliced, but disallows
754/// some more complex non-overlapping constructions.
755///
756/// When constructing a layout via
757/// [`from_shape_and_strides`](MutLayout::from_shape_and_strides) the intended
758/// usage is specified via an [`OverlapPolicy`].
759pub trait MutLayout: Layout + Clone {
760    /// Create a layout with custom strides.
761    ///
762    /// The strides specify the offset gap between successive entries along a
763    /// given axis. `overlap` controls whether the layout is allowed to map
764    /// multiple indices to the same element. This can be true for immutable
765    /// views, but must be false for tensors or views that are mutable.
766    fn from_shape_and_strides(
767        shape: Self::Shape<'_>,
768        strides: Self::Strides<'_>,
769        overlap: OverlapPolicy,
770    ) -> Result<Self, FromDataError>;
771
772    /// Slice a layout by selecting a single entry from a given axis.
773    ///
774    /// Returns an `(offset_range, layout)` tuple for the sliced layout.
775    fn index_axis(&self, axis: usize, index: usize) -> (Range<usize>, <Self as RemoveDim>::Output)
776    where
777        Self: RemoveDim,
778    {
779        assert!(axis < self.ndim());
780        assert!(index < self.size(axis));
781
782        let layout = self.remove_dim(axis);
783
784        let range = if layout.is_empty() {
785            0..0
786        } else {
787            let start_offset = self.stride(axis) * index;
788            start_offset..start_offset + layout.min_data_len()
789        };
790
791        (range, layout)
792    }
793
794    /// Move the axis at position `from` to `to` by swapping their strides.
795    fn move_axis(&mut self, from: usize, to: usize);
796
797    /// Return a layout with the axes permuted according to the given order.
798    fn permuted(&self, order: Self::Index<'_>) -> Self;
799
800    // Modify the size of a dimension. This does not alter the strides.
801    fn resize_dim(&mut self, dim: usize, size: usize);
802
803    /// Reverse the order of dimensions. This is equivalent to
804    /// `self.permuted([N-1, N-2, ... 0])`.
805    fn transposed(&self) -> Self;
806
807    /// Slice the layout and return a static-rank layout.
808    ///
809    /// Returns a tuple of `(offset_range, sliced_layout)`.
810    fn slice<const M: usize>(
811        &self,
812        range: &[SliceItem],
813    ) -> Result<(Range<usize>, NdLayout<M>), SliceError>;
814
815    /// Slice the layout and return a dynamic rank layout.
816    ///
817    /// Returns a tuple of `(offset_range, sliced_layout)`.
818    fn slice_dyn(&self, range: &[SliceItem]) -> Result<(Range<usize>, DynLayout), SliceError>;
819
820    /// Slice the layout along a given axis.
821    ///
822    /// Returns a tuple of `(offset_range, sliced_layout)`.
823    fn slice_axis(
824        &self,
825        axis: usize,
826        range: Range<usize>,
827    ) -> Result<(Range<usize>, Self), SliceError> {
828        if axis >= self.ndim() {
829            return Err(SliceError::InvalidAxis { axis });
830        }
831        if range.end < range.start || range.end > self.size(axis) {
832            return Err(SliceError::InvalidRange {
833                axis,
834                range: range.into(),
835                size: self.size(axis),
836            });
837        }
838
839        let mut sliced_layout = self.clone();
840        sliced_layout.resize_dim(axis, range.len());
841        let range = if sliced_layout.is_empty() {
842            0..0
843        } else {
844            let start_offset = range.start * sliced_layout.stride(axis);
845            let end_offset = start_offset + sliced_layout.min_data_len();
846            start_offset..end_offset
847        };
848        Ok((range, sliced_layout))
849    }
850
851    /// Return a layout with all size-one dimensions removed.
852    fn squeezed(&self) -> DynLayout;
853
854    /// Split the layout along the given axis into two.
855    ///
856    /// Returns a tuple of `(left, right)` where each item is an `(offset_range,
857    /// layout)` tuple.
858    fn split(&self, axis: usize, mid: usize) -> ((Range<usize>, Self), (Range<usize>, Self));
859}
860
861/// Trait for creating a layout from a shape.
862pub trait FromShape: Layout {
863    /// Create a new contiguous layout with a given shape.
864    fn from_shape(shape: Self::Shape<'_>) -> Self;
865}
866
867/// Trait for broadcasting a layout from one shape to another.
868pub trait BroadcastLayout<L: Layout> {
869    /// Broadcast the `self` layout to a given shape.
870    fn broadcast<S: IntoLayout<Layout = L>>(&self, shape: S) -> Result<L, ExpandError>;
871}
872
873impl<const N: usize, const M: usize> BroadcastLayout<NdLayout<M>> for NdLayout<N> {
874    fn broadcast<S: IntoLayout<Layout = NdLayout<M>>>(
875        &self,
876        shape: S,
877    ) -> Result<NdLayout<M>, ExpandError> {
878        let shape: [usize; M] = shape.as_ref().try_into().unwrap();
879        if !self.can_broadcast_to(&shape) {
880            return Err(ExpandError::ShapeMismatch);
881        }
882        let mut strides = [0usize; M];
883        for (i, stride) in broadcast_strides(&self.shape(), &self.strides(), &shape).enumerate() {
884            strides[i] = stride;
885        }
886
887        Ok(NdLayout { shape, strides })
888    }
889}
890
891impl<const N: usize> BroadcastLayout<DynLayout> for NdLayout<N> {
892    fn broadcast<S: IntoLayout<Layout = DynLayout>>(
893        &self,
894        shape: S,
895    ) -> Result<DynLayout, ExpandError> {
896        let dyn_layout: DynLayout = self.into();
897        dyn_layout.broadcast(shape.as_ref())
898    }
899}
900
901impl BroadcastLayout<DynLayout> for DynLayout {
902    fn broadcast<S: IntoLayout<Layout = DynLayout>>(
903        &self,
904        shape: S,
905    ) -> Result<DynLayout, ExpandError> {
906        let to_shape = shape.as_ref();
907
908        if !self.can_broadcast_to(to_shape) {
909            return Err(ExpandError::ShapeMismatch);
910        }
911
912        let mut shape_and_strides = SmallVec::with_capacity(to_shape.len() * 2);
913        shape_and_strides.extend(to_shape.iter().copied());
914        shape_and_strides.extend(broadcast_strides(self.shape(), self.strides(), to_shape));
915
916        Ok(DynLayout { shape_and_strides })
917    }
918}
919
920impl<const N: usize> BroadcastLayout<NdLayout<N>> for DynLayout {
921    fn broadcast<S: IntoLayout<Layout = NdLayout<N>>>(
922        &self,
923        shape: S,
924    ) -> Result<NdLayout<N>, ExpandError> {
925        let dyn_broadcast = self.broadcast(shape.as_ref())?;
926        let layout = (&dyn_broadcast)
927            .try_into()
928            .map_err(|_| ExpandError::ShapeMismatch)?;
929        Ok(layout)
930    }
931}
932
933impl<const N: usize> FromShape for NdLayout<N> {
934    fn from_shape(shape: [usize; N]) -> Self {
935        Self {
936            shape,
937            strides: Self::contiguous_strides(shape),
938        }
939    }
940}
941
942impl<const N: usize> MutLayout for NdLayout<N> {
943    fn from_shape_and_strides(
944        shape: Self::Shape<'_>,
945        strides: Self::Strides<'_>,
946        overlap: OverlapPolicy,
947    ) -> Result<Self, FromDataError> {
948        let layout = NdLayout { shape, strides };
949
950        match overlap {
951            OverlapPolicy::DisallowOverlap => {
952                if may_have_internal_overlap(layout.shape, layout.strides) {
953                    return Err(FromDataError::MayOverlap);
954                }
955            }
956            OverlapPolicy::AllowOverlap => {}
957        }
958
959        Ok(layout)
960    }
961
962    fn move_axis(&mut self, from: usize, to: usize) {
963        assert!(from < N && to < N);
964        let mut dyn_layout = self.as_dyn();
965        dyn_layout.move_axis(from, to);
966        *self = NdLayout::try_from(&dyn_layout).unwrap();
967    }
968
969    fn permuted(&self, dims: [usize; N]) -> NdLayout<N> {
970        assert!(is_valid_permutation(N, &dims), "permutation is invalid");
971        let mut shape = [0; N];
972        let mut strides = [0; N];
973        for i in 0..N {
974            shape[i] = self.shape[dims[i]];
975            strides[i] = self.strides[dims[i]];
976        }
977        NdLayout { shape, strides }
978    }
979
980    fn resize_dim(&mut self, dim: usize, size: usize) {
981        self.shape[dim] = size;
982    }
983
984    fn transposed(&self) -> NdLayout<N> {
985        let dims = std::array::from_fn(|i| N - i - 1);
986        self.permuted(dims)
987    }
988
989    fn slice<const M: usize>(
990        &self,
991        range: &[SliceItem],
992    ) -> Result<(Range<usize>, NdLayout<M>), SliceError> {
993        if self.ndim() < range.len() {
994            return Err(SliceError::TooManyDims {
995                ndim: self.ndim(),
996                range_ndim: range.len(),
997            });
998        }
999
1000        let mut shape: [usize; M] = [0; M];
1001        let mut strides: [usize; M] = [0; M];
1002
1003        let (ndim, offset) =
1004            slice_layout(self.shape, self.strides, &mut shape, &mut strides, range)?;
1005
1006        if ndim != M {
1007            return Err(SliceError::OutputDimsMismatch {
1008                actual: ndim,
1009                expected: M,
1010            });
1011        }
1012
1013        let layout = NdLayout { shape, strides };
1014        Ok((offset..offset + layout.min_data_len(), layout))
1015    }
1016
1017    fn slice_dyn(&self, range: &[SliceItem]) -> Result<(Range<usize>, DynLayout), SliceError> {
1018        self.as_dyn().slice_dyn(range)
1019    }
1020
1021    fn squeezed(&self) -> DynLayout {
1022        self.as_dyn().squeezed()
1023    }
1024
1025    fn split(&self, axis: usize, mid: usize) -> ((Range<usize>, Self), (Range<usize>, Self)) {
1026        assert!(axis < self.ndim());
1027        assert!(mid <= self.size(axis));
1028
1029        let left_shape = std::array::from_fn(|i| if i == axis { mid } else { self.shape[i] });
1030        let right_shape = std::array::from_fn(|i| {
1031            if i == axis {
1032                self.size(axis) - mid
1033            } else {
1034                self.shape[i]
1035            }
1036        });
1037
1038        let left = NdLayout {
1039            shape: left_shape,
1040            strides: self.strides,
1041        };
1042        let right = NdLayout {
1043            shape: right_shape,
1044            strides: self.strides,
1045        };
1046
1047        let mid_offset = mid * self.strides[axis];
1048        let left_offsets = 0..left.min_data_len();
1049        let end_offset = self.min_data_len();
1050
1051        let right_offsets = if right.is_empty() {
1052            end_offset..end_offset
1053        } else {
1054            mid_offset..end_offset
1055        };
1056
1057        ((left_offsets, left), (right_offsets, right))
1058    }
1059}
1060
1061impl FromShape for DynLayout {
1062    fn from_shape(shape: &[usize]) -> Self {
1063        DynLayout {
1064            shape_and_strides: Self::contiguous_shape_and_strides(shape),
1065        }
1066    }
1067}
1068
1069impl MutLayout for DynLayout {
1070    fn from_shape_and_strides(
1071        shape: &[usize],
1072        strides: &[usize],
1073        overlap: OverlapPolicy,
1074    ) -> Result<Self, FromDataError> {
1075        let mut shape_and_strides = SmallVec::with_capacity(shape.len() + strides.len());
1076        shape_and_strides.extend_from_slice(shape);
1077        shape_and_strides.extend_from_slice(strides);
1078        let layout = DynLayout { shape_and_strides };
1079
1080        match overlap {
1081            OverlapPolicy::DisallowOverlap => {
1082                if may_have_internal_overlap(layout.shape(), layout.strides()) {
1083                    return Err(FromDataError::MayOverlap);
1084                }
1085            }
1086            OverlapPolicy::AllowOverlap => {}
1087        }
1088
1089        Ok(layout)
1090    }
1091
1092    fn move_axis(&mut self, from: usize, to: usize) {
1093        let ndim = self.ndim();
1094        assert!(from < ndim && to < ndim);
1095
1096        let size = self.shape_and_strides.remove(from);
1097        let stride = self.shape_and_strides.remove(ndim - 1 + from);
1098        self.shape_and_strides.insert(to, size);
1099        self.shape_and_strides.insert(ndim + to, stride);
1100    }
1101
1102    fn permuted(&self, order: &[usize]) -> DynLayout {
1103        let mut permuted = self.clone();
1104        permuted.permute(order);
1105        permuted
1106    }
1107
1108    fn resize_dim(&mut self, dim: usize, size: usize) {
1109        self.shape_and_strides[dim] = size;
1110    }
1111
1112    fn transposed(&self) -> DynLayout {
1113        let mut transposed = self.clone();
1114        transposed.transpose();
1115        transposed
1116    }
1117
1118    fn slice<const M: usize>(
1119        &self,
1120        range: &[SliceItem],
1121    ) -> Result<(Range<usize>, NdLayout<M>), SliceError> {
1122        let (offset_range, dyn_layout) = self.slice_dyn(range)?;
1123        let nd_layout =
1124            NdLayout::try_from(&dyn_layout).map_err(|_| SliceError::OutputDimsMismatch {
1125                actual: dyn_layout.ndim(),
1126                expected: M,
1127            })?;
1128        Ok((offset_range, nd_layout))
1129    }
1130
1131    fn slice_dyn(&self, range: &[SliceItem]) -> Result<(Range<usize>, DynLayout), SliceError> {
1132        if self.ndim() < range.len() {
1133            return Err(SliceError::TooManyDims {
1134                ndim: self.ndim(),
1135                range_ndim: range.len(),
1136            });
1137        }
1138
1139        let out_dims = self.ndim()
1140            - range
1141                .iter()
1142                .filter(|item| matches!(item, SliceItem::Index(_)))
1143                .count();
1144        let mut shape_and_strides = smallvec![0; out_dims * 2];
1145        let (out_shape, out_strides) = shape_and_strides.as_mut_slice().split_at_mut(out_dims);
1146
1147        let (_ndim, offset) =
1148            slice_layout(self.shape(), self.strides(), out_shape, out_strides, range)?;
1149
1150        let layout = Self { shape_and_strides };
1151        Ok((offset..offset + layout.min_data_len(), layout))
1152    }
1153
1154    fn squeezed(&self) -> DynLayout {
1155        let shape = self.shape().iter().copied().filter(|&size| size != 1);
1156        let strides = self
1157            .shape()
1158            .iter()
1159            .zip(self.strides())
1160            .filter_map(|(&size, &stride)| if size != 1 { Some(stride) } else { None });
1161        DynLayout {
1162            shape_and_strides: shape.chain(strides).collect(),
1163        }
1164    }
1165
1166    fn split(&self, axis: usize, mid: usize) -> ((Range<usize>, Self), (Range<usize>, Self)) {
1167        assert!(axis < self.ndim());
1168        assert!(mid <= self.size(axis));
1169
1170        let mut left_shape_strides: SmallVec<[usize; 8]> = (0..self.ndim())
1171            .map(|i| if i == axis { mid } else { self.size(i) })
1172            .collect();
1173        left_shape_strides.extend_from_slice(self.strides());
1174
1175        let mut right_shape_strides: SmallVec<[usize; 8]> = (0..self.ndim())
1176            .map(|i| {
1177                if i == axis {
1178                    self.size(axis) - mid
1179                } else {
1180                    self.size(i)
1181                }
1182            })
1183            .collect();
1184        right_shape_strides.extend_from_slice(self.strides());
1185
1186        let left = DynLayout {
1187            shape_and_strides: left_shape_strides,
1188        };
1189        let right = DynLayout {
1190            shape_and_strides: right_shape_strides,
1191        };
1192
1193        let mid_offset = mid * self.stride(axis);
1194        let left_offsets = 0..left.min_data_len();
1195        let end_offset = self.min_data_len();
1196
1197        let right_offsets = if right.is_empty() {
1198            end_offset..end_offset
1199        } else {
1200            mid_offset..end_offset
1201        };
1202
1203        ((left_offsets, left), (right_offsets, right))
1204    }
1205}
1206
1207/// Trait for shapes which can be used to create a contiguous layout.
1208///
1209/// This is implemented for `[usize; N]` for creating static-rank layouts from
1210/// arrays, and `&[usize]` for creating dynamic-rank layouts from slices.
1211pub trait IntoLayout: AsRef<[usize]> + std::fmt::Debug {
1212    /// The type of layout produced from this shape.
1213    type Layout: MutLayout;
1214
1215    /// Convert this shape into a contiguous layout.
1216    fn into_layout(self) -> Self::Layout;
1217}
1218
1219impl<const N: usize> IntoLayout for [usize; N] {
1220    type Layout = NdLayout<N>;
1221
1222    #[inline]
1223    fn into_layout(self) -> NdLayout<N> {
1224        NdLayout::from_shape(self)
1225    }
1226}
1227
1228impl IntoLayout for &[usize] {
1229    type Layout = DynLayout;
1230
1231    #[inline]
1232    fn into_layout(self) -> DynLayout {
1233        DynLayout::from_shape(self)
1234    }
1235}
1236
1237/// Trait which extends [`MutLayout`] with support for changing the number of
1238/// dimensions in-place.
1239///
1240/// This is only implemented for [`DynLayout`], since layouts that have a static
1241/// rank cannot change their dimension count at runtime.
1242pub trait ResizeLayout: MutLayout {
1243    /// Insert a size-one axis at the given index in the shape. This will have
1244    /// the same stride as the dimension that follows it.
1245    fn insert_axis(&mut self, index: usize);
1246
1247    /// Remove a size-1 axis at the given index.
1248    ///
1249    /// Since the axis has size one, this does not alter the number of elements
1250    /// in the layout or the order in which they are visited.
1251    ///
1252    /// Panics if the axis does not have a size of 1.
1253    #[track_caller]
1254    fn remove_axis(&mut self, index: usize) {
1255        assert!(
1256            self.size(index) == 1,
1257            "cannot remove axis of size {}",
1258            self.size(index)
1259        );
1260        self.remove_axis_of_any_size(index)
1261    }
1262
1263    /// Remove an axis that may have any size.
1264    ///
1265    /// If the size of the axis is not one, this will "remove" elements from
1266    /// the layout.
1267    fn remove_axis_of_any_size(&mut self, index: usize);
1268
1269    /// Merge consecutive axes where possible.
1270    ///
1271    /// This "simplifies" the layout by minimizing the number of dimensions
1272    /// while preserving the iteration order.
1273    fn merge_axes(&mut self);
1274}
1275
1276impl ResizeLayout for DynLayout {
1277    fn insert_axis(&mut self, index: usize) {
1278        let ndim = self.ndim();
1279        let new_size = 1;
1280
1281        // Choose stride for new dimension as if we were inserting it at the
1282        // beginning. If `dim != 0` then the result is as if we inserted the
1283        // dim at the start and then permuted the layout.
1284        let (max_stride, size_for_max_stride) = self
1285            .strides()
1286            .iter()
1287            .copied()
1288            .zip(self.shape().iter().copied())
1289            .max_by_key(|(stride, _size)| *stride)
1290            .unwrap_or((1, 1));
1291        let new_stride = max_stride * size_for_max_stride;
1292
1293        self.shape_and_strides.insert(index, new_size);
1294        self.shape_and_strides.insert(ndim + 1 + index, new_stride);
1295    }
1296
1297    fn remove_axis_of_any_size(&mut self, index: usize) {
1298        self.shape_and_strides.remove(index);
1299        self.shape_and_strides.remove(self.ndim() + index);
1300    }
1301
1302    fn merge_axes(&mut self) {
1303        let merged = merge_axes(&self.shape(), &self.strides());
1304        self.shape_and_strides = merged
1305            .iter()
1306            .map(|dim| dim.0)
1307            .chain(merged.iter().map(|dim| dim.1))
1308            .collect();
1309    }
1310}
1311
1312/// Trait for converting types into indices for use with a given layout.
1313///
1314/// Static-rank tensors can be indexed with `[usize; N]` arrays. Dynamic-rank
1315/// tensors can be indexed with any type that can be converted to an `&[usize]`
1316/// slice.
1317pub trait AsIndex<L: Layout> {
1318    /// Convert `self` into an index for use the layout `L`.
1319    fn as_index(&self) -> L::Index<'_>;
1320}
1321
1322impl<T: AsRef<[usize]>> AsIndex<DynLayout> for T {
1323    fn as_index(&self) -> &[usize] {
1324        self.as_ref()
1325    }
1326}
1327
1328impl<const N: usize> AsIndex<NdLayout<N>> for [usize; N] {
1329    fn as_index(&self) -> [usize; N] {
1330        *self
1331    }
1332}
1333
1334impl AsIndex<NdLayout<1>> for usize {
1335    fn as_index(&self) -> [usize; 1] {
1336        [*self]
1337    }
1338}
1339
1340/// Trait for converting types into shapes for a given layout.
1341pub trait AsShape<L: Layout> {
1342    /// Convert `self` into an index for use the layout `L`.
1343    fn as_shape(&self) -> L::Shape<'_>;
1344}
1345
1346impl<T: AsRef<[usize]>> AsShape<DynLayout> for T {
1347    fn as_shape(&self) -> &[usize] {
1348        self.as_ref()
1349    }
1350}
1351
1352impl<const N: usize> AsShape<NdLayout<N>> for [usize; N] {
1353    fn as_shape(&self) -> [usize; N] {
1354        *self
1355    }
1356}
1357
1358impl AsShape<NdLayout<1>> for usize {
1359    fn as_shape(&self) -> [usize; 1] {
1360        [*self]
1361    }
1362}
1363
1364/// Trait that removes one dimension from a layout.
1365pub trait RemoveDim {
1366    type Output: MutLayout;
1367
1368    /// Return a copy of this layout with the dimension at index `dim` removed.
1369    fn remove_dim(&self, dim: usize) -> Self::Output;
1370}
1371
1372impl<R: RemoveDim> RemoveDim for &R {
1373    type Output = R::Output;
1374
1375    fn remove_dim(&self, dim: usize) -> Self::Output {
1376        (*self).remove_dim(dim)
1377    }
1378}
1379
1380impl RemoveDim for DynLayout {
1381    type Output = DynLayout;
1382
1383    fn remove_dim(&self, dim: usize) -> DynLayout {
1384        let ndim = self.ndim();
1385        assert!(ndim > 0, "cannot remove axis from tensor with 0 dims");
1386
1387        let shape = (0..ndim - 1).map(|i| {
1388            if i < dim {
1389                self.size(i)
1390            } else {
1391                self.size(i + 1)
1392            }
1393        });
1394        let strides = (0..ndim - 1).map(|i| {
1395            if i < dim {
1396                self.stride(i)
1397            } else {
1398                self.stride(i + 1)
1399            }
1400        });
1401        DynLayout {
1402            shape_and_strides: shape.chain(strides).collect(),
1403        }
1404    }
1405}
1406
1407macro_rules! impl_remove_dim {
1408    ($in_dims:expr, $out_dims:expr) => {
1409        impl RemoveDim for NdLayout<$in_dims> {
1410            type Output = NdLayout<$out_dims>;
1411
1412            fn remove_dim(&self, dim: usize) -> Self::Output {
1413                let shape = std::array::from_fn(|i| {
1414                    if i < dim {
1415                        self.shape[i]
1416                    } else {
1417                        self.shape[i + 1]
1418                    }
1419                });
1420                let strides = std::array::from_fn(|i| {
1421                    if i < dim {
1422                        self.strides[i]
1423                    } else {
1424                        self.strides[i + 1]
1425                    }
1426                });
1427                NdLayout { shape, strides }
1428            }
1429        }
1430    };
1431}
1432
1433impl_remove_dim!(1, 0);
1434impl_remove_dim!(2, 1);
1435impl_remove_dim!(3, 2);
1436impl_remove_dim!(4, 3);
1437impl_remove_dim!(5, 4);
1438
1439/// Trait that inserts one dimension into a layout.
1440pub trait InsertDim {
1441    type Output: MutLayout;
1442
1443    /// Return a copy of this layout with a size-1 dimension inserted at `dim`.
1444    fn insert_dim(&self, dim: usize) -> Self::Output;
1445}
1446
1447impl InsertDim for DynLayout {
1448    type Output = DynLayout;
1449
1450    fn insert_dim(&self, dim: usize) -> DynLayout {
1451        let mut clone = self.clone();
1452        clone.insert_axis(dim);
1453        clone
1454    }
1455}
1456
1457macro_rules! impl_insert_dim {
1458    ($in_dims:expr, $out_dims:expr) => {
1459        impl InsertDim for NdLayout<$in_dims> {
1460            type Output = NdLayout<$out_dims>;
1461
1462            fn insert_dim(&self, dim: usize) -> Self::Output {
1463                let new_stride = self
1464                    .shape
1465                    .iter()
1466                    .zip(self.strides.iter())
1467                    .map(|(s, st)| s * st)
1468                    .max()
1469                    .unwrap_or(1);
1470
1471                let shape = std::array::from_fn(|i| {
1472                    if i < dim {
1473                        self.shape[i]
1474                    } else if i == dim {
1475                        1
1476                    } else {
1477                        self.shape[i - 1]
1478                    }
1479                });
1480                let strides = std::array::from_fn(|i| {
1481                    if i < dim {
1482                        self.strides[i]
1483                    } else if i == dim {
1484                        new_stride
1485                    } else {
1486                        self.strides[i - 1]
1487                    }
1488                });
1489                NdLayout { shape, strides }
1490            }
1491        }
1492    };
1493}
1494
1495impl_insert_dim!(0, 1);
1496impl_insert_dim!(1, 2);
1497impl_insert_dim!(2, 3);
1498impl_insert_dim!(3, 4);
1499impl_insert_dim!(4, 5);
1500
1501/// Trait for slicing a layout with a range.
1502///
1503/// `R` is the type of the slice range. `IdxCount` is a marker type indicating
1504/// the number of items in `R` that are indices, as opposed to ranges.
1505pub trait SliceWith<R: IntoSliceItems, IdxCount: OptionalUInt> {
1506    /// The layout produced after slicing.
1507    type Layout: MutLayout;
1508
1509    /// Slice the layout with a range.
1510    ///
1511    /// Returns a tuple of `(offset_range, sliced_layout)` where `offset_range`
1512    /// is the range of data from the original view that is used by the slice
1513    /// and `sliced_layout` is the layout of the sliced view.
1514    fn slice_with(&self, range: R) -> Result<(Range<usize>, Self::Layout), SliceError>;
1515}
1516
1517impl<R: IntoSliceItems, L: MutLayout> SliceWith<R, Unknown> for L {
1518    type Layout = DynLayout;
1519
1520    fn slice_with(&self, range: R) -> Result<(Range<usize>, Self::Layout), SliceError> {
1521        self.slice_dyn(range.into_slice_items().as_ref())
1522    }
1523}
1524
1525impl<R: IntoSliceItems, const N: usize> SliceWith<R, U0> for NdLayout<N> {
1526    type Layout = NdLayout<N>;
1527
1528    fn slice_with(&self, range: R) -> Result<(Range<usize>, Self::Layout), SliceError> {
1529        self.slice(range.into_slice_items().as_ref())
1530    }
1531}
1532
1533macro_rules! impl_slice_with_dynlayout {
1534    ($range_ndim:ty) => {
1535        impl<R: IntoSliceItems> SliceWith<R, $range_ndim> for DynLayout {
1536            type Layout = DynLayout;
1537
1538            fn slice_with(&self, range: R) -> Result<(Range<usize>, Self::Layout), SliceError> {
1539                self.slice_dyn(range.into_slice_items().as_ref())
1540            }
1541        }
1542    };
1543}
1544
1545impl_slice_with_dynlayout!(U0);
1546impl_slice_with_dynlayout!(U1);
1547impl_slice_with_dynlayout!(U2);
1548impl_slice_with_dynlayout!(U3);
1549impl_slice_with_dynlayout!(U4);
1550impl_slice_with_dynlayout!(U5);
1551
1552macro_rules! impl_slice_with {
1553    ($ndim:literal, $range_ndim:ty, $out_ndim:literal) => {
1554        impl<R: IntoSliceItems> SliceWith<R, $range_ndim> for NdLayout<$ndim> {
1555            type Layout = NdLayout<$out_ndim>;
1556
1557            fn slice_with(&self, range: R) -> Result<(Range<usize>, Self::Layout), SliceError> {
1558                self.slice(range.into_slice_items().as_ref())
1559            }
1560        }
1561    };
1562}
1563
1564impl_slice_with!(1, U1, 0);
1565impl_slice_with!(2, U1, 1);
1566impl_slice_with!(2, U2, 0);
1567impl_slice_with!(3, U1, 2);
1568impl_slice_with!(3, U2, 1);
1569impl_slice_with!(3, U3, 0);
1570impl_slice_with!(4, U1, 3);
1571impl_slice_with!(4, U2, 2);
1572impl_slice_with!(4, U3, 1);
1573impl_slice_with!(4, U4, 0);
1574impl_slice_with!(5, U1, 4);
1575impl_slice_with!(5, U2, 3);
1576impl_slice_with!(5, U3, 2);
1577impl_slice_with!(5, U4, 1);
1578impl_slice_with!(5, U5, 0);
1579
1580#[cfg(test)]
1581mod tests {
1582    use rten_testing::TestCases;
1583
1584    use std::ops::Range;
1585
1586    use super::OverlapPolicy;
1587    use crate::SliceItem;
1588    use crate::errors::{ReshapeError, SliceError};
1589    use crate::layout::{DynLayout, FromShape, Layout, MutLayout, NdLayout, ResizeLayout};
1590
1591    fn layout_with_strides<const N: usize>(shape: [usize; N], strides: [usize; N]) -> NdLayout<N> {
1592        NdLayout::from_shape_and_strides(shape, strides, OverlapPolicy::AllowOverlap).unwrap()
1593    }
1594
1595    #[test]
1596    fn test_is_broadcast() {
1597        // Non-empty, contiguous layout
1598        let layout = DynLayout::from_shape(&[5, 5]);
1599        assert!(!layout.is_broadcast());
1600
1601        // Empty layout
1602        let layout = DynLayout::from_shape(&[5, 0]);
1603        assert!(!layout.is_broadcast());
1604
1605        // Broadcasting layout
1606        let layout =
1607            DynLayout::from_shape_and_strides(&[5, 5], &[0, 0], OverlapPolicy::AllowOverlap)
1608                .unwrap();
1609        assert!(layout.is_broadcast());
1610    }
1611
1612    #[test]
1613    fn test_from_shape_and_strides() {
1614        #[derive(Debug)]
1615        struct Case<'a> {
1616            shape: &'a [usize],
1617            strides: &'a [usize],
1618        }
1619
1620        let cases = [
1621            // Contiguous layout
1622            Case {
1623                shape: &[10, 10],
1624                strides: &[10, 1],
1625            },
1626            // Broadcasting layout
1627            Case {
1628                shape: &[10, 10],
1629                strides: &[10, 0],
1630            },
1631        ];
1632
1633        cases.test_each(|case| {
1634            let layout = DynLayout::from_shape_and_strides(
1635                case.shape,
1636                case.strides,
1637                OverlapPolicy::AllowOverlap,
1638            )
1639            .unwrap();
1640            assert_eq!(layout.shape(), case.shape);
1641            assert_eq!(layout.strides(), case.strides);
1642        })
1643    }
1644
1645    #[test]
1646    fn test_index_axis() {
1647        #[derive(Debug)]
1648        struct Case {
1649            layout: NdLayout<2>,
1650            axis: usize,
1651            index: usize,
1652            expected: (usize, NdLayout<1>), // (start offset, sliced layout)
1653        }
1654
1655        let cases = [
1656            Case {
1657                layout: NdLayout::from_shape([3, 4]),
1658                axis: 0,
1659                index: 1,
1660                expected: (4, layout_with_strides([4], [1])),
1661            },
1662            Case {
1663                layout: NdLayout::from_shape([3, 4]),
1664                axis: 1,
1665                index: 2,
1666                expected: (2, layout_with_strides([3], [4])),
1667            },
1668            // Empty result. The tensor's storage is empty, so the offset must
1669            // be zero.
1670            Case {
1671                layout: NdLayout::from_shape([0, 3]),
1672                axis: 1,
1673                index: 1,
1674                expected: (0, layout_with_strides([0], [3])),
1675            },
1676            Case {
1677                layout: layout_with_strides([3, 0], [1, 3]),
1678                axis: 0,
1679                index: 1,
1680                expected: (0, layout_with_strides([0], [3])),
1681            },
1682        ];
1683
1684        cases.test_each(|case| {
1685            let Case {
1686                layout,
1687                axis,
1688                index,
1689                expected,
1690            } = case;
1691
1692            let (expected_start, expected_layout) = expected;
1693
1694            let (offsets, sliced_layout) = layout.index_axis(*axis, *index);
1695            assert_eq!(sliced_layout, *expected_layout);
1696            assert_eq!(offsets.start, *expected_start);
1697            assert_eq!(offsets.len(), expected_layout.min_data_len());
1698
1699            let (_, sliced_layout_dyn) = layout.as_dyn().index_axis(*axis, *index);
1700            assert_eq!(sliced_layout_dyn, expected_layout.as_dyn());
1701        })
1702    }
1703
1704    #[test]
1705    #[should_panic(expected = "axis < self.ndim()")]
1706    fn test_index_axis_invalid_axis() {
1707        NdLayout::from_shape([2, 3]).index_axis(2, 0);
1708    }
1709
1710    #[test]
1711    #[should_panic(expected = "index < self.size(axis)")]
1712    fn test_index_axis_invalid_index() {
1713        NdLayout::from_shape([2, 3]).index_axis(0, 3);
1714    }
1715
1716    #[test]
1717    fn test_move_axis() {
1718        let mut layout = DynLayout::from_shape(&[2, 4, 8]);
1719        assert_eq!(layout.strides(), [32, 8, 1]);
1720
1721        layout.move_axis(1, 0);
1722        assert_eq!(layout.shape(), [4, 2, 8]);
1723        assert_eq!(layout.strides(), [8, 32, 1]);
1724
1725        layout.move_axis(0, 1);
1726        assert_eq!(layout.shape(), [2, 4, 8]);
1727        assert_eq!(layout.strides(), [32, 8, 1]);
1728
1729        layout.move_axis(2, 1);
1730        assert_eq!(layout.shape(), [2, 8, 4]);
1731        assert_eq!(layout.strides(), [32, 1, 8]);
1732    }
1733
1734    #[test]
1735    #[should_panic]
1736    fn test_move_axis_invalid_from() {
1737        let mut layout = DynLayout::from_shape(&[2, 4, 8]);
1738        layout.move_axis(3, 0);
1739    }
1740
1741    #[test]
1742    #[should_panic]
1743    fn test_move_axis_invalid_to() {
1744        let mut layout = DynLayout::from_shape(&[2, 4, 8]);
1745        layout.move_axis(0, 3);
1746    }
1747
1748    #[test]
1749    #[should_panic(expected = "permutation is invalid")]
1750    fn test_permute_invalid_len() {
1751        let mut layout = DynLayout::from_shape(&[5, 5]);
1752        layout.permute(&[1, 0, 3]);
1753    }
1754
1755    #[test]
1756    #[should_panic(expected = "permutation is invalid")]
1757    fn test_permute_too_few_dims() {
1758        let mut layout = DynLayout::from_shape(&[5, 5]);
1759        layout.permute(&[1]);
1760    }
1761
1762    #[test]
1763    #[should_panic(expected = "permutation is invalid")]
1764    fn test_permute_repeated_dims() {
1765        let mut layout = DynLayout::from_shape(&[5, 5]);
1766        layout.permute(&[1, 1]);
1767    }
1768
1769    #[test]
1770    fn test_remove_axis_of_any_size() {
1771        let shape = [1, 2, 3, 4];
1772        for d in 0..shape.len() {
1773            let mut layout = DynLayout::from_shape(&shape);
1774            let (expected_shape, expected_strides): (Vec<usize>, Vec<usize>) = layout
1775                .shape()
1776                .iter()
1777                .zip(layout.strides())
1778                .enumerate()
1779                .filter_map(|(i, (size, stride))| if i != d { Some((size, stride)) } else { None })
1780                .unzip();
1781
1782            layout.remove_axis_of_any_size(d);
1783
1784            assert_eq!(layout.shape(), expected_shape);
1785            assert_eq!(layout.strides(), expected_strides);
1786        }
1787    }
1788
1789    #[test]
1790    fn test_reshaped() {
1791        #[derive(Debug)]
1792        struct Case<'a> {
1793            layout: DynLayout,
1794            new_shape: &'a [usize],
1795            for_copy: bool,
1796            error: Option<ReshapeError>,
1797        }
1798
1799        let cases = [
1800            // Reshapes that don't allow copying.
1801            Case {
1802                layout: DynLayout::from_shape(&[2, 2]),
1803                new_shape: &[4],
1804                for_copy: false,
1805                error: None,
1806            },
1807            Case {
1808                layout: DynLayout::from_shape(&[2, 2]).transposed(),
1809                new_shape: &[4],
1810                for_copy: false,
1811                error: Some(ReshapeError::NotContiguous),
1812            },
1813            Case {
1814                layout: DynLayout::from_shape(&[2, 2]),
1815                new_shape: &[3],
1816                for_copy: false,
1817                error: Some(ReshapeError::LengthMismatch),
1818            },
1819            // Reshapes that do allow copying.
1820            Case {
1821                layout: DynLayout::from_shape(&[2, 2]).transposed(),
1822                new_shape: &[4],
1823                for_copy: true,
1824                error: None,
1825            },
1826            Case {
1827                layout: DynLayout::from_shape(&[2, 2]),
1828                new_shape: &[3],
1829                for_copy: false,
1830                error: Some(ReshapeError::LengthMismatch),
1831            },
1832        ];
1833
1834        cases.test_each(|case| {
1835            let Case {
1836                layout,
1837                new_shape,
1838                for_copy,
1839                error,
1840            } = case;
1841
1842            let reshaped = if *for_copy {
1843                layout.reshaped_for_copy(*new_shape)
1844            } else {
1845                layout.reshaped_for_view(*new_shape)
1846            };
1847
1848            assert_eq!(reshaped.as_ref().err(), error.as_ref());
1849            if let Ok(new_layout) = reshaped {
1850                assert_eq!(new_layout.shape(), *new_shape);
1851            }
1852        })
1853    }
1854
1855    #[test]
1856    fn test_squeezed() {
1857        let layout = DynLayout::from_shape(&[1, 1, 10, 20]);
1858        let squeezed = layout.squeezed();
1859        assert_eq!(squeezed.shape(), &[10, 20]);
1860        assert_eq!(squeezed.strides(), &[20, 1]);
1861    }
1862
1863    #[test]
1864    fn test_slice_axis() {
1865        #[derive(Clone, Debug)]
1866        struct Case<'a> {
1867            shape: &'a [usize],
1868            axis: usize,
1869            range: Range<usize>,
1870            sliced_shape: &'a [usize],
1871            offsets: Range<usize>,
1872        }
1873
1874        let cases = [Case {
1875            shape: &[3, 5],
1876            axis: 1,
1877            range: 2..4,
1878            sliced_shape: &[3, 2],
1879            offsets: 2..14,
1880        }];
1881
1882        cases.test_each_clone(|case| {
1883            let Case {
1884                shape,
1885                axis,
1886                range,
1887                sliced_shape,
1888                offsets,
1889            } = case;
1890
1891            let layout = DynLayout::from_shape(shape);
1892            let (offset_range, sliced_layout) = layout.slice_axis(axis, range).unwrap();
1893            assert_eq!(sliced_layout.shape(), sliced_shape);
1894            assert_eq!(sliced_layout.strides(), layout.strides());
1895            assert_eq!(offset_range, offsets);
1896        })
1897    }
1898
1899    #[test]
1900    fn test_slice_axis_invalid() {
1901        #[derive(Debug)]
1902        struct Case<'a> {
1903            shape: &'a [usize],
1904            axis: usize,
1905            range: Range<usize>,
1906            expected: SliceError,
1907        }
1908
1909        let cases = [
1910            Case {
1911                shape: &[1, 2, 3],
1912                axis: 4,
1913                range: 0..1,
1914                expected: SliceError::InvalidAxis { axis: 4 },
1915            },
1916            Case {
1917                shape: &[1, 2, 3],
1918                axis: 0,
1919                range: 0..2,
1920                expected: SliceError::InvalidRange {
1921                    axis: 0,
1922                    range: (0..2).into(),
1923                    size: 1,
1924                },
1925            },
1926        ];
1927
1928        cases.test_each(|case| {
1929            let layout = DynLayout::from_shape(case.shape);
1930            let result = layout.slice_axis(case.axis, case.range.clone());
1931            assert_eq!(result, Err(case.expected.clone()));
1932        })
1933    }
1934
1935    #[test]
1936    fn test_slice_invalid() {
1937        #[derive(Debug)]
1938        struct Case<'a> {
1939            layout: DynLayout,
1940            ranges: &'a [SliceItem],
1941            expected: SliceError,
1942        }
1943
1944        let cases = [
1945            Case {
1946                layout: DynLayout::from_shape(&[3, 5]),
1947                ranges: &[SliceItem::Index(4), SliceItem::Index(0)],
1948                expected: SliceError::InvalidIndex {
1949                    axis: 0,
1950                    index: 4,
1951                    size: 3,
1952                },
1953            },
1954            Case {
1955                layout: DynLayout::from_shape(&[3, 5]),
1956                ranges: &[SliceItem::Range((1..4).into()), SliceItem::Index(0)],
1957                expected: SliceError::InvalidRange {
1958                    axis: 0,
1959                    range: (1..4).into(),
1960                    size: 3,
1961                },
1962            },
1963            Case {
1964                layout: DynLayout::from_shape(&[3, 5]),
1965                ranges: &[SliceItem::Index(-4)],
1966                expected: SliceError::InvalidIndex {
1967                    axis: 0,
1968                    index: -4,
1969                    size: 3,
1970                },
1971            },
1972            Case {
1973                layout: DynLayout::from_shape(&[3, 5]),
1974                ranges: &[SliceItem::Range((4..).into()), SliceItem::Index(0)],
1975                expected: SliceError::InvalidRange {
1976                    axis: 0,
1977                    range: (4..).into(),
1978                    size: 3,
1979                },
1980            },
1981            Case {
1982                layout: DynLayout::from_shape(&[3, 5]),
1983                ranges: &[SliceItem::full_range(), SliceItem::range(0, None, -1)],
1984                expected: SliceError::InvalidStep { axis: 1, step: -1 },
1985            },
1986        ];
1987
1988        cases.test_each(|case| {
1989            let result = case.layout.slice_dyn(case.ranges);
1990            assert_eq!(result, Err(case.expected.clone()));
1991        })
1992    }
1993
1994    #[test]
1995    fn test_size_stride() {
1996        let layout = DynLayout::from_shape(&[10, 20, 30]);
1997        for (dim, (&size, &stride)) in layout.shape().iter().zip(layout.strides()).enumerate() {
1998            assert_eq!(layout.size(dim), size);
1999            assert_eq!(layout.stride(dim), stride);
2000        }
2001    }
2002
2003    #[test]
2004    fn test_split() {
2005        #[derive(Debug)]
2006        struct Case {
2007            shape: [usize; 2],
2008            strides: Option<[usize; 2]>,
2009            axis: usize,
2010            mid: usize,
2011        }
2012
2013        let mut cases = Vec::new();
2014
2015        // All combinations of (axis, mid) for a small shape.
2016        let shape = [4, 2];
2017        for axis in 0..shape.len() {
2018            for mid in 0..shape[axis] {
2019                cases.push(Case {
2020                    shape,
2021                    axis,
2022                    mid,
2023                    strides: None,
2024                });
2025            }
2026        }
2027
2028        // Empty layout
2029        cases.push(Case {
2030            shape: [0, 0],
2031            strides: None,
2032            axis: 0,
2033            mid: 0,
2034        });
2035
2036        // Case where we are splitting a 1-sized dimension with `mid=1` and
2037        // the stride is larger than the minimum storage length for the layout.
2038        cases.push(Case {
2039            shape: [1, 4],
2040            strides: Some([10, 0]),
2041            axis: 0,
2042            mid: 1,
2043        });
2044
2045        fn check_split<L: MutLayout>(layout: L, axis: usize, mid: usize) {
2046            let (left, right) = layout.split(axis, mid);
2047            let (left_offsets, left_layout) = left;
2048            let (right_offsets, right_layout) = right;
2049
2050            assert_eq!(left_layout.strides(), layout.strides());
2051            assert_eq!(right_layout.strides(), layout.strides());
2052
2053            assert_eq!(left_offsets.len(), left_layout.min_data_len());
2054            assert_eq!(right_offsets.len(), right_layout.min_data_len());
2055
2056            let orig_len = layout.min_data_len();
2057            assert!(left_offsets.start <= orig_len && left_offsets.end <= orig_len);
2058            assert!(right_offsets.start <= orig_len && right_offsets.end <= orig_len);
2059
2060            for i in 0..layout.ndim() {
2061                assert_eq!(
2062                    left_layout.size(i),
2063                    if i == axis { mid } else { layout.size(i) }
2064                );
2065                assert_eq!(
2066                    right_layout.size(i),
2067                    if i == axis {
2068                        layout.size(i) - mid
2069                    } else {
2070                        layout.size(i)
2071                    }
2072                );
2073            }
2074        }
2075
2076        cases.test_each(|case| {
2077            let Case {
2078                shape,
2079                strides,
2080                axis,
2081                mid,
2082            } = case;
2083
2084            let layout = if let Some(strides) = strides {
2085                NdLayout::from_shape_and_strides(*shape, *strides, OverlapPolicy::AllowOverlap)
2086                    .unwrap()
2087            } else {
2088                NdLayout::from_shape(*shape)
2089            };
2090            let dyn_layout = if let Some(strides) = strides {
2091                DynLayout::from_shape_and_strides(
2092                    shape.as_slice(),
2093                    strides.as_slice(),
2094                    OverlapPolicy::AllowOverlap,
2095                )
2096                .unwrap()
2097            } else {
2098                DynLayout::from_shape(shape.as_slice())
2099            };
2100
2101            check_split(layout, *axis, *mid);
2102            check_split(dyn_layout, *axis, *mid);
2103        })
2104    }
2105
2106    #[test]
2107    fn test_merge_axes() {
2108        #[derive(Debug)]
2109        struct Case<'a> {
2110            shape: &'a [usize],
2111            strides: &'a [usize],
2112            merged_shape: &'a [usize],
2113            merged_strides: &'a [usize],
2114        }
2115
2116        let cases = [
2117            // Empty shape
2118            Case {
2119                shape: &[],
2120                strides: &[],
2121                merged_shape: &[],
2122                merged_strides: &[],
2123            },
2124            // Vector
2125            Case {
2126                shape: &[10],
2127                strides: &[2],
2128                merged_shape: &[10],
2129                merged_strides: &[2],
2130            },
2131            // Simple contiguous layout
2132            Case {
2133                shape: &[10, 10],
2134                strides: &[10, 1],
2135                merged_shape: &[100],
2136                merged_strides: &[1],
2137            },
2138            // Transposed matrix
2139            Case {
2140                shape: &[10, 10],
2141                strides: &[1, 10],
2142                merged_shape: &[10, 10],
2143                merged_strides: &[1, 10],
2144            },
2145            // Leading 1-sized dims
2146            Case {
2147                shape: &[1, 10, 10],
2148                strides: &[10, 1, 10],
2149                merged_shape: &[10, 10],
2150                merged_strides: &[1, 10],
2151            },
2152            // Inner 1-sized dims
2153            Case {
2154                shape: &[2, 1, 1, 2],
2155                strides: &[2, 2, 2, 1],
2156                merged_shape: &[4],
2157                merged_strides: &[1],
2158            },
2159            // Inner 1-sized dims that have been shifted over from the left,
2160            // ie. where the 1-sized dims where inserted at the left and then
2161            // shifted over to the middle.
2162            Case {
2163                shape: &[2, 1, 1, 2],
2164                strides: &[2, 4, 4, 1],
2165                merged_shape: &[4],
2166                merged_strides: &[1],
2167            },
2168        ];
2169
2170        cases.test_each(|case| {
2171            let mut layout = DynLayout::from_shape_and_strides(
2172                case.shape,
2173                case.strides,
2174                OverlapPolicy::AllowOverlap,
2175            )
2176            .unwrap();
2177            layout.merge_axes();
2178            assert_eq!(layout.shape(), case.merged_shape);
2179            assert_eq!(layout.strides(), case.merged_strides);
2180        })
2181    }
2182}