Repository: rust-ndarray/ndarray (rust)

Issue:
Method to remove index along axis
A user on #rust-sci asked the best way to remove an element from an `Array1`. With current `ndarray`, if it's known to be contiguous (and with no extra elements in the `Vec` before/after the array data), probably the best way to do this is to convert to a `Vec` with `.into_raw_vec()`, remove the element with `.remove()`, and then convert the `Vec` back to an `Array1`. If the array is possibly discontiguous, it's simpler to allocate a new `Array1` and copy the data into it with slicing and `.assign()`.

It would be nice to provide a `remove_index_axis` method to provide this functionality for n-dimensional arrays.

Ideally, the implementation would reuse the existing allocation and just shift the existing elements.

- For the "remove index along outermost axis for a contiguous array" special case, it would probably be best to use `std::ptr::copy` to shift the elements (i.e. implement it like `Vec::remove`). For `Array`, we'd need to be careful about how to drop the removed data and adjust the length of the underlying `Vec`. If we want to implement `remove_index_axis` for `ArrayViewMut`, we'd have to consider what to place in the last subview along the axis (before reducing the length of the axis in the view) so that we aren't illegally duplicating the elements in the last subview. If we want to move the removed data into that location, we'd need to store it in a temporary allocation before shifting the other elements with `std::ptr::copy`. We'd also need to be careful to avoid panics while in an invalid state.

- For the general case, I'd suggest using something like `accumulate_axis_inplace` with `Clone` to copy the elements. In principle, we could implement the general case without `Clone`, but this would have the same safety challenges as using `std::ptr::copy` for the special case, while being more complex to implement.

For a first implementation, just implementing the general case would be useful, even if it isn't the most efficient approach in all cases.

Pull request description:
Add method .remove_index(axis, index)
Add method to remove the `index`th entry along all lanes of `axis`.
The method doesn't change the number of initialized elements, just like slicing in place works
(this could be confusing, w.r.t remove semantics).

Fixes #931

Patch:
diff --git a/README-quick-start.md b/README-quick-start.md
index 981ef3e9..a6f07310 100644
--- a/README-quick-start.md
+++ b/README-quick-start.md
@@ -100,7 +100,7 @@ fn main() {
 ```
 
 ### Some common create helper functions
-`linspace` - Create a 1-D array with 11 elements with values 0., …, 5.
+`linspace` - Create a 1-D array with 21 elements with values 0., …, 5.
 ```rust
 use ndarray::prelude::*;
 use ndarray::{Array, Ix3};
diff --git a/examples/axis_ops.rs b/examples/axis_ops.rs
index 98d2d3d7..624af32c 100644
--- a/examples/axis_ops.rs
+++ b/examples/axis_ops.rs
@@ -7,16 +7,7 @@
 
 use ndarray::prelude::*;
 
-/// Reorder a's axes so that they are in "standard" axis order;
-/// make sure axes are in positive stride direction, and merge adjacent
-/// axes if possible.
-///
-/// This changes the logical order of the elments in the
-/// array, so that if we read them in row-major order after regularization,
-/// it corresponds to their order in memory.
-///
-/// Errors if array has a 0-stride axis
-fn regularize<A, D>(a: &mut Array<A, D>) -> Result<(), &'static str>
+fn regularize<A, D>(a: &mut Array<A, D>) -> Result<(), ()>
 where
     D: Dimension,
     A: ::std::fmt::Debug,
@@ -25,9 +16,7 @@ where
     // reverse all neg axes
     while let Some(ax) = a.axes().find(|ax| ax.stride <= 0) {
         if ax.stride == 0 {
-            // no real reason to error on this case; other than
-            // stride == 0 is incompatible with an owned array.
-            return Err("Cannot regularize array with stride == 0 axis");
+            return Err(());
         }
         // reverse ax
         println!("Reverse {:?}", ax.axis);
@@ -38,11 +27,8 @@ where
     let mut i = 0;
     let n = a.ndim();
     while let Some(ax) = a.axes().rev().skip(i).min_by_key(|ax| ax.stride.abs()) {
-        let cur_axis = Axis(n - 1 - i);
-        if ax.axis != cur_axis {
-            a.swap_axes(cur_axis.index(), ax.axis.index());
-            println!("Swap {:?} <=> {:?}", cur_axis, ax.axis);
-        }
+        a.swap_axes(n - 1 - i, ax.axis.index());
+        println!("Swap {:?} <=> {}", ax.axis, n - 1 - i);
         i += 1;
     }
 
@@ -54,7 +40,7 @@ where
             break;
         }
     }
