Repository: bluss/rust-ndarray
Pull request #253 (2016-12-22 12:55:24)

Add methods for reversing axes and merging them
- Add `.invert_axis()` to reverse the stride of an axis
- Add `merge_axes()` to merge two axes
- Add `remove_axis()` to remove an axis


Patch:
diff --git a/.travis.yml b/.travis.yml
index b342c483..98f4b2fa 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,9 @@ sudo: required
 dist: trusty
 matrix:
   include:
+    - rust: 1.13.0
+      env:
+       - FEATURES='test'
     - rust: stable
       env:
        - FEATURES='test'
diff --git a/examples/axis.rs b/examples/axis.rs
deleted file mode 100644
index beeeda2e..00000000
--- a/examples/axis.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-extern crate ndarray;
-
-use ndarray::{
-    Array,
-    Axis,
-};
-
-fn main() {
-    let a = Array::<f32, _>::linspace(0., 24., 25).into_shape((5, 5)).unwrap();
-    println!("{:?}", a.subview(Axis(0), 0));
-    println!("{:?}", a.subview(Axis(0), 1));
-    println!("{:?}", a.subview(Axis(1), 1));
-    println!("{:?}", a.subview(Axis(0), 1));
-    println!("{:?}", a.subview(Axis(2), 1)); // PANIC
-}
diff --git a/examples/axis_ops.rs b/examples/axis_ops.rs
new file mode 100644
index 00000000..a5632e7a
--- /dev/null
+++ b/examples/axis_ops.rs
@@ -0,0 +1,69 @@
+
+#[macro_use(s)]
+extern crate ndarray;
+
+use ndarray::Array;
+use ndarray::Dimension;
+use ndarray::Axis;
+
+fn regularize<A, D>(a: &mut Array<A, D>) -> Result<(), ()>
+    where D: Dimension,
+          A: ::std::fmt::Debug,
+{
+    println!("Regularize:\n{:?}", a);
+    // reverse all neg axes
+    while let Some(ax) = a.axes().find(|ax| ax.stride() <= 0) {
+        if ax.stride() == 0 { return Err(()); }
+        // reverse ax
+        println!("Reverse {:?}", ax.axis());
+        a.invert_axis(ax.axis());
+    }
+
+    // sort by least stride
+    let mut i = 0;
+    let n = a.ndim();
+    while let Some(ax) = a.axes().rev().skip(i).min_by_key(|ax| ax.stride().abs()) {
+        a.swap_axes(n - 1 - i, ax.axis().index());
+        println!("Swap {:?} <=> {}", ax.axis(), n - 1 - i);
+        i += 1;
+    }
+
+    // merge the lower axes if possible
+    for j in (0..n).rev().skip(1) {
+        if a.merge_axes(Axis(j), Axis(n - 1)) {
+            println!("Merged {:?} into {:?}", Axis(j), Axis(n - 1));
+        } else {
+            break;
+        }
+    }
+    println!("{:?}", a);
+    Ok(())
+}
+
+fn main() {
+    let mut a = Array::<u8, _>::zeros((2, 3, 4));
+    for (i, elt) in (0..).zip(&mut a) {
+        *elt = i;
+    }
+    a.swap_axes(0, 1);
+    a.swap_axes(0, 2);
+    a.islice(s![.., ..;-1, ..]);
+    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).ok();
+    let mut b = b.into_shape(a.len()).unwrap();
+    regularize(&mut b).ok();
+    b.invert_axis(Axis(0));
+    regularize(&mut b).ok();
+
+    let mut a = Array::<u8, _>::zeros((2, 3, 4));
+    for (i, elt) in (0..).zip(&mut a) {
+        *elt = i;
+    }
+    a.islice(s![..;-1, ..;2, ..]);
+    regularize(&mut a).ok();
+}
diff --git a/src/dimension/axis.rs b/src/dimension/axis.rs
index 35ce02ae..e222912f 100644
--- a/src/dimension/axis.rs
+++ b/src/dimension/axis.rs
@@ -19,6 +19,10 @@ use std::cmp::Ordering;
 pub struct Axis(pub usize);
 
 impl Axis {
+    /// Return the index of the axis.
+    #[inline(always)]
+    pub fn index(&self) -> usize { self.0 }
+    #[deprecated(note = "Renamed to .index()")]
     #[inline(always)]
     pub fn axis(&self) -> usize { self.0 }
 }
