diff --git a/benches/bench1.rs b/benches/bench1.rs index 382ec94fb..992143b74 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -48,7 +48,7 @@ fn sum_1d_raw(bench: &mut test::Bencher) let a = black_box(a); bench.iter(|| { let mut sum = 0; - for &elt in a.raw_data() { + for &elt in a.as_slice_memory_order().unwrap() { sum += elt; } sum @@ -93,7 +93,7 @@ fn sum_2d_raw(bench: &mut test::Bencher) let a = black_box(a); bench.iter(|| { let mut sum = 0; - for &elt in a.raw_data() { + for &elt in a.as_slice_memory_order().unwrap() { sum += elt; } sum @@ -373,12 +373,12 @@ fn muladd_2d_f32_blas(bench: &mut test::Bencher) } #[bench] -fn assign_scalar_2d_large(bench: &mut test::Bencher) +fn assign_scalar_2d_corder(bench: &mut test::Bencher) { let a = OwnedArray::zeros((64, 64)); let mut a = black_box(a); let s = 3.; - bench.iter(|| a.assign_scalar(&s)) + bench.iter(move || a.assign_scalar(&s)) } #[bench] @@ -388,26 +388,43 @@ fn assign_scalar_2d_cutout(bench: &mut test::Bencher) let a = a.slice_mut(s![1..-1, 1..-1]); let mut a = black_box(a); let s = 3.; - bench.iter(|| a.assign_scalar(&s)) + bench.iter(move || a.assign_scalar(&s)) } #[bench] -fn assign_scalar_2d_transposed_large(bench: &mut test::Bencher) +fn assign_scalar_2d_forder(bench: &mut test::Bencher) { let mut a = OwnedArray::zeros((64, 64)); a.swap_axes(0, 1); let mut a = black_box(a); let s = 3.; - bench.iter(|| a.assign_scalar(&s)) + bench.iter(move || a.assign_scalar(&s)) } #[bench] -fn assign_scalar_2d_raw_large(bench: &mut test::Bencher) +fn assign_zero_2d_corder(bench: &mut test::Bencher) { let a = OwnedArray::zeros((64, 64)); let mut a = black_box(a); - let s = 3.; - bench.iter(|| for elt in a.raw_data_mut() { *elt = s; }); + bench.iter(|| a.assign_scalar(&0.)) +} + +#[bench] +fn assign_zero_2d_cutout(bench: &mut test::Bencher) +{ + let mut a = OwnedArray::zeros((66, 66)); + let a = a.slice_mut(s![1..-1, 1..-1]); + let mut a = black_box(a); + bench.iter(|| a.assign_scalar(&0.)) +} + +#[bench] +fn assign_zero_2d_forder(bench: &mut test::Bencher) +{ + let mut a = OwnedArray::zeros((64, 64)); + a.swap_axes(0, 1); + let mut a = black_box(a); + bench.iter(|| a.assign_scalar(&0.)) } #[bench] diff --git a/src/dimension.rs b/src/dimension.rs index f7c768bc7..c22540bd3 100644 --- a/src/dimension.rs +++ b/src/dimension.rs @@ -8,6 +8,7 @@ use std::cmp::Ordering; use std::fmt::Debug; use std::slice; +use itertools::free::enumerate; use super::{Si, Ix, Ixs}; use super::zipsl; @@ -19,30 +20,6 @@ pub fn stride_offset(n: Ix, stride: Ix) -> isize { (n as isize) * ((stride as Ixs) as isize) } -/// Check whether `stride` is strictly positive -#[inline] -fn stride_is_positive(stride: Ix) -> bool { - (stride as Ixs) > 0 -} - -/// Return the axis ordering corresponding to the fastest variation -/// -/// Assumes that no stride value appears twice. This cannot yield the correct -/// result the strides are not positive. -fn fastest_varying_order(strides: &D) -> D { - let mut sorted = strides.clone(); - sorted.slice_mut().sort(); - let mut res = strides.clone(); - for (index, &val) in strides.slice().iter().enumerate() { - let sorted_ind = sorted.slice() - .iter() - .position(|&x| x == val) - .unwrap(); // cannot panic by construction - res.slice_mut()[sorted_ind] = index; - } - res -} - /// Check whether the given `dim` and `stride` lead to overlapping indices /// /// There is overlap if, when iterating through the dimensions in the order @@ -51,15 +28,19 @@ fn fastest_varying_order(strides: &D) -> D { /// /// The current implementation assumes strides to be positive pub fn dim_stride_overlap(dim: &D, strides: &D) -> bool { - let order = fastest_varying_order(strides); + let order = strides._fastest_varying_stride_order(); + let dim = dim.slice(); + let strides = strides.slice(); let mut prev_offset = 1; - for &index in order.slice().iter() { - let s = strides.slice()[index]; - if (s as isize) < prev_offset { + for &index in order.slice() { + let d = dim[index]; + let s = strides[index]; + // any stride is ok if dimension is 1 + if d != 1 && (s as isize) < prev_offset { return true; } - prev_offset = stride_offset(dim.slice()[index], s); + prev_offset = stride_offset(d, s); } false } @@ -74,33 +55,42 @@ pub fn dim_stride_overlap(dim: &D, strides: &D) -> bool { pub fn can_index_slice(data: &[A], dim: &D, strides: &D) -> Result<(), ShapeError> { - if strides.slice().iter().cloned().all(stride_is_positive) { - if dim.size_checked().is_none() { - return Err(from_kind(ErrorKind::OutOfBounds)); + // check lengths of axes. + let len = match dim.size_checked() { + Some(l) => l, + None => return Err(from_kind(ErrorKind::OutOfBounds)), + }; + // check if strides are strictly positive (zero ok for len 0) + for &s in strides.slice() { + let s = s as Ixs; + if s < 1 && (len != 0 || s < 0) { + return Err(from_kind(ErrorKind::Unsupported)); } - let mut last_index = dim.clone(); - for mut index in last_index.slice_mut().iter_mut() { - *index -= 1; - } - if let Some(offset) = stride_offset_checked_arithmetic(dim, - strides, - &last_index) - { - // offset is guaranteed to be positive so no issue converting - // to usize here - if (offset as usize) >= data.len() { - return Err(from_kind(ErrorKind::OutOfBounds)); - } - if dim_stride_overlap(dim, strides) { - return Err(from_kind(ErrorKind::Unsupported)); - } - } else { + } + if len == 0 { + return Ok(()); + } + // check that the maximum index is in bounds + let mut last_index = dim.clone(); + for mut index in last_index.slice_mut().iter_mut() { + *index -= 1; + } + if let Some(offset) = stride_offset_checked_arithmetic(dim, + strides, + &last_index) + { + // offset is guaranteed to be positive so no issue converting + // to usize here + if (offset as usize) >= data.len() { return Err(from_kind(ErrorKind::OutOfBounds)); } - Ok(()) + if dim_stride_overlap(dim, strides) { + return Err(from_kind(ErrorKind::Unsupported)); + } } else { - return Err(from_kind(ErrorKind::Unsupported)); + return Err(from_kind(ErrorKind::OutOfBounds)); } + Ok(()) } /// Return stride offset for this dimension and index. @@ -335,6 +325,21 @@ pub unsafe trait Dimension : Clone + Eq + Debug + Send + Sync { offset } + /// Return the axis ordering corresponding to the fastest variation + /// (in ascending order). + /// + /// Assumes that no stride value appears twice. This cannot yield the correct + /// result the strides are not positive. + #[doc(hidden)] + fn _fastest_varying_stride_order(&self) -> Self { + let mut indices = self.clone(); + for (i, elt) in enumerate(indices.slice_mut()) { + *elt = i; + } + let strides = self.slice(); + indices.slice_mut().sort_by_key(|&i| strides[i]); + indices + } } /// Implementation-specific extensions to `Dimension` @@ -484,6 +489,11 @@ unsafe impl Dimension for (Ix, Ix) { (self.1, 1) } + #[inline] + fn _fastest_varying_stride_order(&self) -> Self { + if self.0 as Ixs <= self.1 as Ixs { (0, 1) } else { (1, 0) } + } + #[inline] fn first_index(&self) -> Option<(Ix, Ix)> { let (m, n) = *self; @@ -563,6 +573,29 @@ unsafe impl Dimension for (Ix, Ix, Ix) { let (s, t, u) = *strides; stride_offset(i, s) + stride_offset(j, t) + stride_offset(k, u) } + + #[inline] + fn _fastest_varying_stride_order(&self) -> Self { + let mut stride = *self; + let mut order = (0, 1, 2); + macro_rules! swap { + ($stride:expr, $order:expr, $x:expr, $y:expr) => { + if $stride[$x] > $stride[$y] { + $stride.swap($x, $y); + $order.swap($x, $y); + } + } + } + { + // stable sorting network for 3 elements + let order = order.slice_mut(); + let strides = stride.slice_mut(); + swap![strides, order, 1, 2]; + swap![strides, order, 0, 1]; + swap![strides, order, 1, 2]; + } + order + } } macro_rules! large_dim { @@ -742,13 +775,6 @@ mod test { use super::Dimension; use error::StrideError; - #[test] - fn fastest_varying_order() { - let strides = (2, 8, 4, 1); - let order = super::fastest_varying_order(&strides); - assert_eq!(order.slice(), &[3, 0, 2, 1]); - } - #[test] fn slice_indexing_uncommon_strides() { let v: Vec<_> = (0..12).collect(); diff --git a/src/error.rs b/src/error.rs index e5ff22b92..16fec770c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -87,13 +87,13 @@ impl Error for ShapeError { impl fmt::Display for ShapeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.description().fmt(f) + write!(f, "ShapeError/{:?}: {}", self.kind(), self.description()) } } impl fmt::Debug for ShapeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ShapeError {:?}: {}", self.kind(), self.description()) + write!(f, "ShapeError/{:?}: {}", self.kind(), self.description()) } } diff --git a/src/impl_methods.rs b/src/impl_methods.rs index fa9e00227..7372b8be0 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -550,6 +550,7 @@ impl ArrayBase where S: Data, D: Dimension if self.strides == defaults { return true; } + if self.ndim() == 1 { return false; } // check all dimensions -- a dimension of length 1 can have unequal strides for (&dim, (&s, &ds)) in zipsl(self.dim.slice(), zipsl(self.strides(), defaults.slice())) @@ -561,11 +562,43 @@ impl ArrayBase where S: Data, D: Dimension true } + fn is_contiguous(&self) -> bool { + let defaults = self.dim.default_strides(); + if self.strides == defaults { + return true; + } + if self.ndim() == 1 { return false; } + let order = self.strides._fastest_varying_stride_order(); + let strides = self.strides.slice(); + + // FIXME: Negative strides + let dim = self.dim.slice(); + let mut cstride = 1; + for &i in order.slice() { + // a dimension of length 1 can have unequal strides + if dim[i] != 1 && strides[i] != cstride { + return false; + } + cstride *= dim[i]; + } + true + } + + /// Return a pointer to the first element in the array. + /// + /// Raw access to array elements needs to follow the strided indexing + /// scheme: an element at multi-index *I* in an array with strides *S* is + /// located at offset + /// + /// *Σ0 ≤ k < d Ik × Sk* + /// + /// where *d* is `self.ndim()`. #[inline(always)] pub fn as_ptr(&self) -> *const A { self.ptr } + /// Return a mutable pointer to the first element in the array. #[inline(always)] pub fn as_mut_ptr(&mut self) -> *mut A where S: DataMut @@ -574,8 +607,11 @@ impl ArrayBase where S: Data, D: Dimension self.ptr } - /// Return the array’s data as a slice, if it is contiguous and - /// the element order corresponds to the memory order. Return `None` otherwise. + /// Return the array’s data as a slice, if it is contiguous and in standard order. + /// Return `None` otherwise. + /// + /// If this function returns `Some(_)`, then the element order in the slice + /// corresponds to the logical order of the array’s elements. pub fn as_slice(&self) -> Option<&[A]> { if self.is_standard_layout() { unsafe { @@ -586,8 +622,8 @@ impl ArrayBase where S: Data, D: Dimension } } - /// Return the array’s data as a slice, if it is contiguous and - /// the element order corresponds to the memory order. Return `None` otherwise. + /// Return the array’s data as a slice, if it is contiguous and in standard order. + /// Return `None` otherwise. pub fn as_slice_mut(&mut self) -> Option<&mut [A]> where S: DataMut { @@ -601,6 +637,38 @@ impl ArrayBase where S: Data, D: Dimension } } + /// Return the array’s data as a slice if it is contiguous, + /// return `None` otherwise. + /// + /// If this function returns `Some(_)`, then the elements in the slice + /// have whatever order the elements have in memory. + /// + /// Implementation notes: Does not yet support negatively strided arrays. + pub fn as_slice_memory_order(&self) -> Option<&[A]> { + if self.is_contiguous() { + unsafe { + Some(slice::from_raw_parts(self.ptr, self.len())) + } + } else { + None + } + } + + /// Return the array’s data as a slice if it is contiguous, + /// return `None` otherwise. + pub fn as_slice_memory_order_mut(&mut self) -> Option<&mut [A]> + where S: DataMut + { + if self.is_contiguous() { + self.ensure_unique(); + unsafe { + Some(slice::from_raw_parts_mut(self.ptr, self.len())) + } + } else { + None + } + } + /// Transform the array into `shape`; any shape with the same number of /// elements is accepted. /// @@ -820,6 +888,7 @@ impl ArrayBase where S: Data, D: Dimension /// **Note:** Data memory order may not correspond to the index order /// of the array. Neither is the raw data slice is restricted to just the /// array’s view.
+ #[cfg_attr(has_deprecated, deprecated(note="Use .as_slice_memory_order() instead"))] pub fn raw_data(&self) -> &[A] where S: DataOwned, { @@ -834,6 +903,7 @@ impl ArrayBase where S: Data, D: Dimension /// /// **Note:** The data is uniquely held and nonaliased /// while it is mutably borrowed. + #[cfg_attr(has_deprecated, deprecated(note="Use .as_slice_memory_order_mut() instead"))] pub fn raw_data_mut(&mut self) -> &mut [A] where S: DataOwned + DataMut, { diff --git a/src/impl_numeric.rs b/src/impl_numeric.rs index 98486fa92..601128c0a 100644 --- a/src/impl_numeric.rs +++ b/src/impl_numeric.rs @@ -62,7 +62,7 @@ impl ArrayBase pub fn scalar_sum(&self) -> A where A: Clone + Add + libnum::Zero, { - if let Some(slc) = self.as_slice() { + if let Some(slc) = self.as_slice_memory_order() { return numeric_util::unrolled_sum(slc); } let mut sum = A::zero(); diff --git a/src/lib.rs b/src/lib.rs index 33f97b07d..51a2bd971 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -237,11 +237,14 @@ pub type Ixs = isize; /// for element indices in `.get()` and `array[index]`. The dimension type `Vec` /// allows a dynamic number of axes. /// -/// The default memory order of an array is *row major* order, where each -/// row is contiguous in memory. -/// A *column major* (a.k.a. fortran) memory order array has +/// The default memory order of an array is *row major* order (a.k.a ”c” order), +/// where each row is contiguous in memory. +/// A *column major* (a.k.a. “f” or fortran) memory order array has /// columns (or, in general, the outermost axis) with contiguous elements. /// +/// The logical order of any array’s elements is the row major order. +/// The iterators `.iter(), .iter_mut()` always adhere to this order, for example. +/// /// ## Slicing /// /// You can use slicing to create a view of a subset of the data in @@ -506,7 +509,7 @@ impl ArrayBase where S: DataMut, F: FnMut(&mut A) { - if let Some(slc) = self.as_slice_mut() { + if let Some(slc) = self.as_slice_memory_order_mut() { for elt in slc { f(elt); } diff --git a/src/linalg.rs b/src/linalg.rs index a4c57e257..672f38d2d 100644 --- a/src/linalg.rs +++ b/src/linalg.rs @@ -54,7 +54,7 @@ impl LinalgScalar for T pub trait NdFloat : Float + fmt::Display + fmt::Debug + fmt::LowerExp + fmt::UpperExp + - ScalarOperand + LinalgScalar + ScalarOperand + LinalgScalar + Send + Sync { } /// Floating-point element types `f32` and `f64`. @@ -70,7 +70,7 @@ pub trait NdFloat : Float + AddAssign + SubAssign + MulAssign + DivAssign + RemAssign + fmt::Display + fmt::Debug + fmt::LowerExp + fmt::UpperExp + - ScalarOperand + LinalgScalar + ScalarOperand + LinalgScalar + Send + Sync { } impl NdFloat for f32 { } diff --git a/tests/array.rs b/tests/array.rs index 9bdefd236..a7ed8f523 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -7,11 +7,14 @@ extern crate itertools; use ndarray::{RcArray, S, Si, OwnedArray, }; -use ndarray::{arr0, arr1, arr2, arr3, +use ndarray::{ + rcarr2, + arr0, arr1, arr2, arr3, aview0, aview1, aview2, aview_mut1, + Dimension, }; use ndarray::Indexes; use ndarray::Axis; @@ -243,8 +246,8 @@ fn swapaxes() assert_eq!(a, b); a.swap_axes(1, 1); assert_eq!(a, b); - assert!(a.raw_data() == [1., 2., 3., 4.]); - assert!(b.raw_data() == [1., 3., 2., 4.]); + assert_eq!(a.as_slice_memory_order(), Some(&[1., 2., 3., 4.][..])); + assert_eq!(b.as_slice_memory_order(), Some(&[1., 3., 2., 4.][..])); } #[test] @@ -379,11 +382,12 @@ fn map1() } #[test] -fn raw_data_mut() +fn as_slice_memory_order() { - let a = arr2(&[[1., 2.], [3., 4.0f32]]); + // test that mutation breaks sharing + let a = rcarr2(&[[1., 2.], [3., 4.0f32]]); let mut b = a.clone(); - for elt in b.raw_data_mut() { + for elt in b.as_slice_memory_order_mut().unwrap() { *elt = 0.; } assert!(a != b, "{:?} != {:?}", a, b); @@ -424,6 +428,123 @@ fn owned_array_with_stride() { assert_eq!(a.strides(), &[1, 4, 2]); } +macro_rules! assert_matches { + ($value:expr, $pat:pat) => { + match $value { + $pat => {} + ref err => panic!("assertion failed: `{}` matches `{}` found: {:?}", + stringify!($value), stringify!($pat), err), + } + } +} + +#[test] +fn from_vec_dim_stride_empty_1d() { + let empty: [f32; 0] = []; + assert_matches!(OwnedArray::from_vec_dim_stride(0, 1, empty.to_vec()), + Ok(_)); +} + +#[test] +fn from_vec_dim_stride_0d() { + let empty: [f32; 0] = []; + let one = [1.]; + let two = [1., 2.]; + // too few elements + assert_matches!(OwnedArray::from_vec_dim_stride((), (), empty.to_vec()), Err(_)); + // exact number of elements + assert_matches!(OwnedArray::from_vec_dim_stride((), (), one.to_vec()), Ok(_)); + // too many are ok + assert_matches!(OwnedArray::from_vec_dim_stride((), (), two.to_vec()), Ok(_)); +} + +#[test] +fn from_vec_dim_stride_2d_1() { + let two = [1., 2.]; + let d = (2, 1); + let s = d.default_strides(); + assert_matches!(OwnedArray::from_vec_dim_stride(d, s, two.to_vec()), Ok(_)); +} + +#[test] +fn from_vec_dim_stride_2d_2() { + let two = [1., 2.]; + let d = (1, 2); + let s = d.default_strides(); + assert_matches!(OwnedArray::from_vec_dim_stride(d, s, two.to_vec()), Ok(_)); +} + +#[test] +fn from_vec_dim_stride_2d_3() { + let a = arr3(&[[[1]], + [[2]], + [[3]]]); + let d = a.dim(); + let s = d.default_strides(); + assert_matches!(OwnedArray::from_vec_dim_stride(d, s, a.as_slice().unwrap().to_vec()), Ok(_)); +} + +#[test] +fn from_vec_dim_stride_2d_4() { + let a = arr3(&[[[1]], + [[2]], + [[3]]]); + let d = a.dim(); + let s = d.fortran_strides(); + assert_matches!(OwnedArray::from_vec_dim_stride(d, s, a.as_slice().unwrap().to_vec()), Ok(_)); +} + +#[test] +fn from_vec_dim_stride_2d_5() { + let a = arr3(&[[[1, 2, 3]]]); + let d = a.dim(); + let s = d.fortran_strides(); + assert_matches!(OwnedArray::from_vec_dim_stride(d, s, a.as_slice().unwrap().to_vec()), Ok(_)); +} + +#[test] +fn from_vec_dim_stride_2d_6() { + let a = [1., 2., 3., 4., 5., 6.]; + let d = (2, 1, 1); + let s = (2, 2, 1); + assert_matches!(OwnedArray::from_vec_dim_stride(d, s, a.to_vec()), Ok(_)); + + let d = (1, 2, 1); + let s = (2, 2, 1); + assert_matches!(OwnedArray::from_vec_dim_stride(d, s, a.to_vec()), Ok(_)); +} + +#[test] +fn from_vec_dim_stride_2d_7() { + // empty arrays can have 0 strides + let a: [f32; 0] = []; + // [[]] shape=[4, 0], strides=[0, 1] + let d = (4, 0); + let s = (0, 1); + assert_matches!(OwnedArray::from_vec_dim_stride(d, s, a.to_vec()), Ok(_)); +} + +#[test] +fn from_vec_dim_stride_2d_8() { + // strides must be strictly positive (nonzero) + let a = [1.]; + let d = (1, 1); + let s = (0, 1); + assert_matches!(OwnedArray::from_vec_dim_stride(d, s, a.to_vec()), Err(_)); +} + +#[test] +fn from_vec_dim_stride_2d_rejects() { + let two = [1., 2.]; + let d = (2, 2); + let s = (1, 0); + assert_matches!(OwnedArray::from_vec_dim_stride(d, s, two.to_vec()), Err(_)); + + let d = (2, 2); + let s = (0, 1); + assert_matches!(OwnedArray::from_vec_dim_stride(d, s, two.to_vec()), Err(_)); +} + #[test] fn views() { let a = RcArray::from_vec(vec![1, 2, 3, 4]).reshape((2, 2)); @@ -761,3 +882,27 @@ fn test_f_order() { let dupf = &f + &f; assert_eq!(dupc, dupf); } + +#[test] +fn test_contiguous() { + let c = arr3(&[[[1, 2, 3], + [4, 5, 6]], + [[4, 5, 6], + [7, 7, 7]]]); + assert!(c.is_standard_layout()); + assert!(c.as_slice_memory_order().is_some()); + let v = c.slice(s![.., 0..1, ..]); + assert!(!v.is_standard_layout()); + assert!(!v.as_slice_memory_order().is_some()); + + let v = c.slice(s![1..2, .., ..]); + assert!(v.is_standard_layout()); + assert!(v.as_slice_memory_order().is_some()); + let v = v.reversed_axes(); + assert!(!v.is_standard_layout()); + assert!(v.as_slice_memory_order().is_some()); + let mut v = v.reversed_axes(); + v.swap_axes(1, 2); + assert!(!v.is_standard_layout()); + assert!(v.as_slice_memory_order().is_some()); +} diff --git a/tests/dimension.rs b/tests/dimension.rs index 0f423eb6f..cbc843f3e 100644 --- a/tests/dimension.rs +++ b/tests/dimension.rs @@ -6,6 +6,7 @@ use ndarray::{ RemoveAxis, arr2, Axis, + Dimension, }; #[test] @@ -40,3 +41,21 @@ fn dyn_dimension() let z = OwnedArray::::zeros(dim.clone()); assert_eq!(z.shape(), &dim[..]); } + +#[test] +fn fastest_varying_order() { + let strides = (2, 8, 4, 1); + let order = strides._fastest_varying_stride_order(); + assert_eq!(order.slice(), &[3, 0, 2, 1]); + + assert_eq!((1, 3)._fastest_varying_stride_order(), (0, 1)); + assert_eq!((7, 2)._fastest_varying_stride_order(), (1, 0)); + assert_eq!((6, 1, 3)._fastest_varying_stride_order(), (1, 2, 0)); + + // it's important that it produces distinct indices. Prefer the stable order + // where 0 is before 1 when they are equal. + assert_eq!((2, 2)._fastest_varying_stride_order(), (0, 1)); + assert_eq!((2, 2, 1)._fastest_varying_stride_order(), (2, 0, 1)); + assert_eq!((2, 2, 3, 1, 2)._fastest_varying_stride_order(), (3, 0, 1, 4, 2)); +} +