-    println!("Result:\n{:?}\n", a);
+    println!("{:?}", a);
     Ok(())
 }
 
@@ -66,24 +52,22 @@ fn main() {
     a.swap_axes(0, 1);
     a.swap_axes(0, 2);
     a.slice_collapse(s![.., ..;-1, ..]);
-    regularize(&mut a).unwrap();
+    regularize(&mut a).ok();
 
     let mut b = Array::<u8, _>::zeros((2, 3, 4));
     for (i, elt) in (0..).zip(&mut b) {
         *elt = i;
     }
-    regularize(&mut b).unwrap();
-
+    regularize(&mut b).ok();
     let mut b = b.into_shape(a.len()).unwrap();
-    regularize(&mut b).unwrap();
-
+    regularize(&mut b).ok();
     b.invert_axis(Axis(0));
-    regularize(&mut b).unwrap();
+    regularize(&mut b).ok();
 
     let mut a = Array::<u8, _>::zeros((2, 3, 4));
     for (i, elt) in (0..).zip(&mut a) {
         *elt = i;
     }
     a.slice_collapse(s![..;-1, ..;2, ..]);
-    regularize(&mut a).unwrap();
+    regularize(&mut a).ok();
 }
diff --git a/examples/column_standardize.rs b/examples/column_standardize.rs
index 6a1840f0..c360170b 100644
--- a/examples/column_standardize.rs
+++ b/examples/column_standardize.rs
@@ -1,26 +1,31 @@
-#[cfg(feature = "std")]
 use ndarray::prelude::*;
 
-#[cfg(feature = "std")]
+fn std1d(a: ArrayView1<'_, f64>) -> f64 {
+    let n = a.len() as f64;
+    if n == 0. {
+        return 0.;
+    }
+    let mean = a.sum() / n;
+    (a.fold(0., |acc, &x| acc + (x - mean).powi(2)) / n).sqrt()
+}
+
+fn std(a: &Array2<f64>, axis: Axis) -> Array1<f64> {
+    a.map_axis(axis, std1d)
+}
+
 fn main() {
-    // This example recreates the following from python/numpy
+    // "recreating the following"
     // counts -= np.mean(counts, axis=0)
     // counts /= np.std(counts, axis=0)
 
     let mut data = array![[-1., -2., -3.], [1., -3., 5.], [2., 2., 2.]];
 
     println!("{:8.4}", data);
-    println!("Mean along axis=0 (along columns):\n{:8.4}", data.mean_axis(Axis(0)).unwrap());
+    println!("{:8.4} (Mean axis=0)", data.mean_axis(Axis(0)).unwrap());
 
     data -= &data.mean_axis(Axis(0)).unwrap();
-    println!("Centered around mean:\n{:8.4}", data);
-
-    data /= &data.std_axis(Axis(0), 0.);
-    println!("Scaled to normalize std:\n{:8.4}", data);
+    println!("{:8.4}", data);
 
-    println!("New mean:\n{:8.4}", data.mean_axis(Axis(0)).unwrap());
-    println!("New std: \n{:8.4}", data.std_axis(Axis(0), 0.));
+    data /= &std(&data, Axis(0));
+    println!("{:8.4}", data);
 }