diff --git a/src/dimension/mod.rs b/src/dimension/mod.rs
index 51eb4559..83eb03d3 100644
--- a/src/dimension/mod.rs
+++ b/src/dimension/mod.rs
@@ -176,12 +176,12 @@ impl<D> DimensionExt for D
 {
     #[inline]
     fn axis(&self, axis: Axis) -> Ix {
-        self[axis.axis()]
+        self[axis.index()]
     }
 
     #[inline]
     fn set_axis(&mut self, axis: Axis, value: Ix) {
-        self[axis.axis()] = value;
+        self[axis.index()] = value;
     }
 }
 
@@ -189,12 +189,12 @@ impl<'a> DimensionExt for [Ix]
 {
     #[inline]
     fn axis(&self, axis: Axis) -> Ix {
-        self[axis.axis()]
+        self[axis.index()]
     }
 
     #[inline]
     fn set_axis(&mut self, axis: Axis, value: Ix) {
-        self[axis.axis()] = value;
+        self[axis.index()] = value;
     }
 }
 
@@ -216,6 +216,21 @@ pub fn do_sub<A, D: Dimension>(dims: &mut D, ptr: &mut *mut A, strides: &D,
 }
 
 
+pub fn merge_axes<D>(dim: &mut D, strides: &mut D, take: Axis, into: Axis) -> bool
+    where D: Dimension,
+{
+    let il = dim.axis(into);
+    let is = strides.axis(into) as Ixs;
+    let tl = dim.axis(take);
+    let ts = strides.axis(take) as Ixs;
+    if il as Ixs * is != ts {
+        return false;
+    }
+    // merge them
+    dim.set_axis(into, il * tl);
+    dim.set_axis(take, 1);
+    true
+}
 
 
 // NOTE: These tests are not compiled & tested
