Skip to main content

rstsr_common/layout/
layoutbase.rs

1//! Layout of tensor.
2use crate::prelude_dev::*;
3use itertools::izip;
4
5/* #region Struct Definitions */
6
7/// Layout of tensor.
8///
9/// Layout is a struct that contains shape, stride, and offset of tensor.
10/// - Shape is the size of each dimension of tensor.
11/// - Stride is the number of elements to skip to get to the next element in each dimension.
12/// - Offset is the starting position of tensor.
13#[doc = include_str!("readme.md")]
14#[derive(Clone)]
15pub struct Layout<D>
16where
17    D: DimBaseAPI,
18{
19    // essential definitions to layout
20    pub(crate) shape: D,
21    pub(crate) stride: D::Stride,
22    pub(crate) offset: usize,
23}
24
25unsafe impl<D> Send for Layout<D> where D: DimBaseAPI {}
26unsafe impl<D> Sync for Layout<D> where D: DimBaseAPI {}
27
28/* #endregion */
29
30/* #region Layout */
31
32/// Getter/setter functions for layout.
33impl<D> Layout<D>
34where
35    D: DimBaseAPI,
36{
37    /// Shape of tensor. Getter function.
38    #[inline]
39    pub fn shape(&self) -> &D {
40        &self.shape
41    }
42
43    /// Stride of tensor. Getter function.
44    #[inline]
45    pub fn stride(&self) -> &D::Stride {
46        &self.stride
47    }
48
49    /// Starting offset of tensor. Getter function.
50    #[inline]
51    pub fn offset(&self) -> usize {
52        self.offset
53    }
54
55    /// Number of dimensions of tensor.
56    #[inline]
57    pub fn ndim(&self) -> usize {
58        self.shape.ndim()
59    }
60
61    /// Total number of elements in tensor.
62    ///
63    /// # Note
64    ///
65    /// This function uses cached size, instead of evaluating from shape.
66    #[inline]
67    pub fn size(&self) -> usize {
68        self.shape().as_ref().iter().product()
69    }
70
71    /// Manually set offset.
72    ///
73    /// # Safety
74    ///
75    /// We will not check whether this offset is valid or not.
76    /// In most cases, it is not intended to be used by user.
77    pub unsafe fn set_offset(&mut self, offset: usize) -> &mut Self {
78        self.offset = offset;
79        return self;
80    }
81}
82
83/// Properties of layout.
84impl<D> Layout<D>
85where
86    D: DimBaseAPI + DimShapeAPI,
87{
88    /// Whether this tensor is f-preferred.
89    pub fn f_prefer(&self) -> bool {
90        // always true for 0-dimension or 0-size tensor
91        if self.ndim() == 0 || self.size() == 0 {
92            return true;
93        }
94
95        let stride = self.stride.as_ref();
96        let shape = self.shape.as_ref();
97        let mut last = 0;
98        for (&s, &d) in stride.iter().zip(shape.iter()) {
99            if d != 1 {
100                if s < last {
101                    // latter strides must larger than previous strides
102                    return false;
103                }
104                if last == 0 && s != 1 {
105                    // first stride must be 1
106                    return false;
107                }
108                last = s;
109            } else if last == 0 {
110                // if dimension is one, then consider that stride is one, counted as contiguous
111                // in last dimension
112                last = 1;
113            }
114        }
115        return true;
116    }
117
118    /// Whether this tensor is c-preferred.
119    pub fn c_prefer(&self) -> bool {
120        // always true for 0-dimension or 0-size tensor
121        if self.ndim() == 0 || self.size() == 0 {
122            return true;
123        }
124
125        let stride = self.stride.as_ref();
126        let shape = self.shape.as_ref();
127        let mut last = 0;
128        for (&s, &d) in stride.iter().zip(shape.iter()).rev() {
129            if d != 1 {
130                if s < last {
131                    // previous strides must larger than latter strides
132                    return false;
133                }
134                if last == 0 && s != 1 {
135                    // last stride must be 1
136                    return false;
137                }
138                last = s;
139            } else if last == 0 {
140                // if dimension is one, then consider that stride is one, counted as contiguous
141                // in last dimension
142                last = 1;
143            }
144        }
145        return true;
146    }
147
148    /// Least number of dimensions that is f-contiguous for layout.
149    ///
150    /// This function can be useful determining when to iterate by contiguous,
151    /// and when to iterate by index.
152    pub fn ndim_of_f_contig(&self) -> usize {
153        if self.ndim() == 0 || self.size() == 0 {
154            return self.ndim();
155        }
156        let stride = self.stride.as_ref();
157        let shape = self.shape.as_ref();
158        let mut acc = 1;
159        for (ndim, (&s, &d)) in stride.iter().zip(shape.iter()).enumerate() {
160            if d != 1 && s != acc {
161                return ndim;
162            }
163            acc *= d as isize;
164        }
165        return self.ndim();
166    }
167
168    /// Least number of dimensions that is c-contiguous for layout.
169    ///
170    /// This function can be useful determining when to iterate by contiguous,
171    /// and when to iterate by index.
172    pub fn ndim_of_c_contig(&self) -> usize {
173        if self.ndim() == 0 || self.size() == 0 {
174            return self.ndim();
175        }
176        let stride = self.stride.as_ref();
177        let shape = self.shape.as_ref();
178        let mut acc = 1;
179        for (ndim, (&s, &d)) in stride.iter().zip(shape.iter()).rev().enumerate() {
180            if d != 1 && s != acc {
181                return ndim;
182            }
183            acc *= d as isize;
184        }
185        return self.ndim();
186    }
187
188    /// Whether this tensor is f-contiguous.
189    ///
190    /// Special cases
191    /// - When length of a dimension is one, then stride to that dimension is not important.
192    /// - When length of a dimension is zero, then tensor contains no elements, thus f-contiguous.
193    pub fn f_contig(&self) -> bool {
194        self.ndim() == self.ndim_of_f_contig()
195    }
196
197    /// Whether this tensor is c-contiguous.
198    ///
199    /// Special cases
200    /// - When length of a dimension is one, then stride to that dimension is not important.
201    /// - When length of a dimension is zero, then tensor contains no elements, thus c-contiguous.
202    pub fn c_contig(&self) -> bool {
203        self.ndim() == self.ndim_of_c_contig()
204    }
205
206    /// Index of tensor by list of indexes to dimensions.
207    ///
208    /// This function does not optimized for performance.
209    pub fn index_f(&self, index: &[isize]) -> Result<usize> {
210        rstsr_assert_eq!(index.len(), self.ndim(), InvalidLayout)?;
211        let mut pos = self.offset() as isize;
212        let shape = self.shape.as_ref();
213        let stride = self.stride.as_ref();
214
215        for (&idx, &shp, &strd) in izip!(index.iter(), shape.iter(), stride.iter()) {
216            let idx = if idx < 0 { idx + shp as isize } else { idx };
217            rstsr_pattern!(idx, 0..(shp as isize), IndexError)?;
218            pos += strd * idx;
219        }
220        rstsr_pattern!(pos, 0.., ValueOutOfRange)?;
221        return Ok(pos as usize);
222    }
223
224    /// Index of tensor by list of indexes to dimensions.
225    ///
226    /// This function does not optimized for performance. Negative index
227    /// allowed.
228    pub fn index(&self, index: &[isize]) -> usize {
229        self.index_f(index).unwrap()
230    }
231
232    /// Index range bounds of current layout. This bound is [min, max), which
233    /// could be feed into range (min..max). If min == max, then this layout
234    /// should not contains any element.
235    ///
236    /// This function will raise error when minimum index is smaller than zero.
237    pub fn bounds_index(&self) -> Result<(usize, usize)> {
238        let n = self.ndim();
239        let offset = self.offset;
240        let shape = self.shape.as_ref();
241        let stride = self.stride.as_ref();
242
243        if n == 0 {
244            return Ok((offset, offset + 1));
245        }
246
247        let mut min = offset as isize;
248        let mut max = offset as isize;
249
250        for i in 0..n {
251            if shape[i] == 0 {
252                return Ok((offset, offset));
253            }
254            if stride[i] > 0 {
255                max += stride[i] * (shape[i] as isize - 1);
256            } else {
257                min += stride[i] * (shape[i] as isize - 1);
258            }
259        }
260        rstsr_pattern!(min, 0.., ValueOutOfRange)?;
261        return Ok((min as usize, max as usize + 1));
262    }
263
264    /// Check if strides is correct (no elemenets can overlap).
265    ///
266    /// This will check if all number of elements in dimension of small strides
267    /// is less than larger strides. For example of valid stride:
268    /// ```output
269    /// shape:  (3,    2,  6)  -> sorted ->  ( 3,   6,   2)
270    /// stride: (3, -300, 15)  -> sorted ->  ( 3,  15, 300)
271    /// number of elements:                    9,  90,
272    /// stride of next dimension              15, 300,
273    /// number of elem < stride of next dim?   +,   +,
274    /// ```
275    ///
276    /// Special cases
277    /// - if length of tensor is zero, then strides will always be correct.
278    /// - if certain dimension is one, then check for this stride will be ignored.
279    /// - if stride has zero value, `skip_zero` parameter will determine whether this function will
280    ///   raise error or not.
281    ///
282    /// # TODO
283    ///
284    /// Correctness of this function is not fully ensured.
285    pub fn check_strides(&self, skip_zero: bool) -> Result<()> {
286        let shape = self.shape.as_ref();
287        let stride = self.stride.as_ref();
288        rstsr_assert_eq!(shape.len(), stride.len(), InvalidLayout)?;
289        let n = shape.len();
290
291        // unconditionally ok if no elements (length of tensor is zero)
292        // unconditionally ok if 0-dimension
293        if self.size() == 0 || n == 0 {
294            return Ok(());
295        }
296
297        let mut indices = (0..n).filter(|&k| shape[k] > 1).collect::<Vec<_>>();
298        indices.sort_by_key(|&k| stride[k].abs());
299        let shape_sorted = indices.iter().map(|&k| shape[k]).collect::<Vec<_>>();
300        let stride_sorted = indices.iter().map(|&k| stride[k].unsigned_abs()).collect::<Vec<_>>();
301
302        // elem_cum: cumulative number count of elements in tensor for small strides
303        let mut elem_cum = 0;
304        for i in 0..indices.len() {
305            // if stride is zero, then skip check for this axis
306            if stride_sorted[i] == 0 && skip_zero {
307                continue;
308            }
309            // following function also checks that stride could not be zero
310            rstsr_pattern!(
311                elem_cum,
312                0..stride_sorted[i],
313                InvalidLayout,
314                "Either stride be zero, or stride too small that elements in tensor can be overlapped."
315            )?;
316
317            elem_cum += (shape_sorted[i] - 1) * stride_sorted[i];
318        }
319        return Ok(());
320    }
321
322    pub fn diagonal(
323        &self,
324        offset: Option<isize>,
325        axis1: Option<isize>,
326        axis2: Option<isize>,
327    ) -> Result<Layout<<D as DimSmallerOneAPI>::SmallerOne>>
328    where
329        D: DimSmallerOneAPI,
330    {
331        // check if this layout is at least 2-dimension
332        rstsr_assert!(self.ndim() >= 2, InvalidLayout)?;
333        // unwrap optional parameters
334        let offset = offset.unwrap_or(0);
335        let axis1 = axis1.unwrap_or(0);
336        let axis2 = axis2.unwrap_or(1);
337        let axis1 = rstsr_check_axis!(axis1, self.ndim())?;
338        let axis2 = rstsr_check_axis!(axis2, self.ndim())?;
339
340        // shape and strides of last two dimensions
341        let d1 = self.shape()[axis1] as isize;
342        let d2 = self.shape()[axis2] as isize;
343        let t1 = self.stride()[axis1];
344        let t2 = self.stride()[axis2];
345
346        // number of elements in diagonal, and starting offset
347        //
348        // For a negative offset (sub-diagonal) the element is A[i + |k|, i], valid
349        // while i + |k| < d1 (rows), i.e. |k| < d1, i.e. offset in (-d1, 0). Use
350        // (-d1 + 1..0) so a non-square matrix with more rows than cols reaches its
351        // lower sub-diagonals (square matrices are unaffected, d1 == d2).
352        let (offset_diag, d_diag) = if (-d1 + 1..0).contains(&offset) {
353            let offset = -offset;
354            let offset_diag = (self.offset() as isize + t1 * offset) as usize;
355            let d_diag = (d1 - offset).min(d2) as usize;
356            (offset_diag, d_diag)
357        } else if (0..d1).contains(&offset) {
358            let offset_diag = (self.offset() as isize + t2 * offset) as usize;
359            let d_diag = (d2 - offset).min(d1) as usize;
360            (offset_diag, d_diag)
361        } else {
362            (self.offset(), 0)
363        };
364
365        // build new layout
366        let t_diag = t1 + t2;
367        let mut shape_diag = vec![];
368        let mut stride_diag = vec![];
369        for i in 0..self.ndim() {
370            if i != axis1 && i != axis2 {
371                shape_diag.push(self.shape()[i]);
372                stride_diag.push(self.stride()[i]);
373            }
374        }
375        shape_diag.push(d_diag);
376        stride_diag.push(t_diag);
377        let layout_diag = Layout::new(shape_diag, stride_diag, offset_diag)?;
378        return layout_diag.into_dim::<<D as DimSmallerOneAPI>::SmallerOne>();
379    }
380}
381
382/// Constructors of layout. See also [`DimLayoutContigAPI`] layout from shape
383/// directly.
384impl<D> Layout<D>
385where
386    D: DimBaseAPI,
387{
388    /// Generate new layout by providing everything.
389    ///
390    /// # Error when
391    ///
392    /// - Shape and stride length mismatch
393    /// - Strides is correct (no elements can overlap)
394    /// - Minimum bound is not negative
395    #[inline]
396    pub fn new(shape: D, stride: D::Stride, offset: usize) -> Result<Self>
397    where
398        D: DimShapeAPI,
399    {
400        let layout = unsafe { Layout::new_unchecked(shape, stride, offset) };
401        layout.bounds_index()?;
402        layout.check_strides(true)?;
403        return Ok(layout);
404    }
405
406    /// Generate new layout by providing everything, without checking bounds and
407    /// strides.
408    ///
409    /// # Safety
410    ///
411    /// This function does not check whether layout is valid.
412    #[inline]
413    pub unsafe fn new_unchecked(shape: D, stride: D::Stride, offset: usize) -> Self {
414        Layout { shape, stride, offset }
415    }
416
417    /// New zero shape, which number of dimensions are the same to current
418    /// layout.
419    #[inline]
420    pub fn new_shape(&self) -> D {
421        self.shape.new_shape()
422    }
423
424    /// New zero stride, which number of dimensions are the same to current
425    /// layout.
426    #[inline]
427    pub fn new_stride(&self) -> D::Stride {
428        self.shape.new_stride()
429    }
430}
431
432/// Manipulation of layout.
433impl<D> Layout<D>
434where
435    D: DimBaseAPI + DimShapeAPI,
436{
437    /// Transpose layout by permutation.
438    ///
439    /// # See also
440    ///
441    /// - [`numpy.transpose`](https://numpy.org/doc/stable/reference/generated/numpy.transpose.html)
442    /// - [Python array API: `permute_dims`](https://data-apis.org/array-api/2024.12/API_specification/generated/array_api.permute_dims.html)
443    pub fn transpose(&self, axes: &[isize]) -> Result<Self> {
444        // check axes and cast to usize
445        let n = self.ndim();
446        rstsr_assert_eq!(
447            axes.len(),
448            n,
449            InvalidLayout,
450            "number of elements in axes should be the same to number of dimensions."
451        )?;
452        // normalize axes; since we have checked number of elements, and not allowed duplicate, so
453        // no other check is needed for axes
454        let axes = normalize_axes_index(axes.into(), n, false, false)?;
455        let axes = axes.into_iter().map(|a| a as usize).collect::<Vec<usize>>();
456
457        let shape_old = self.shape();
458        let stride_old = self.stride();
459        let mut shape = self.new_shape();
460        let mut stride = self.new_stride();
461        for i in 0..self.ndim() {
462            shape[i] = shape_old[axes[i]];
463            stride[i] = stride_old[axes[i]];
464        }
465        return unsafe { Ok(Layout::new_unchecked(shape, stride, self.offset)) };
466    }
467
468    /// Transpose layout by permutation.
469    ///
470    /// This is the same function to [`Layout::transpose`]
471    pub fn permute_dims(&self, axes: &[isize]) -> Result<Self> {
472        self.transpose(axes)
473    }
474
475    /// Reverse axes of layout.
476    pub fn reverse_axes(&self) -> Self {
477        let shape_old = self.shape();
478        let stride_old = self.stride();
479        let mut shape = self.new_shape();
480        let mut stride = self.new_stride();
481        for i in 0..self.ndim() {
482            shape[i] = shape_old[self.ndim() - i - 1];
483            stride[i] = stride_old[self.ndim() - i - 1];
484        }
485        return unsafe { Layout::new_unchecked(shape, stride, self.offset) };
486    }
487
488    /// Swap axes of layout.
489    pub fn swapaxes(&self, axis1: isize, axis2: isize) -> Result<Self> {
490        let axis1 = rstsr_check_axis!(axis1, self.ndim())?;
491        let axis2 = rstsr_check_axis!(axis2, self.ndim())?;
492
493        let mut shape = self.shape().clone();
494        let mut stride = self.stride().clone();
495        shape.as_mut().swap(axis1, axis2);
496        stride.as_mut().swap(axis1, axis2);
497        return unsafe { Ok(Layout::new_unchecked(shape, stride, self.offset)) };
498    }
499}
500
501/// Fast indexing and utilities of layout.
502///
503/// These functions are mostly internal to this crate.
504impl<D> Layout<D>
505where
506    D: DimBaseAPI + DimShapeAPI,
507{
508    /// Index of tensor by list of indexes to dimensions.
509    ///
510    /// # Safety
511    ///
512    /// This function does not check for bounds, including
513    /// - Negative index
514    /// - Index greater than shape
515    ///
516    /// Due to these reasons, this function may well give index smaller than
517    /// zero, which may occur in iterator; so this function returns isize.
518    #[inline]
519    pub unsafe fn index_uncheck(&self, index: &[usize]) -> isize {
520        let stride = self.stride.as_ref();
521        match self.ndim() {
522            0 => self.offset as isize,
523            1 => self.offset as isize + stride[0] * index[0] as isize,
524            2 => self.offset as isize + stride[0] * index[0] as isize + stride[1] * index[1] as isize,
525            3 => {
526                self.offset as isize
527                    + stride[0] * index[0] as isize
528                    + stride[1] * index[1] as isize
529                    + stride[2] * index[2] as isize
530            },
531            4 => {
532                self.offset as isize
533                    + stride[0] * index[0] as isize
534                    + stride[1] * index[1] as isize
535                    + stride[2] * index[2] as isize
536                    + stride[3] * index[3] as isize
537            },
538            _ => {
539                let mut pos = self.offset as isize;
540                stride.iter().zip(index.iter()).for_each(|(&s, &i)| pos += s * i as isize);
541                pos
542            },
543        }
544    }
545}
546
547impl<D> PartialEq for Layout<D>
548where
549    D: DimBaseAPI,
550{
551    /// For layout, shape must be the same, offset must be the same, while stride should be the same
552    /// when shape is not zero or one, but can be arbitary otherwise.
553    fn eq(&self, other: &Self) -> bool {
554        if self.ndim() != other.ndim() {
555            return false;
556        }
557        if self.offset != other.offset {
558            return false;
559        }
560        for i in 0..self.ndim() {
561            let s1 = self.shape()[i];
562            let s2 = other.shape()[i];
563            if s1 != s2 {
564                return false;
565            }
566            if s1 != 1 && s1 != 0 && self.stride()[i] != other.stride()[i] {
567                return false;
568            }
569        }
570        return true;
571    }
572}
573
574pub trait DimLayoutContigAPI: DimBaseAPI + DimShapeAPI {
575    /// Generate new layout by providing shape and offset; stride fits into
576    /// c-contiguous.
577    fn new_c_contig(&self, offset: Option<usize>) -> Layout<Self> {
578        let shape = self.clone();
579        let stride = shape.stride_c_contig();
580        unsafe { Layout::new_unchecked(shape, stride, offset.unwrap_or(0)) }
581    }
582
583    /// Generate new layout by providing shape and offset; stride fits into
584    /// f-contiguous.
585    fn new_f_contig(&self, offset: Option<usize>) -> Layout<Self> {
586        let shape = self.clone();
587        let stride = shape.stride_f_contig();
588        unsafe { Layout::new_unchecked(shape, stride, offset.unwrap_or(0)) }
589    }
590
591    /// Simplified function to generate c-contiguous layout. See also
592    /// [DimLayoutContigAPI::new_c_contig].
593    fn c(&self) -> Layout<Self> {
594        self.new_c_contig(None)
595    }
596
597    /// Simplified function to generate f-contiguous layout. See also
598    /// [DimLayoutContigAPI::new_f_contig].
599    fn f(&self) -> Layout<Self> {
600        self.new_f_contig(None)
601    }
602
603    /// Generate new layout by providing shape, offset and order.
604    fn new_contig(&self, offset: Option<usize>, order: FlagOrder) -> Layout<Self> {
605        match order {
606            FlagOrder::C => self.new_c_contig(offset),
607            FlagOrder::F => self.new_f_contig(offset),
608        }
609    }
610}
611
612impl<const N: usize> DimLayoutContigAPI for Ix<N> {}
613impl DimLayoutContigAPI for IxD {}
614
615/* #endregion Layout */
616
617/* #region Dimension Conversion */
618
619pub trait DimIntoAPI<D>: DimBaseAPI
620where
621    D: DimBaseAPI,
622{
623    fn into_dim(layout: Layout<Self>) -> Result<Layout<D>>;
624}
625
626impl<D> DimIntoAPI<D> for IxD
627where
628    D: DimBaseAPI,
629{
630    fn into_dim(layout: Layout<IxD>) -> Result<Layout<D>> {
631        let shape = layout.shape().clone().try_into().map_err(|_| rstsr_error!(InvalidLayout))?;
632        let stride = layout.stride().clone().try_into().map_err(|_| rstsr_error!(InvalidLayout))?;
633        let offset = layout.offset();
634        return Ok(Layout { shape, stride, offset });
635    }
636}
637
638impl<const N: usize> DimIntoAPI<IxD> for Ix<N> {
639    fn into_dim(layout: Layout<Ix<N>>) -> Result<Layout<IxD>> {
640        let shape = (*layout.shape()).into();
641        let stride = (*layout.stride()).into();
642        let offset = layout.offset();
643        return Ok(Layout { shape, stride, offset });
644    }
645}
646
647impl<const N: usize, const M: usize> DimIntoAPI<Ix<M>> for Ix<N> {
648    fn into_dim(layout: Layout<Ix<N>>) -> Result<Layout<Ix<M>>> {
649        rstsr_assert_eq!(N, M, InvalidLayout)?;
650        let shape = layout.shape().to_vec().try_into().unwrap();
651        let stride = layout.stride().to_vec().try_into().unwrap();
652        let offset = layout.offset();
653        return Ok(Layout { shape, stride, offset });
654    }
655}
656
657impl<D> Layout<D>
658where
659    D: DimBaseAPI,
660{
661    /// Convert layout to another dimension.
662    pub fn into_dim<D2>(self) -> Result<Layout<D2>>
663    where
664        D2: DimBaseAPI,
665        D: DimIntoAPI<D2>,
666    {
667        D::into_dim(self)
668    }
669
670    /// Convert layout to another dimension.
671    pub fn to_dim<D2>(&self) -> Result<Layout<D2>>
672    where
673        D2: DimBaseAPI,
674        D: DimIntoAPI<D2>,
675    {
676        D::into_dim(self.clone())
677    }
678}
679
680impl<const N: usize> From<Ix<N>> for Layout<Ix<N>> {
681    fn from(shape: Ix<N>) -> Self {
682        let stride = shape.stride_contig();
683        Layout { shape, stride, offset: 0 }
684    }
685}
686
687impl From<IxD> for Layout<IxD> {
688    fn from(shape: IxD) -> Self {
689        let stride = shape.stride_contig();
690        Layout { shape, stride, offset: 0 }
691    }
692}
693
694/* #endregion */
695
696#[cfg(test)]
697mod test {
698    use std::panic::catch_unwind;
699
700    use super::*;
701
702    #[test]
703    fn test_layout_new() {
704        // a successful layout new
705        let shape = [3, 2, 6];
706        let stride = [3, -300, 15];
707        let layout = Layout::new(shape, stride, 917).unwrap();
708        assert_eq!(layout.shape(), &[3, 2, 6]);
709        assert_eq!(layout.stride(), &[3, -300, 15]);
710        assert_eq!(layout.offset(), 917);
711        assert_eq!(layout.ndim(), 3);
712        // unsuccessful layout new (offset underflow)
713        let shape = [3, 2, 6];
714        let stride = [3, -300, 15];
715        let layout = Layout::new(shape, stride, 0);
716        assert!(layout.is_err());
717        // unsuccessful layout new (stride too small)
718        let shape = [3, 2, 6];
719        let stride = [3, 4, 7];
720        let layout = Layout::new(shape, stride, 1000);
721        assert!(layout.is_err());
722        // successful layout new (zero stride for non-0/1 shape)
723        let shape = [3, 2, 6];
724        let stride = [3, -300, 0];
725        let layout = Layout::new(shape, stride, 1000);
726        assert!(layout.is_ok());
727        // successful layout new (zero dim)
728        let shape = [];
729        let stride = [];
730        let layout = Layout::new(shape, stride, 1000);
731        assert!(layout.is_ok());
732        // successful layout new (stride 0 for 1-shape)
733        let shape = [3, 1, 5];
734        let stride = [1, 0, 15];
735        let layout = Layout::new(shape, stride, 1);
736        assert!(layout.is_ok());
737        // successful layout new (stride 0 for 1-shape)
738        let shape = [3, 1, 5];
739        let stride = [1, 0, 15];
740        let layout = Layout::new(shape, stride, 1);
741        assert!(layout.is_ok());
742        // successful layout new (zero-size tensor)
743        let shape = [3, 0, 5];
744        let stride = [-1, -2, -3];
745        let layout = Layout::new(shape, stride, 1);
746        assert!(layout.is_ok());
747        // anyway, if one need custom layout, use new_unchecked
748        let shape = [3, 2, 6];
749        let stride = [3, -300, 0];
750        let r = catch_unwind(|| unsafe { Layout::new_unchecked(shape, stride, 1000) });
751        assert!(r.is_ok());
752    }
753
754    #[test]
755    fn test_is_f_prefer() {
756        // general case
757        let shape = [3, 5, 7];
758        let layout = Layout::new(shape, [1, 10, 100], 0).unwrap();
759        assert!(layout.f_prefer());
760        let layout = Layout::new(shape, [1, 3, 15], 0).unwrap();
761        assert!(layout.f_prefer());
762        let layout = Layout::new(shape, [1, 3, -15], 1000).unwrap();
763        assert!(!layout.f_prefer());
764        let layout = Layout::new(shape, [1, 21, 3], 0).unwrap();
765        assert!(!layout.f_prefer());
766        let layout = Layout::new(shape, [35, 7, 1], 0).unwrap();
767        assert!(!layout.f_prefer());
768        let layout = Layout::new(shape, [2, 6, 30], 0).unwrap();
769        assert!(!layout.f_prefer());
770        // zero dimension
771        let layout = Layout::new([], [], 0).unwrap();
772        assert!(layout.f_prefer());
773        // zero size
774        let layout = Layout::new([2, 0, 4], [1, 10, 100], 0).unwrap();
775        assert!(layout.f_prefer());
776        // shape with 1
777        let layout = Layout::new([2, 1, 4], [1, 1, 2], 0).unwrap();
778        assert!(layout.f_prefer());
779    }
780
781    #[test]
782    fn test_is_c_prefer() {
783        // general case
784        let shape = [3, 5, 7];
785        let layout = Layout::new(shape, [100, 10, 1], 0).unwrap();
786        assert!(layout.c_prefer());
787        let layout = Layout::new(shape, [35, 7, 1], 0).unwrap();
788        assert!(layout.c_prefer());
789        let layout = Layout::new(shape, [-35, 7, 1], 1000).unwrap();
790        assert!(!layout.c_prefer());
791        let layout = Layout::new(shape, [7, 21, 1], 0).unwrap();
792        assert!(!layout.c_prefer());
793        let layout = Layout::new(shape, [1, 3, 15], 0).unwrap();
794        assert!(!layout.c_prefer());
795        let layout = Layout::new(shape, [70, 14, 2], 0).unwrap();
796        assert!(!layout.c_prefer());
797        // zero dimension
798        let layout = Layout::new([], [], 0).unwrap();
799        assert!(layout.c_prefer());
800        // zero size
801        let layout = Layout::new([2, 0, 4], [1, 10, 100], 0).unwrap();
802        assert!(layout.c_prefer());
803        // shape with 1
804        let layout = Layout::new([2, 1, 4], [4, 1, 1], 0).unwrap();
805        assert!(layout.c_prefer());
806    }
807
808    #[test]
809    fn test_is_f_contig() {
810        // general case
811        let shape = [3, 5, 7];
812        let layout = Layout::new(shape, [1, 3, 15], 0).unwrap();
813        assert!(layout.f_contig());
814        let layout = Layout::new(shape, [1, 4, 20], 0).unwrap();
815        assert!(!layout.f_contig());
816        // zero dimension
817        let layout = Layout::new([], [], 0).unwrap();
818        assert!(layout.f_contig());
819        // zero size
820        let layout = Layout::new([2, 0, 4], [1, 10, 100], 0).unwrap();
821        assert!(layout.f_contig());
822        // shape with 1
823        let layout = Layout::new([2, 1, 4], [1, 1, 2], 0).unwrap();
824        assert!(layout.f_contig());
825    }
826
827    #[test]
828    fn test_is_c_contig() {
829        // general case
830        let shape = [3, 5, 7];
831        let layout = Layout::new(shape, [35, 7, 1], 0).unwrap();
832        assert!(layout.c_contig());
833        let layout = Layout::new(shape, [36, 7, 1], 0).unwrap();
834        assert!(!layout.c_contig());
835        // zero dimension
836        let layout = Layout::new([], [], 0).unwrap();
837        assert!(layout.c_contig());
838        // zero size
839        let layout = Layout::new([2, 0, 4], [1, 10, 100], 0).unwrap();
840        assert!(layout.c_contig());
841        // shape with 1
842        let layout = Layout::new([2, 1, 4], [4, 1, 1], 0).unwrap();
843        assert!(layout.c_contig());
844    }
845
846    #[test]
847    fn test_index() {
848        // a = np.arange(9 * 12 * 15)
849        //       .reshape(9, 12, 15)[4:2:-1, 4:10, 2:10:3]
850        //       .transpose(2, 0, 1)
851        let layout = Layout::new([3, 2, 6], [3, -180, 15], 782).unwrap();
852        assert_eq!(layout.index(&[0, 0, 0]), 782);
853        assert_eq!(layout.index(&[2, 1, 4]), 668);
854        assert_eq!(layout.index(&[1, -2, -3]), 830);
855        // zero-dim
856        let layout = Layout::new([], [], 10).unwrap();
857        assert_eq!(layout.index(&[]), 10);
858    }
859
860    #[test]
861    fn test_bounds_index() {
862        // a = np.arange(9 * 12 * 15)
863        //       .reshape(9, 12, 15)[4:2:-1, 4:10, 2:10:3]
864        //       .transpose(2, 0, 1)
865        // a.min() = 602, a.max() = 863
866        let layout = Layout::new([3, 2, 6], [3, -180, 15], 782).unwrap();
867        assert_eq!(layout.bounds_index().unwrap(), (602, 864));
868        // situation that fails
869        let layout = unsafe { Layout::new_unchecked([3, 2, 6], [3, -180, 15], 15) };
870        assert!(layout.bounds_index().is_err());
871        // zero-dim
872        let layout = Layout::new([], [], 10).unwrap();
873        assert_eq!(layout.bounds_index().unwrap(), (10, 11));
874    }
875
876    #[test]
877    fn test_transpose() {
878        // general
879        let layout = Layout::new([3, 2, 6], [3, -180, 15], 782).unwrap();
880        let trans = layout.transpose(&[2, 0, 1]).unwrap();
881        assert_eq!(trans.shape(), &[6, 3, 2]);
882        assert_eq!(trans.stride(), &[15, 3, -180]);
883        // permute_dims is alias of transpose
884        let trans = layout.permute_dims(&[2, 0, 1]).unwrap();
885        assert_eq!(trans.shape(), &[6, 3, 2]);
886        assert_eq!(trans.stride(), &[15, 3, -180]);
887        // negative axis also allowed
888        let trans = layout.transpose(&[-1, 0, 1]).unwrap();
889        assert_eq!(trans.shape(), &[6, 3, 2]);
890        assert_eq!(trans.stride(), &[15, 3, -180]);
891        // repeated axis
892        let trans = layout.transpose(&[-2, 0, 1]);
893        assert!(trans.is_err());
894        // non-valid dimension
895        let trans = layout.transpose(&[1, 0]);
896        assert!(trans.is_err());
897        // zero-dim
898        let layout = Layout::new([], [], 0).unwrap();
899        let trans = layout.transpose(&[]);
900        assert!(trans.is_ok());
901    }
902
903    #[test]
904    fn test_reverse_axes() {
905        // general
906        let layout = Layout::new([3, 2, 6], [3, -180, 15], 782).unwrap();
907        let trans = layout.reverse_axes();
908        assert_eq!(trans.shape(), &[6, 2, 3]);
909        assert_eq!(trans.stride(), &[15, -180, 3]);
910        // zero-dim
911        let layout = Layout::new([], [], 782).unwrap();
912        let trans = layout.reverse_axes();
913        assert_eq!(trans.shape(), &[]);
914        assert_eq!(trans.stride(), &[]);
915    }
916
917    #[test]
918    fn test_swapaxes() {
919        // general
920        let layout = Layout::new([3, 2, 6], [3, -180, 15], 782).unwrap();
921        let trans = layout.swapaxes(-1, -2).unwrap();
922        assert_eq!(trans.shape(), &[3, 6, 2]);
923        assert_eq!(trans.stride(), &[3, 15, -180]);
924        // same index is allowed
925        let layout = Layout::new([3, 2, 6], [3, -180, 15], 782).unwrap();
926        let trans = layout.swapaxes(-1, -1).unwrap();
927        assert_eq!(trans.shape(), &[3, 2, 6]);
928        assert_eq!(trans.stride(), &[3, -180, 15]);
929    }
930
931    #[test]
932    fn test_index_uncheck() {
933        // a = np.arange(9 * 12 * 15)
934        //       .reshape(9, 12, 15)[4:2:-1, 4:10, 2:10:3]
935        //       .transpose(2, 0, 1)
936        unsafe {
937            // fixed dim
938            let layout = Layout::new([3, 2, 6], [3, -180, 15], 782).unwrap();
939            assert_eq!(layout.index_uncheck(&[0, 0, 0]), 782);
940            assert_eq!(layout.index_uncheck(&[2, 1, 4]), 668);
941            // dynamic dim
942            let layout = Layout::new(vec![3, 2, 6], vec![3, -180, 15], 782).unwrap();
943            assert_eq!(layout.index_uncheck(&[0, 0, 0]), 782);
944            assert_eq!(layout.index_uncheck(&[2, 1, 4]), 668);
945            // zero-dim
946            let layout = Layout::new([], [], 10).unwrap();
947            assert_eq!(layout.index_uncheck(&[]), 10);
948        }
949    }
950
951    #[test]
952    fn test_diagonal() {
953        let layout = [2, 3, 4].c();
954        let diag = layout.diagonal(None, None, None).unwrap();
955        assert_eq!(diag, Layout::new([4, 2], [1, 16], 0).unwrap());
956        let diag = layout.diagonal(Some(-1), Some(-2), Some(-1)).unwrap();
957        assert_eq!(diag, Layout::new([2, 2], [12, 5], 4).unwrap()); // fixed at issue 77
958        let diag = layout.diagonal(Some(-4), Some(-2), Some(-1)).unwrap();
959        assert_eq!(diag, Layout::new([2, 0], [12, 5], 0).unwrap());
960    }
961
962    #[test]
963    fn test_new_contig() {
964        let layout = [3, 2, 6].c();
965        assert_eq!(layout.shape(), &[3, 2, 6]);
966        assert_eq!(layout.stride(), &[12, 6, 1]);
967        let layout = [3, 2, 6].f();
968        assert_eq!(layout.shape(), &[3, 2, 6]);
969        assert_eq!(layout.stride(), &[1, 3, 6]);
970        // following code generates contiguous layout
971        // c/f-contig depends on cargo feature
972        let layout: Layout<_> = [3, 2, 6].into();
973        println!("{layout:?}");
974    }
975
976    #[test]
977    fn test_layout_cast() {
978        let layout = [3, 2, 6].c();
979        assert!(layout.clone().into_dim::<IxD>().is_ok());
980        assert!(layout.clone().into_dim::<Ix3>().is_ok());
981        let layout = vec![3, 2, 6].c();
982        assert!(layout.clone().into_dim::<IxD>().is_ok());
983        assert!(layout.clone().into_dim::<Ix3>().is_ok());
984        assert!(layout.clone().into_dim::<Ix2>().is_err());
985    }
986
987    #[test]
988    fn test_unravel_index() {
989        unsafe {
990            let shape = [3, 2, 6];
991            assert_eq!(shape.unravel_index_f(0), [0, 0, 0]);
992            assert_eq!(shape.unravel_index_f(16), [1, 1, 2]);
993            assert_eq!(shape.unravel_index_c(0), [0, 0, 0]);
994            assert_eq!(shape.unravel_index_c(16), [1, 0, 4]);
995        }
996    }
997
998    #[test]
999    fn fix_too_strict_stride_check() {
1000        let layout = [10, 11, 12].c();
1001        let slc = (.., slice!(-1, 0, -4));
1002        let slc: AxesIndex<Indexer> = slc.try_into().unwrap();
1003        let indexed = layout.dim_slice(slc.as_ref()).unwrap();
1004        assert_eq!(indexed.shape(), &[10, 3, 12]);
1005        assert_eq!(indexed.stride(), &[132, -48, 1]);
1006    }
1007}