-
-#[cfg(not(feature = "std"))]
-fn main() {}
diff --git a/examples/sort-axis.rs b/examples/sort-axis.rs
index ee5ff346..d721571b 100644
--- a/examples/sort-axis.rs
+++ b/examples/sort-axis.rs
@@ -1,7 +1,3 @@
-//! This is an example of sorting arrays along an axis.
-//! This file may not be so instructive except for advanced users, instead it
-//! could be a "feature preview" before sorting is added to the main crate.
-//!
 use ndarray::prelude::*;
 use ndarray::{Data, RemoveAxis, Zip};
 
diff --git a/examples/zip_many.rs b/examples/zip_many.rs
index e2c5169f..40b855df 100644
--- a/examples/zip_many.rs
+++ b/examples/zip_many.rs
@@ -9,40 +9,45 @@ use ndarray::prelude::*;
 use ndarray::Zip;
 
 fn main() {
-    let n = 6;
-
+    let n = 16;
     let mut a = Array::<f32, _>::zeros((n, n));
-    let mut b = Array::<f32, _>::zeros((n, n));
+    let mut b = Array::<f32, _>::from_elem((n, n), 1.);
     for ((i, j), elt) in b.indexed_iter_mut() {
-        *elt = 1. / (1. + (i + 2 * j) as f32);
+        *elt /= 1. + (i + 2 * j) as f32;
     }
     let c = Array::<f32, _>::from_elem((n, n + 1), 1.7);
     let c = c.slice(s![.., ..-1]);
 
-    // Using Zip for arithmetic ops across a, b, c
-    Zip::from(&mut a).and(&b).and(&c)
-        .for_each(|a, &b, &c| *a = b + c);
-    assert_eq!(a, &b + &c);
+    {
+        let a = a.view_mut().reversed_axes();
+        azip!((a in a, &b in b.t()) *a = b);
+    }
+    assert_eq!(a, b);
 
-    // and this is how to do the *same thing* with azip!()
     azip!((a in &mut a, &b in &b, &c in c) *a = b + c);
-
-    println!("{:8.4}", a);
+    assert_eq!(a, &b + &c);
 
     // sum of each row
-    let mut sums = Array::zeros(a.nrows());
-    Zip::from(a.rows()).and(&mut sums)
-        .for_each(|row, sum| *sum = row.sum());
-    // show sums as a column matrix
-    println!("{:8.4}", sums.insert_axis(Axis(1)));
+    let ax = Axis(0);
+    let mut sums = Array::zeros(a.len_of(ax));
+    azip!((s in &mut sums, a in a.axis_iter(ax)) *s = a.sum());
 
-    // sum of each 2x2 chunk
+    // sum of each chunk
     let chunk_sz = (2, 2);
     let nchunks = (n / chunk_sz.0, n / chunk_sz.1);
     let mut sums = Array::zeros(nchunks);
-
-    Zip::from(a.exact_chunks(chunk_sz))
-        .and(&mut sums)
-        .for_each(|chunk, sum| *sum = chunk.sum());
-    println!("{:8.4}", sums);
+    azip!((s in &mut sums, a in a.exact_chunks(chunk_sz)) *s = a.sum());
+
+    // Let's imagine we split to parallelize
+    {
+        let (x, y) = Zip::indexed(&mut a).split();
+        x.for_each(|(_, j), elt| {
+            *elt = elt.powi(j as i32);
+        });
+
+        y.for_each(|(_, j), elt| {
+            *elt = elt.powi(j as i32);
+        });
+    }
+    println!("{:8.3?}", a);
 }