diff --git a/src/dimension/remove_axis.rs b/src/dimension/remove_axis.rs
index 9b26056c..824c5b21 100644
--- a/src/dimension/remove_axis.rs
+++ b/src/dimension/remove_axis.rs
@@ -29,7 +29,7 @@ impl RemoveAxis for Dim<[Ix; 2]> {
     type Smaller = Ix1;
     #[inline]
     fn remove_axis(&self, axis: Axis) -> Ix1 {
-        let axis = axis.axis();
+        let axis = axis.index();
         debug_assert!(axis < self.ndim());
         if axis == 0 { Ix1(get!(self, 1)) } else { Ix1(get!(self, 0)) }
     }
@@ -47,7 +47,7 @@ macro_rules! impl_remove_axis_array(
                 {
                     let mut it = tup.slice_mut().iter_mut();
                     for (i, &d) in self.slice().iter().enumerate() {
-                        if i == axis.axis() {
+                        if i == axis.index() {
                             continue;
                         }
                         for rr in it.by_ref() {
@@ -70,7 +70,7 @@ impl RemoveAxis for Dim<Vec<Ix>> {
     type Smaller = Self;
     fn remove_axis(&self, axis: Axis) -> Self {
         let mut res = self.clone();
-        res.ixm().remove(axis.axis());
+        res.ixm().remove(axis.index());
         res
     }
 }
diff --git a/src/impl_methods.rs b/src/impl_methods.rs
index 5d967904..732e046b 100644
--- a/src/impl_methods.rs
+++ b/src/impl_methods.rs
@@ -21,7 +21,7 @@ use error::{self, ShapeError};
 use super::zipsl;
 use super::ZipExt;
 use dimension::IntoDimension;
-use dimension::{axes_of, Axes};
+use dimension::{axes_of, Axes, merge_axes, stride_offset};
 
 use {
     NdIndex,
@@ -53,7 +53,7 @@ impl<A, S, D> ArrayBase<S, D> where S: Data<Elem=A>, D: Dimension
     ///
     /// ***Panics*** if the axis is out of bounds.
     pub fn len_of(&self, axis: Axis) -> usize {
-        self.dim[axis.axis()]
+        self.dim[axis.index()]
     }
 
     /// Return the number of dimensions (axes) in the array
@@ -415,26 +415,18 @@ impl<A, S, D> ArrayBase<S, D> where S: Data<Elem=A>, D: Dimension
     /// **Panics** if `index` is past the length of the axis.
     pub fn isubview(&mut self, axis: Axis, index: Ix) {
         dimension::do_sub(&mut self.dim, &mut self.ptr, &self.strides,
-                          axis.axis(), index)
+                          axis.index(), index)
     }
 
     /// Along `axis`, select the subview `index` and return `self`
     /// with that axis removed.
     ///
     /// See [`.subview()`](#method.subview) and [*Subviews*](#subviews) for full documentation.
-    pub fn into_subview(mut self, axis: Axis, index: Ix)
-        -> ArrayBase<S, <D as RemoveAxis>::Smaller>
+    pub fn into_subview(mut self, axis: Axis, index: Ix) -> ArrayBase<S, D::Smaller>
         where D: RemoveAxis,
     {
         self.isubview(axis, index);
-        // don't use reshape -- we always know it will fit the size,
-        // and we can use remove_axis on the strides as well
-        ArrayBase {
-            data: self.data,
-            ptr: self.ptr,
-            dim: self.dim.remove_axis(axis),
-            strides: self.strides.remove_axis(axis),
-        }
+        self.remove_axis(axis)
     }
 
     /// Along `axis`, select arbitrary subviews corresponding to `indices`
@@ -554,7 +546,7 @@ impl<A, S, D> ArrayBase<S, D> where S: Data<Elem=A>, D: Dimension
     pub fn axis_iter(&self, axis: Axis) -> AxisIter<A, D::Smaller>
         where D: RemoveAxis,
     {
-        iterators::new_axis_iter(self.view(), axis.axis())
+        iterators::new_axis_iter(self.view(), axis.index())
     }
 
 
@@ -569,7 +561,7 @@ impl<A, S, D> ArrayBase<S, D> where S: Data<Elem=A>, D: Dimension
         where S: DataMut,
               D: RemoveAxis,
     {
-        iterators::new_axis_iter_mut(self.view_mut(), axis.axis())
+        iterators::new_axis_iter_mut(self.view_mut(), axis.index())
     }
 
 
@@ -600,7 +592,7 @@ impl<A, S, D> ArrayBase<S, D> where S: Data<Elem=A>, D: Dimension
     ///                                              [[26, 27]]]));
     /// ```
     pub fn axis_chunks_iter(&self, axis: Axis, size: usize) -> AxisChunksIter<A, D> {
-        iterators::new_chunk_iter(self.view(), axis.axis(), size)
+        iterators::new_chunk_iter(self.view(), axis.index(), size)
     }
 
     /// Return an iterator that traverses over `axis` by chunks of `size`,
@@ -613,7 +605,7 @@ impl<A, S, D> ArrayBase<S, D> where S: Data<Elem=A>, D: Dimension
         -> AxisChunksIterMut<A, D>
         where S: DataMut
     {
-        iterators::new_chunk_iter_mut(self.view_mut(), axis.axis(), size)
+        iterators::new_chunk_iter_mut(self.view_mut(), axis.index(), size)
     }
 
     // Return (length, stride) for diagonal
@@ -1009,6 +1001,50 @@ impl<A, S, D> ArrayBase<S, D> where S: Data<Elem=A>, D: Dimension
         self.dim.max_stride_axis(&self.strides)
     }
 
+    /// Reverse the stride of `axis`.
+    ///
+    /// ***Panics*** if the axis is out of bounds.
+    pub fn invert_axis(&mut self, axis: Axis) {
+        unsafe {
+            let s = self.strides.axis(axis) as Ixs;
+            let m = self.dim.axis(axis);
+            if m != 0 {
+                self.ptr = self.ptr.offset(stride_offset(m - 1, s as Ix));
+            }
+            self.strides.set_axis(axis, (-s) as Ix);
+        }
+    }
+
+    /// If possible, merge in the axis `take` to `into`.
+    ///
+    /// ```
+    /// use ndarray::Array3;
+    /// use ndarray::Axis;
+    ///
+    /// let mut a = Array3::<f64>::zeros((2, 3, 4));
+    /// a.merge_axes(Axis(1), Axis(2));
+    /// assert_eq!(a.shape(), &[2, 1, 12]);
+    /// ```
+    ///
+    /// ***Panics*** if an axis is out of bounds.
+    pub fn merge_axes(&mut self, take: Axis, into: Axis) -> bool {
+        merge_axes(&mut self.dim, &mut self.strides, take, into)
+    }
+
+    /// Remove array axis `axis` and return the result.
+    pub fn remove_axis(self, axis: Axis) -> ArrayBase<S, D::Smaller>
+        where D: RemoveAxis,
+    {
+        assert!(self.ndim() != 0);
+        let d = self.dim.remove_axis(axis);
+        let s = self.strides.remove_axis(axis);
+        ArrayBase {
+            ptr: self.ptr,
+            data: self.data,
+            dim: d,
+            strides: s,
+        }
+    }
 
     fn pointer_is_inbounds(&self) -> bool {
         let slc = self.data._data_slice();
@@ -1166,7 +1202,7 @@ impl<A, S, D> ArrayBase<S, D> where S: Data<Elem=A>, D: Dimension
                 let narrow_axis = v.axes()
                                    .filter(|ax| ax.len() > 1)
                                    .min_by_key(|ax| ax.stride().abs())
-                                   .map_or(last, |ax| ax.axis().axis());
+                                   .map_or(last, |ax| ax.axis().index());
                 v.swap_axes(last, narrow_axis);
             }
             v.into_elements_base().fold(init, f)
diff --git a/src/numeric/impl_numeric.rs b/src/numeric/impl_numeric.rs
index 84c1daf0..b5260937 100644
--- a/src/numeric/impl_numeric.rs
+++ b/src/numeric/impl_numeric.rs
@@ -70,10 +70,10 @@ impl<A, S, D> ArrayBase<S, D>
     {
         let n = self.shape().axis(axis);
         let mut res = self.subview(axis, 0).to_owned();
-        let stride = self.strides()[axis.axis()];
+        let stride = self.strides()[axis.index()];
         if self.ndim() == 2 && stride == 1 {
             // contiguous along the axis we are summing
-            let ax = axis.axis();
+            let ax = axis.index();
             for (i, elt) in enumerate(&mut res) {
                 *elt = self.subview(Axis(1 - ax), i).scalar_sum();
             }
diff --git a/src/stacking.rs b/src/stacking.rs
index 0f698bef..3832dcbe 100644
--- a/src/stacking.rs
+++ b/src/stacking.rs
@@ -38,7 +38,7 @@ pub fn stack<'a, A, D>(axis: Axis, arrays: &[ArrayView<'a, A, D>])
         return Err(from_kind(ErrorKind::Unsupported));
     }
     let mut res_dim = arrays[0].raw_dim();
-    if axis.axis() >= res_dim.ndim() {
+    if axis.index() >= res_dim.ndim() {
         return Err(from_kind(ErrorKind::OutOfBounds));
     }
     let common_dim = res_dim.remove_axis(axis);


Test patch:
diff --git a/tests/array.rs b/tests/array.rs
index e5ce5a32..7740f9cf 100644
--- a/tests/array.rs
+++ b/tests/array.rs
@@ -957,6 +957,15 @@ fn to_owned_memory_order() {
     assert_eq!(f.strides(), fo.strides());
 }
 
+#[test]
+fn to_owned_neg_stride() {
+    let mut c = arr2(&[[1, 2, 3],
+                       [4, 5, 6]]);
+    c.islice(s![.., ..;-1]);
+    let co = c.to_owned();
+    assert_eq!(c, co);
+}
+
 #[test]
 fn map_memory_order() {
     let a = arr3(&[[[1, 2, 3],