diff --git a/src/arraytraits.rs b/src/arraytraits.rs
index 0d273ec3..23f99b8f 100644
--- a/src/arraytraits.rs
+++ b/src/arraytraits.rs
@@ -252,7 +252,7 @@ where
         if let Some(self_s) = self.as_slice() {
             hash::Hash::hash_slice(self_s, state);
         } else {
-            for row in self.rows() {
+            for row in self.inner_rows() {
                 if let Some(row_s) = row.as_slice() {
                     hash::Hash::hash_slice(row_s, state);
                 } else {
diff --git a/src/dimension/axes.rs b/src/dimension/axes.rs
index fb1ff099..d6f1c728 100644
--- a/src/dimension/axes.rs
+++ b/src/dimension/axes.rs
@@ -48,11 +48,8 @@ pub struct Axes<'a, D> {
 /// Description of the axis, its length and its stride.
 #[derive(Debug)]
 pub struct AxisDescription {
-    /// Axis identifier (index)
     pub axis: Axis,
-    /// Length in count of elements of the current axis
     pub len: usize,
-    /// Stride in count of elements of the current axis
     pub stride: isize,
 }
 
diff --git a/src/impl_1d.rs b/src/impl_1d.rs
index 704d8643..74eaaad2 100644
--- a/src/impl_1d.rs
+++ b/src/impl_1d.rs
@@ -8,7 +8,10 @@
 
 //! Methods for one-dimensional arrays.
 use alloc::vec::Vec;
+use std::mem::MaybeUninit;
+
 use crate::imp_prelude::*;
+use crate::low_level_util::AbortIfPanic;
 
 /// # Methods For 1-D Arrays
 impl<A, S> ArrayBase<S, Ix1>
@@ -27,4 +30,35 @@ where
             crate::iterators::to_vec(self.iter().cloned())
         }
     }
+
+    /// Rotate the elements of the array by 1 element towards the front;
+    /// the former first element becomes the last.
+    pub(crate) fn rotate1_front(&mut self)
+    where
+        S: DataMut,
+    {
+        // use swapping to keep all elements initialized (as required by owned storage)
+        let mut lane_iter = self.iter_mut();
+        let mut dst = if let Some(dst) = lane_iter.next() { dst } else { return };
+
+        // Logically we do a circular swap here, all elements in a chain
+        // Using MaybeUninit to avoid unecessary writes in the safe swap solution
+        //
+        //  for elt in lane_iter {
+        //      std::mem::swap(dst, elt);
+        //      dst = elt;
+        //  }
+        //
+        let guard = AbortIfPanic(&"rotate1_front: temporarily moving out of owned value");
+        let mut slot = MaybeUninit::<A>::uninit();
+        unsafe {
+            slot.as_mut_ptr().copy_from_nonoverlapping(dst, 1);
+            for elt in lane_iter {
+                (dst as *mut A).copy_from_nonoverlapping(elt, 1);
+                dst = elt;
+            }
+            (dst as *mut A).copy_from_nonoverlapping(slot.as_ptr(), 1);
+        }
+        guard.defuse();
+    }
 }
diff --git a/src/impl_methods.rs b/src/impl_methods.rs
index efea69c4..03ca09d7 100644
--- a/src/impl_methods.rs
+++ b/src/impl_methods.rs
@@ -2444,6 +2444,30 @@ where
         }
     }
 
+    /// Remove the `index`th elements along `axis` and shift down elements from higher indexes.
+    ///
+    /// Note that this "removes" the elements by swapping them around to the end of the axis and
+    /// shortening the length of the axis; the elements are not deinitialized or dropped by this,
+    /// just moved out of view (this only matters for elements with ownership semantics). It's
+    /// similar to slicing an owned array in place.
+    ///
+    /// Decreases the length of `axis` by one.
+    ///
+    /// ***Panics*** if `axis` is out of bounds<br>
+    /// ***Panics*** if not `index < self.len_of(axis)`.
+    pub fn remove_index(&mut self, axis: Axis, index: usize)
+    where
+        S: DataOwned + DataMut,
+    {
+        assert!(index < self.len_of(axis), "index {} must be less than length of Axis({})",
+                index, axis.index());
+        let (_, mut tail) = self.view_mut().split_at(axis, index);
+        // shift elements to the front
+        Zip::from(tail.lanes_mut(axis)).for_each(|mut lane| lane.rotate1_front());
+        // then slice the axis in place to cut out the removed final element
+        self.slice_axis_inplace(axis, Slice::new(0, Some(-1), 1));
+    }
+
     /// Iterates over pairs of consecutive elements along the axis.
     ///
     /// The first argument to the closure is an element, and the second
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index d4271dd7..625fc7ff 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -170,34 +170,6 @@ mod tests {
         }
     }
 
-    #[test]
-    fn no_layouts() {
-        let a = M::zeros((5, 5));
-        let b = M::zeros((5, 5).f());
-
-        // 2D row/column matrixes
-        let arow = a.slice(s![0..1, ..]);
-        let acol = a.slice(s![.., 0..1]);
-        let brow = b.slice(s![0..1, ..]);
-        let bcol = b.slice(s![.., 0..1]);
-        assert_layouts!(arow, CORDER, FORDER);
-        assert_not_layouts!(acol, CORDER, CPREFER, FORDER, FPREFER);
-        assert_layouts!(bcol, CORDER, FORDER);
-        assert_not_layouts!(brow, CORDER, CPREFER, FORDER, FPREFER);
-
-        // 2D row/column matrixes - now made with insert axis
-        for &axis in &[Axis(0), Axis(1)] {
-            let arow = a.slice(s![0, ..]).insert_axis(axis);
-            let acol = a.slice(s![.., 0]).insert_axis(axis);
-            let brow = b.slice(s![0, ..]).insert_axis(axis);
-            let bcol = b.slice(s![.., 0]).insert_axis(axis);
-            assert_layouts!(arow, CORDER, FORDER);
-            assert_not_layouts!(acol, CORDER, CPREFER, FORDER, FPREFER);
-            assert_layouts!(bcol, CORDER, FORDER);
-            assert_not_layouts!(brow, CORDER, CPREFER, FORDER, FPREFER);
-        }
-    }
-
     #[test]
     fn skip_layouts() {
         let a = M::zeros((5, 5));
diff --git a/src/lib.rs b/src/lib.rs
index 51636836..762a5563 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -71,16 +71,16 @@
 //! `Cargo.toml`.
 //!
 //! - `std`
-//!   - Rust standard library (enabled by default)
-//!   - This crate can be used without the standard library by disabling the
-//!     default `std` feature. To do so, use `default-features = false` in
-//!     your `Cargo.toml`.
-//!   - The `geomspace` `linspace` `logspace` `range` `std` `var` `var_axis`
-//!     and `std_axis` methods are only available when `std` is enabled.
+//!  - Rust standard library (enabled by default)
+//!  - This crate can be used without the standard library by disabling the
+//!    default `std` feature. To do so, use `default-features = false` in
+//!    your `Cargo.toml`.
+//!  - The `geomspace` `linspace` `logspace` `range` `std` `var` `var_axis`
+//!    and `std_axis` methods are only available when `std` is enabled.
 //! - `serde`
 //!   - Enables serialization support for serde 1.x
 //! - `rayon`
-//!   - Enables parallel iterators, parallelized methods, the [`parallel`] module and [`par_azip!`].
+//!   - Enables parallel iterators, parallelized methods and [`par_azip!`].
 //!   - Implies std
 //! - `approx`
 //!   - Enables implementations of traits from the [`approx`] crate.
@@ -147,7 +147,7 @@ pub use crate::slice::{
 };
 
 use crate::iterators::Baseiter;
-use crate::iterators::{ElementsBase, ElementsBaseMut, Iter, IterMut};
+use crate::iterators::{ElementsBase, ElementsBaseMut, Iter, IterMut, Lanes};
 
 pub use crate::arraytraits::AsArray;
 #[cfg(feature = "std")]
@@ -205,6 +205,7 @@ mod shape_builder;
 mod slice;
 mod split_at;
 mod stacking;
+mod low_level_util;
 #[macro_use]
 mod zip;
 
@@ -1588,6 +1589,12 @@ where
             self.with_strides_dim(s, d)
         }
     }
+
+    /// n-d generalization of rows, just like inner iter
+    fn inner_rows(&self) -> iterators::Lanes<'_, A, D::Smaller> {
+        let n = self.ndim();
+        Lanes::new(self.view(), Axis(n.saturating_sub(1)))
+    }
 }
 
 // parallel methods
diff --git a/src/low_level_util.rs b/src/low_level_util.rs
new file mode 100644
index 00000000..b61b06f0
--- /dev/null
+++ b/src/low_level_util.rs
@@ -0,0 +1,40 @@
+// Copyright 2021 bluss and ndarray developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+
+/// Guard value that will abort if it is dropped.
+/// To defuse, this value must be forgotten before the end of the scope.
+///
+/// The string value is added to the message printed if aborting.
+#[must_use]
+pub(crate) struct AbortIfPanic(pub(crate) &'static &'static str);
+
+impl AbortIfPanic {
+    /// Defuse the AbortIfPanic guard. This *must* be done when finished.
+    #[inline]
+    pub(crate) fn defuse(self) {
+        std::mem::forget(self);
+    }
+}
+
+impl Drop for AbortIfPanic {
+    // The compiler should be able to remove this, if it can see through that there
+    // is no panic in the code section.
+    fn drop(&mut self) {
+        #[cfg(feature="std")]
+        {
+            eprintln!("ndarray: panic in no-panic section, aborting: {}", self.0);
+            std::process::abort()
+        }
+        #[cfg(not(feature="std"))]
+        {
+            // no-std uses panic-in-panic (should abort)
+            panic!("ndarray: panic in no-panic section, bailing out: {}", self.0);
+        }
+    }
+}
diff --git a/src/numeric/impl_numeric.rs b/src/numeric/impl_numeric.rs
index da170c9e..fbec8041 100644
--- a/src/numeric/impl_numeric.rs
+++ b/src/numeric/impl_numeric.rs
@@ -38,7 +38,7 @@ where
             return numeric_util::unrolled_fold(slc, A::zero, A::add);
         }
         let mut sum = A::zero();
-        for row in self.rows() {
+        for row in self.inner_rows() {
             if let Some(slc) = row.as_slice() {
                 sum = sum + numeric_util::unrolled_fold(slc, A::zero, A::add);
             } else {
@@ -103,7 +103,7 @@ where
             return numeric_util::unrolled_fold(slc, A::one, A::mul);
         }
         let mut sum = A::one();
-        for row in self.rows() {
+        for row in self.inner_rows() {
             if let Some(slc) = row.as_slice() {
                 sum = sum * numeric_util::unrolled_fold(slc, A::one, A::mul);
             } else {
diff --git a/src/slice.rs b/src/slice.rs
index 3cbdf2ee..3c554a5c 100644
--- a/src/slice.rs
+++ b/src/slice.rs
@@ -16,7 +16,7 @@ use std::ops::{Deref, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, Rang
 
 /// A slice (range with step size).
 ///
-/// `end` is an exclusive index. Negative `start` or `end` indexes are counted
+/// `end` is an exclusive index. Negative `begin` or `end` indexes are counted
 /// from the back of the axis. If `end` is `None`, the slice extends to the end
 /// of the axis.
 ///
@@ -36,12 +36,8 @@ use std::ops::{Deref, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, Rang
 /// The Python equivalent is `[a::-1]`.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 pub struct Slice {
-    /// start index; negative are counted from the back of the axis
     pub start: isize,
-    /// end index; negative are counted from the back of the axis; when not present
-    /// the default is the full length of the axis.
     pub end: Option<isize>,
-    /// step size in elements; the default is 1, for every element.
     pub step: isize,
 }
 
@@ -109,16 +105,12 @@ pub struct NewAxis;
 /// `[np.newaxis]`. The macro equivalent is `s![NewAxis]`.
 #[derive(Debug, PartialEq, Eq, Hash)]
 pub enum SliceInfoElem {
-    /// A range with step size. `end` is an exclusive index. Negative `start`
+    /// A range with step size. `end` is an exclusive index. Negative `begin`
     /// or `end` indexes are counted from the back of the axis. If `end` is
     /// `None`, the slice extends to the end of the axis.
     Slice {
-        /// start index; negative are counted from the back of the axis
         start: isize,
-        /// end index; negative are counted from the back of the axis; when not present
-        /// the default is the full length of the axis.
         end: Option<isize>,
-        /// step size in elements; the default is 1, for every element.
         step: isize,
     },
     /// A single index.
diff --git a/src/zip/mod.rs b/src/zip/mod.rs
index 5f9b15c5..38dbeb57 100644
--- a/src/zip/mod.rs
+++ b/src/zip/mod.rs
@@ -69,9 +69,9 @@ where
         } else if n > 1 && self.raw_view().reversed_axes().is_standard_layout() {
             Layout::f()
         } else if n > 1 {
-            if self.len_of(Axis(0)) > 1 && self.stride_of(Axis(0)) == 1 {
+            if self.stride_of(Axis(0)) == 1 {
                 Layout::fpref()
-            } else if self.len_of(Axis(n - 1)) > 1 && self.stride_of(Axis(n - 1)) == 1 {
+            } else if self.stride_of(Axis(n - 1)) == 1 {
                 Layout::cpref()
             } else {
                 Layout::none()


Test patch:
diff --git a/tests/array.rs b/tests/array.rs
index 9e45d161..38d2711a 100644
--- a/tests/array.rs
+++ b/tests/array.rs
@@ -2397,3 +2397,79 @@ mod array_cow_tests {
         });
     }
 }
+
+#[test]
+fn test_remove_index() {
+    let mut a = arr2(&[[1, 2, 3],
+                       [4, 5, 6],
+                       [7, 8, 9],
+                       [10,11,12]]);
+    a.remove_index(Axis(0), 1);
+    a.remove_index(Axis(1), 2);
+    assert_eq!(a.shape(), &[3, 2]);
+    assert_eq!(a,
+        array![[1, 2],
+               [7, 8],
+               [10,11]]);
+
+    let mut a = arr2(&[[1, 2, 3],
+                       [4, 5, 6],
+                       [7, 8, 9],
+                       [10,11,12]]);
+    a.invert_axis(Axis(0));
+    a.remove_index(Axis(0), 1);
+    a.remove_index(Axis(1), 2);
+    assert_eq!(a.shape(), &[3, 2]);
+    assert_eq!(a,
+        array![[10,11],
+               [4, 5],
+               [1, 2]]);
+
+    a.remove_index(Axis(1), 1);
+
+    assert_eq!(a.shape(), &[3, 1]);
+    assert_eq!(a,
+        array![[10],
+               [4],
+               [1]]);
+    a.remove_index(Axis(1), 0);
+    assert_eq!(a.shape(), &[3, 0]);
+    assert_eq!(a,
+        array![[],
+               [],
+               []]);
+}
+
+#[should_panic(expected="must be less")]
+#[test]
+fn test_remove_index_oob1() {
+    let mut a = arr2(&[[1, 2, 3],
+                       [4, 5, 6],
+                       [7, 8, 9],
+                       [10,11,12]]);
+    a.remove_index(Axis(0), 4);
+}
+
+#[should_panic(expected="must be less")]
+#[test]
+fn test_remove_index_oob2() {
+    let mut a = array![[10], [4], [1]];
+    a.remove_index(Axis(1), 0);
+    assert_eq!(a.shape(), &[3, 0]);
+    assert_eq!(a,
+        array![[],
+               [],
+               []]);
+    a.remove_index(Axis(0), 1); // ok
+    assert_eq!(a,
+        array![[],
+               []]);
+    a.remove_index(Axis(1), 0); // oob
+}
+
+#[should_panic(expected="index out of bounds")]
+#[test]
+fn test_remove_index_oob3() {
+    let mut a = array![[10], [4], [1]];
+    a.remove_index(Axis(2), 0);
+}