Repository: rust-ndarray/ndarray
Pull request #1107 (2021-11-07 05:19:17)

Implement real/imag splitting of arrays
Picking things up from https://github.com/rust-ndarray/ndarray/pull/1029

It seems the main thing is that tests were needed looking at the discussion. I've added a few to test mutation, reading the views, etc. I don't have a lot of experience testing matrix libraries so let me know if you have other suggestions for tests.

Closes #1029


Patch:
diff --git a/README.rst b/README.rst
index a6ed9e90..50302488 100644
--- a/README.rst
+++ b/README.rst
@@ -15,7 +15,7 @@ __ https://docs.rs/ndarray/
    :alt: CI build status
 .. _build_status: https://github.com/rust-ndarray/ndarray/actions
 
-.. |crates| image:: https://img.shields.io/crates/v/ndarray.svg
+.. |crates| image:: http://meritbadge.herokuapp.com/ndarray
    :alt: ndarray at crates.io
 .. _crates: https://crates.io/crates/ndarray
 
diff --git a/src/data_traits.rs b/src/data_traits.rs
index b117792c..bcf19659 100644
--- a/src/data_traits.rs
+++ b/src/data_traits.rs
@@ -16,9 +16,7 @@ use std::ptr::NonNull;
 use alloc::sync::Arc;
 use alloc::vec::Vec;
 
-use crate::{
-    ArcArray, Array, ArrayBase, CowRepr, Dimension, OwnedArcRepr, OwnedRepr, RawViewRepr, ViewRepr,
-};
+use crate::{ArrayBase, CowRepr, Dimension, OwnedArcRepr, OwnedRepr, RawViewRepr, ViewRepr};
 
 /// Array representation trait.
 ///
@@ -106,25 +104,16 @@ pub unsafe trait Data: RawData {
     /// Converts the array to a uniquely owned array, cloning elements if necessary.
     #[doc(hidden)]
     #[allow(clippy::wrong_self_convention)]
-    fn into_owned<D>(self_: ArrayBase<Self, D>) -> Array<Self::Elem, D>
+    fn into_owned<D>(self_: ArrayBase<Self, D>) -> ArrayBase<OwnedRepr<Self::Elem>, D>
     where
         Self::Elem: Clone,
         D: Dimension;
 
-    /// Converts the array into `Array<A, D>` if this is possible without
-    /// cloning the array elements. Otherwise, returns `self_` unchanged.
-    #[doc(hidden)]
-    fn try_into_owned_nocopy<D>(
-        self_: ArrayBase<Self, D>,
-    ) -> Result<Array<Self::Elem, D>, ArrayBase<Self, D>>
-    where
-        D: Dimension;
-
     /// Return a shared ownership (copy on write) array based on the existing one,
     /// cloning elements if necessary.
     #[doc(hidden)]
     #[allow(clippy::wrong_self_convention)]
-    fn to_shared<D>(self_: &ArrayBase<Self, D>) -> ArcArray<Self::Elem, D>
+    fn to_shared<D>(self_: &ArrayBase<Self, D>) -> ArrayBase<OwnedArcRepr<Self::Elem>, D>
     where
         Self::Elem: Clone,
         D: Dimension,
@@ -273,7 +262,7 @@ where
 }
 
 unsafe impl<A> Data for OwnedArcRepr<A> {
-    fn into_owned<D>(mut self_: ArrayBase<Self, D>) -> Array<Self::Elem, D>
+    fn into_owned<D>(mut self_: ArrayBase<Self, D>) -> ArrayBase<OwnedRepr<Self::Elem>, D>
     where
         A: Clone,
         D: Dimension,
@@ -287,29 +276,8 @@ unsafe impl<A> Data for OwnedArcRepr<A> {
         }
     }
 
-    fn try_into_owned_nocopy<D>(
-        self_: ArrayBase<Self, D>,
-    ) -> Result<Array<Self::Elem, D>, ArrayBase<Self, D>>
-    where
-        D: Dimension,
-    {
-        match Arc::try_unwrap(self_.data.0) {
-            Ok(owned_data) => unsafe {
-                // Safe because the data is equivalent.
-                Ok(ArrayBase::from_data_ptr(owned_data, self_.ptr)
-                    .with_strides_dim(self_.strides, self_.dim))
-            },
-            Err(arc_data) => unsafe {
-                // Safe because the data is equivalent; we're just
-                // reconstructing `self_`.
-                Err(ArrayBase::from_data_ptr(OwnedArcRepr(arc_data), self_.ptr)
-                    .with_strides_dim(self_.strides, self_.dim))
-            },
-        }
-    }
-
     #[allow(clippy::wrong_self_convention)]
-    fn to_shared<D>(self_: &ArrayBase<Self, D>) -> ArcArray<Self::Elem, D>
+    fn to_shared<D>(self_: &ArrayBase<Self, D>) -> ArrayBase<OwnedArcRepr<Self::Elem>, D>
     where
         Self::Elem: Clone,
         D: Dimension,
@@ -362,23 +330,13 @@ unsafe impl<A> RawDataMut for OwnedRepr<A> {
 
 unsafe impl<A> Data for OwnedRepr<A> {
     #[inline]
-    fn into_owned<D>(self_: ArrayBase<Self, D>) -> Array<Self::Elem, D>
+    fn into_owned<D>(self_: ArrayBase<Self, D>) -> ArrayBase<OwnedRepr<Self::Elem>, D>
     where
         A: Clone,
         D: Dimension,
     {
         self_
     }
-
-    #[inline]
-    fn try_into_owned_nocopy<D>(
-        self_: ArrayBase<Self, D>,
-    ) -> Result<Array<Self::Elem, D>, ArrayBase<Self, D>>
-    where
-        D: Dimension,
-    {
-        Ok(self_)
-    }
 }
 
 unsafe impl<A> DataMut for OwnedRepr<A> {}
@@ -428,22 +386,13 @@ unsafe impl<'a, A> RawData for ViewRepr<&'a A> {
 }
 
 unsafe impl<'a, A> Data for ViewRepr<&'a A> {
-    fn into_owned<D>(self_: ArrayBase<Self, D>) -> Array<Self::Elem, D>
+    fn into_owned<D>(self_: ArrayBase<Self, D>) -> ArrayBase<OwnedRepr<Self::Elem>, D>
     where
         Self::Elem: Clone,
         D: Dimension,
     {
         self_.to_owned()
     }
-
-    fn try_into_owned_nocopy<D>(
-        self_: ArrayBase<Self, D>,
-    ) -> Result<Array<Self::Elem, D>, ArrayBase<Self, D>>
-    where
-        D: Dimension,
-    {
-        Err(self_)
-    }
 }
 
 unsafe impl<'a, A> RawDataClone for ViewRepr<&'a A> {
@@ -482,22 +431,13 @@ unsafe impl<'a, A> RawDataMut for ViewRepr<&'a mut A> {
 }
 
 unsafe impl<'a, A> Data for ViewRepr<&'a mut A> {
-    fn into_owned<D>(self_: ArrayBase<Self, D>) -> Array<Self::Elem, D>
+    fn into_owned<D>(self_: ArrayBase<Self, D>) -> ArrayBase<OwnedRepr<Self::Elem>, D>
     where
         Self::Elem: Clone,
         D: Dimension,
     {
         self_.to_owned()
     }
-
-    fn try_into_owned_nocopy<D>(
-        self_: ArrayBase<Self, D>,
-    ) -> Result<Array<Self::Elem, D>, ArrayBase<Self, D>>
-    where
-        D: Dimension,
-    {
-        Err(self_)
-    }
 }
 
 unsafe impl<'a, A> DataMut for ViewRepr<&'a mut A> {}
@@ -655,7 +595,7 @@ where
 
 unsafe impl<'a, A> Data for CowRepr<'a, A> {
     #[inline]
-    fn into_owned<D>(self_: ArrayBase<CowRepr<'a, A>, D>) -> Array<Self::Elem, D>
+    fn into_owned<D>(self_: ArrayBase<CowRepr<'a, A>, D>) -> ArrayBase<OwnedRepr<Self::Elem>, D>
     where
         A: Clone,
         D: Dimension,
@@ -669,22 +609,6 @@ unsafe impl<'a, A> Data for CowRepr<'a, A> {
             },
         }
     }
-
-    fn try_into_owned_nocopy<D>(
-        self_: ArrayBase<Self, D>,
-    ) -> Result<Array<Self::Elem, D>, ArrayBase<Self, D>>
-    where
-        D: Dimension,
-    {
-        match self_.data {
-            CowRepr::View(_) => Err(self_),
-            CowRepr::Owned(data) => unsafe {
-                // safe because the data is equivalent so ptr, dims remain valid
-                Ok(ArrayBase::from_data_ptr(data, self_.ptr)
-                    .with_strides_dim(self_.strides, self_.dim))
-            },
-        }
-    }
 }
 
 unsafe impl<'a, A> DataMut for CowRepr<'a, A> where A: Clone {}
diff --git a/src/impl_cow.rs b/src/impl_cow.rs
index 22d5c78b..5cd00077 100644
--- a/src/impl_cow.rs
+++ b/src/impl_cow.rs
@@ -51,34 +51,3 @@ where
         }
     }
 }
-
-impl<'a, A, Slice: ?Sized> From<&'a Slice> for CowArray<'a, A, Ix1>
-where
-    Slice: AsRef<[A]>,
-{
-    /// Create a one-dimensional clone-on-write view of the data in `slice`.
-    ///
-    /// **Panics** if the slice length is greater than [`isize::MAX`].
-    ///
-    /// ```
-    /// use ndarray::{array, CowArray};
-    ///
-    /// let array = CowArray::from(&[1., 2., 3., 4.]);
-    /// assert!(array.is_view());
-    /// assert_eq!(array, array![1., 2., 3., 4.]);
-    /// ```
-    fn from(slice: &'a Slice) -> Self {
-        Self::from(ArrayView1::from(slice))
-    }
-}
-
-impl<'a, A, S, D> From<&'a ArrayBase<S, D>> for CowArray<'a, A, D>
-where
-    S: Data<Elem = A>,
-    D: Dimension,
-{
-    /// Create a read-only clone-on-write view of the array.
-    fn from(array: &'a ArrayBase<S, D>) -> Self {
-        Self::from(array.view())
-    }
-}
diff --git a/src/impl_methods.rs b/src/impl_methods.rs
index 745a8e60..27abe689 100644
--- a/src/impl_methods.rs
+++ b/src/impl_methods.rs
@@ -241,34 +241,6 @@ where
         S::into_owned(self)
     }
 
-    /// Converts the array into `Array<A, D>` if this is possible without
-    /// cloning the array elements. Otherwise, returns `self` unchanged.
-    ///
-    /// ```
-    /// use ndarray::{array, rcarr2, ArcArray2, Array2};
-    ///
-    /// // Reference-counted, clone-on-write `ArcArray`.
-    /// let a: ArcArray2<_> = rcarr2(&[[1., 2.], [3., 4.]]);
-    /// {
-    ///     // Another reference to the same data.
-    ///     let b: ArcArray2<_> = a.clone();
-    ///     // Since there are two references to the same data, `.into_owned()`
-    ///     // would require cloning the data, so `.try_into_owned_nocopy()`
-    ///     // returns `Err`.
-    ///     assert!(b.try_into_owned_nocopy().is_err());
-    /// }
-    /// // Here, since the second reference has been dropped, the `ArcArray`
-    /// // can be converted into an `Array` without cloning the data.
-    /// let unique: Array2<_> = a.try_into_owned_nocopy().unwrap();
-    /// assert_eq!(unique, array![[1., 2.], [3., 4.]]);
-    /// ```
-    pub fn try_into_owned_nocopy(self) -> Result<Array<A, D>, Self>
-    where
-        S: Data,
-    {
-        S::try_into_owned_nocopy(self)
-    }
-
     /// Turn the array into a shared ownership (copy on write) array,
     /// without any copying.
     pub fn into_shared(self) -> ArcArray<A, D>
@@ -1427,50 +1399,6 @@ where
         Windows::new(self.view(), window_size)
     }
 
-    /// Returns a producer which traverses over all windows of a given length along an axis.
-    ///
-    /// The windows are all distinct, possibly-overlapping views. The shape of each window
-    /// is the shape of `self`, with the length of `axis` replaced with `window_size`.
-    ///
-    /// **Panics** if `axis` is out-of-bounds or if `window_size` is zero.
-    ///
-    /// ```
-    /// use ndarray::{Array3, Axis, s};
-    ///
-    /// let arr = Array3::from_shape_fn([4, 5, 2], |(i, j, k)| i * 100 + j * 10 + k);
-    /// let correct = vec![
-    ///     arr.slice(s![.., 0..3, ..]),
-    ///     arr.slice(s![.., 1..4, ..]),
-    ///     arr.slice(s![.., 2..5, ..]),
-    /// ];
-    /// for (window, correct) in arr.axis_windows(Axis(1), 3).into_iter().zip(&correct) {
-    ///     assert_eq!(window, correct);
-    ///     assert_eq!(window.shape(), &[4, 3, 2]);
-    /// }
-    /// ```
-    pub fn axis_windows(&self, axis: Axis, window_size: usize) -> Windows<'_, A, D>
-    where
-        S: Data,
-    {
-        let axis_index = axis.index();
-
-        ndassert!(
-            axis_index < self.ndim(),
-            concat!(
-                "Window axis {} does not match array dimension {} ",
-                "(with array of shape {:?})"
-            ),
-            axis_index,
-            self.ndim(),
-            self.shape()
-        );
-
-        let mut size = self.raw_dim();
-        size[axis_index] = window_size;
-
-        Windows::new(self.view(), size)
-    }
-
     // Return (length, stride) for diagonal
     fn diag_params(&self) -> (Ix, Ixs) {
         /* empty shape has len 1 */
diff --git a/src/impl_raw_views.rs b/src/impl_raw_views.rs
index 90cfa637..425af96c 100644
--- a/src/impl_raw_views.rs
+++ b/src/impl_raw_views.rs
@@ -1,3 +1,4 @@
+use num_complex::Complex;
 use std::mem;
 use std::ptr::NonNull;
 
@@ -149,6 +150,73 @@ where
     }
 }
 
+impl<T, D> RawArrayView<Complex<T>, D>
+where
+    D: Dimension,
+{
+    /// Splits the view into views of the real and imaginary components of the
+    /// elements.
+    pub fn split_re_im(self) -> Complex<RawArrayView<T, D>> {
+        // Check that the size and alignment of `Complex<T>` are as expected.
+        // These assertions should always pass, for arbitrary `T`.
+        assert_eq!(
+            mem::size_of::<Complex<T>>(),
+            mem::size_of::<T>().checked_mul(2).unwrap()
+        );
+        assert_eq!(mem::align_of::<Complex<T>>(), mem::align_of::<T>());
+
+        let dim = self.dim.clone();
+
+        // Double the strides. In the zero-sized element case and for axes of
+        // length <= 1, we leave the strides as-is to avoid possible overflow.
+        let mut strides = self.strides.clone();
+        if mem::size_of::<T>() != 0 {
+            for ax in 0..strides.ndim() {
+                if dim[ax] > 1 {
+                    strides[ax] = (strides[ax] as isize * 2) as usize;
+                }
+            }
+        }
+
+        let ptr_re: *mut T = self.ptr.as_ptr().cast();
+        let ptr_im: *mut T = if self.is_empty() {
+            // In the empty case, we can just reuse the existing pointer since
+            // it won't be dereferenced anyway. It is not safe to offset by
+            // one, since the allocation may be empty.
+            ptr_re
+        } else {
+            // In the nonempty case, we can safely offset into the first
+            // (complex) element.
+            unsafe { ptr_re.add(1) }
+        };
+
+        // `Complex` is `repr(C)` with only fields `re: T` and `im: T`. So, the
+        // real components of the elements start at the same pointer, and the
+        // imaginary components start at the pointer offset by one, with
+        // exactly double the strides. The new, doubled strides still meet the
+        // overflow constraints:
+        //
+        // - For the zero-sized element case, the strides are unchanged in
+        //   units of bytes and in units of the element type.
+        //
+        // - For the nonzero-sized element case:
+        //
+        //   - In units of bytes, the strides are unchanged. The only exception
+        //     is axes of length <= 1, but those strides are irrelevant anyway.
+        //
+        //   - Since `Complex<T>` for nonzero `T` is always at least 2 bytes,
+        //     and the original strides did not overflow in units of bytes, we
+        //     know that the new, doubled strides will not overflow in units of
+        //     `T`.
+        unsafe {
+            Complex {
+                re: RawArrayView::new_(ptr_re, dim.clone(), strides.clone()),
+                im: RawArrayView::new_(ptr_im, dim, strides),
+            }
+        }
+    }
+}
+
 impl<A, D> RawArrayViewMut<A, D>
 where
     D: Dimension,
@@ -300,3 +368,20 @@ where
         unsafe { RawArrayViewMut::new(ptr, self.dim, self.strides) }
     }
 }
+
+impl<T, D> RawArrayViewMut<Complex<T>, D>
+where
+    D: Dimension,
+{
+    /// Splits the view into views of the real and imaginary components of the
+    /// elements.
+    pub fn split_re_im(self) -> Complex<RawArrayViewMut<T, D>> {
+        let Complex { re, im } = self.into_raw_view().split_re_im();
+        unsafe {
+            Complex {
+                re: RawArrayViewMut::new(re.ptr, re.dim, re.strides),
+                im: RawArrayViewMut::new(im.ptr, im.dim, im.strides),
+            }
+        }
+    }
+}
diff --git a/src/impl_views/splitting.rs b/src/impl_views/splitting.rs
index f2c0dc41..84d24038 100644
--- a/src/impl_views/splitting.rs
+++ b/src/impl_views/splitting.rs
@@ -8,6 +8,7 @@
 
 use crate::imp_prelude::*;
 use crate::slice::MultiSliceArg;
+use num_complex::Complex;
 
 /// Methods for read-only array views.
 impl<'a, A, D> ArrayView<'a, A, D>
@@ -95,6 +96,37 @@ where
     }
 }
 
+impl<'a, T, D> ArrayView<'a, Complex<T>, D>
+where
+    D: Dimension,
+{
+    /// Splits the view into views of the real and imaginary components of the
+    /// elements.
+    ///
+    /// ```
+    /// use ndarray::prelude::*;
+    /// use num_complex::{Complex, Complex64};
+    ///
+    /// let arr = array![
+    ///     [Complex64::new(1., 2.), Complex64::new(3., 4.)],
+    ///     [Complex64::new(5., 6.), Complex64::new(7., 8.)],
+    ///     [Complex64::new(9., 10.), Complex64::new(11., 12.)],
+    /// ];
+    /// let Complex { re, im } = arr.view().split_re_im();
+    /// assert_eq!(re, array![[1., 3.], [5., 7.], [9., 11.]]);
+    /// assert_eq!(im, array![[2., 4.], [6., 8.], [10., 12.]]);
+    /// ```
+    pub fn split_re_im(self) -> Complex<ArrayView<'a, T, D>> {
+        unsafe {
+            let Complex { re, im } = self.into_raw_view().split_re_im();
+            Complex {
+                re: re.deref_into_view(),
+                im: im.deref_into_view(),
+            }
+        }
+    }
+}
+
 /// Methods for read-write array views.
 impl<'a, A, D> ArrayViewMut<'a, A, D>
 where
@@ -135,3 +167,41 @@ where
         info.multi_slice_move(self)
     }
 }
+
+impl<'a, T, D> ArrayViewMut<'a, Complex<T>, D>
+where
+    D: Dimension,
+{
+    /// Splits the view into views of the real and imaginary components of the
+    /// elements.
+    ///
+    /// ```
+    /// use ndarray::prelude::*;
+    /// use num_complex::{Complex, Complex64};
+    ///
+    /// let mut arr = array![
+    ///     [Complex64::new(1., 2.), Complex64::new(3., 4.)],
+    ///     [Complex64::new(5., 6.), Complex64::new(7., 8.)],
+    ///     [Complex64::new(9., 10.), Complex64::new(11., 12.)],
+    /// ];
+    ///
+    /// let Complex { mut re, mut im } = arr.view_mut().split_re_im();
+    /// assert_eq!(re, array![[1., 3.], [5., 7.], [9., 11.]]);
+    /// assert_eq!(im, array![[2., 4.], [6., 8.], [10., 12.]]);
+    ///
+    /// re[[0, 1]] = 13.;
+    /// im[[2, 0]] = 14.;
+    ///
+    /// assert_eq!(arr[[0, 1]], Complex64::new(13., 4.));
+    /// assert_eq!(arr[[2, 0]], Complex64::new(9., 14.));
+    /// ```
+    pub fn split_re_im(self) -> Complex<ArrayViewMut<'a, T, D>> {
+        unsafe {
+            let Complex { re, im } = self.into_raw_view_mut().split_re_im();
+            Complex {
+                re: re.deref_into_view_mut(),
+                im: im.deref_into_view_mut(),
+            }
+        }
+    }
+}
diff --git a/src/parallel/impl_par_methods.rs b/src/parallel/impl_par_methods.rs
index 381b86b5..33d39334 100644
--- a/src/parallel/impl_par_methods.rs
+++ b/src/parallel/impl_par_methods.rs
@@ -177,56 +177,6 @@ macro_rules! zip_impl {
                 self.par_map_assign_into(into, f)
             }
 
-            /// Parallel version of `fold`.
-            ///
-            /// Splits the producer in multiple tasks which each accumulate a single value
-            /// using the `fold` closure. Those tasks are executed in parallel and their results
-            /// are then combined to a single value using the `reduce` closure.
-            ///
-            /// The `identity` closure provides the initial values for each of the tasks and
-            /// for the final reduction.
-            ///
-            /// This is a shorthand for calling `self.into_par_iter().fold(...).reduce(...)`.
-            ///
-            /// Note that it is often more efficient to parallelize not per-element but rather
-            /// based on larger chunks of an array like generalized rows and operating on each chunk
-            /// using a sequential variant of the accumulation.
-            /// For example, sum each row sequentially and in parallel, taking advatange of locality
-            /// and vectorization within each task, and then reduce their sums to the sum of the matrix.
-            ///
-            /// Also note that the splitting of the producer into multiple tasks is _not_ deterministic
-            /// which needs to be considered when the accuracy of such an operation is analyzed.
-            ///
-            /// ## Examples
-            ///
-            /// ```rust
-            /// use ndarray::{Array, Zip};
-            ///
-            /// let a = Array::<usize, _>::ones((128, 1024));
-            /// let b = Array::<usize, _>::ones(128);
-            ///
-            /// let weighted_sum = Zip::from(a.rows()).and(&b).par_fold(
-            ///     || 0,
-            ///     |sum, row, factor| sum + row.sum() * factor,
-            ///     |sum, other_sum| sum + other_sum,
-            /// );
-            ///
-            /// assert_eq!(weighted_sum, a.len());
-            /// ```
-            pub fn par_fold<ID, F, R, T>(self, identity: ID, fold: F, reduce: R) -> T
-            where
-                ID: Fn() -> T + Send + Sync + Clone,
-                F: Fn(T, $($p::Item),*) -> T + Send + Sync,
-                R: Fn(T, T) -> T + Send + Sync,
-                T: Send
-            {
-                self.into_par_iter()
-                    .fold(identity.clone(), move |accumulator, ($($p,)*)| {
-                        fold(accumulator, $($p),*)
-                    })
-                    .reduce(identity, reduce)
-            }
-
             );
         }
         )+


Test patch:
diff --git a/tests/array.rs b/tests/array.rs
index d0fc67de..16d90156 100644
--- a/tests/array.rs
+++ b/tests/array.rs
@@ -7,12 +7,14 @@
     clippy::float_cmp
 )]
 
+use approx::assert_relative_eq;
 use defmac::defmac;
 use itertools::{zip, Itertools};
 use ndarray::prelude::*;
 use ndarray::{arr3, rcarr2};
 use ndarray::indices;
 use ndarray::{Slice, SliceInfo, SliceInfoElem};
+use num_complex::Complex;
 use std::convert::TryFrom;
 
 macro_rules! assert_panics {
@@ -2501,3 +2503,71 @@ fn test_remove_index_oob3() {
     let mut a = array![[10], [4], [1]];
     a.remove_index(Axis(2), 0);
 }
+
+#[test]
+fn test_split_re_im_view() {
+    let a = Array3::from_shape_fn((3, 4, 5), |(i, j, k)| {
+        Complex::<f32>::new(i as f32 * j as f32, k as f32)
+    });
+    let Complex { re, im } = a.view().split_re_im();
+    assert_relative_eq!(re.sum(), 90.);
+    assert_relative_eq!(im.sum(), 120.);
+}
+
+#[test]
+fn test_split_re_im_view_roundtrip() {
+    let a_re = Array3::from_shape_fn((3,1,5), |(i, j, _k)| {
+        i * j
+    });
+    let a_im = Array3::from_shape_fn((3,1,5), |(_i, _j, k)| {
+        k
+    });
+    let a = Array3::from_shape_fn((3,1,5), |(i,j,k)| {
+        Complex::new(a_re[[i,j,k]], a_im[[i,j,k]])
+    });
+    let Complex { re, im } = a.view().split_re_im();
+    assert_eq!(a_re, re);
+    assert_eq!(a_im, im);
+}
+
+#[test]
+fn test_split_re_im_view_mut() {
+    let eye_scalar = Array2::<u32>::eye(4);
+    let eye_complex = Array2::<Complex<u32>>::eye(4);
+    let mut a = Array2::<Complex<u32>>::zeros((4, 4));
+    let Complex { mut re, im } = a.view_mut().split_re_im();
+    re.assign(&eye_scalar);
+    assert_eq!(im.sum(), 0);
+    assert_eq!(a, eye_complex);
+}
+
+#[test]
+fn test_split_re_im_zerod() {
+    let mut a = Array0::from_elem((), Complex::new(42, 32));
+    let Complex { re, im } = a.view().split_re_im();
+    assert_eq!(re.get(()), Some(&42));
+    assert_eq!(im.get(()), Some(&32));
+    let cmplx = a.view_mut().split_re_im();
+    cmplx.re.assign_to(cmplx.im);
+    assert_eq!(a.get(()).unwrap().im, 42);
+}
+
+#[test]
+fn test_split_re_im_permuted() {
+    let a = Array3::from_shape_fn((3, 4, 5), |(i, j, k)| {
+        Complex::new(i * k + j, k)
+    });
+    let permuted = a.view().permuted_axes([1,0,2]);
+    let Complex { re, im } = permuted.split_re_im();
+    assert_eq!(re.get((3,2,4)).unwrap(), &11);
+    assert_eq!(im.get((3,2,4)).unwrap(), &4);
+}
+
+#[test]
+fn test_split_re_im_invert_axis() {
+    let mut a = Array::from_shape_fn((2, 3, 2), |(i, j, k)| Complex::new(i as f64 + j as f64, i as f64 + k as f64));
+    a.invert_axis(Axis(1));
+    let cmplx = a.view().split_re_im();
+    assert_eq!(cmplx.re, a.mapv(|z| z.re));
+    assert_eq!(cmplx.im, a.mapv(|z| z.im));
+}
diff --git a/tests/windows.rs b/tests/windows.rs
index 664616f6..b0482e4b 100644
--- a/tests/windows.rs
+++ b/tests/windows.rs
@@ -6,7 +6,7 @@
 )]
 
 use ndarray::prelude::*;
-use ndarray::{arr3, Zip};
+use ndarray::Zip;
 
 // Edge Cases for Windows iterator:
 //
@@ -79,6 +79,7 @@ fn windows_iterator_2d() {
 /// Simple test for iterating 3d-arrays via `Windows`.
 #[test]
 fn windows_iterator_3d() {
+    use ndarray::arr3;
     let a = Array::from_iter(10..37).into_shape((3, 3, 3)).unwrap();
     itertools::assert_equal(
         a.windows(Dim((2, 2, 2))),
@@ -116,87 +117,6 @@ fn test_window_zip() {
     }
 }
 
-/// Test verifies that non existant Axis results in panic
-#[test]
-#[should_panic]
-fn axis_windows_outofbound() {
-    let a = Array::from_iter(10..37).into_shape((3, 3, 3)).unwrap();
-    a.axis_windows(Axis(4), 2);
-}
-
-/// Test verifies that zero sizes results in panic
-#[test]
-#[should_panic]
-fn axis_windows_zero_size() {
-    let a = Array::from_iter(10..37).into_shape((3, 3, 3)).unwrap();
-    a.axis_windows(Axis(0), 0);
-}
-
-/// Test verifies that over sized windows yield nothing
-#[test]
-fn axis_windows_oversized() {
-    let a = Array::from_iter(10..37).into_shape((3, 3, 3)).unwrap();
-    let mut iter = a.axis_windows(Axis(2), 4).into_iter();
-    assert_eq!(iter.next(), None);
-}
-
-/// Simple test for iterating 1d-arrays via `Axis Windows`.
-#[test]
-fn test_axis_windows_1d() {
-    let a = Array::from_iter(10..20).into_shape(10).unwrap();
-
-    itertools::assert_equal(
-        a.axis_windows(Axis(0), 5),
-        vec![
-            arr1(&[10, 11, 12, 13, 14]),
-            arr1(&[11, 12, 13, 14, 15]),
-            arr1(&[12, 13, 14, 15, 16]),
-            arr1(&[13, 14, 15, 16, 17]),
-            arr1(&[14, 15, 16, 17, 18]),
-            arr1(&[15, 16, 17, 18, 19]),
-        ],
-    );
-}
-
-/// Simple test for iterating 2d-arrays via `Axis Windows`.
-#[test]
-fn test_axis_windows_2d() {
-    let a = Array::from_iter(10..30).into_shape((5, 4)).unwrap();
-
-    itertools::assert_equal(
-        a.axis_windows(Axis(0), 2),
-        vec![
-            arr2(&[[10, 11, 12, 13], [14, 15, 16, 17]]),
-            arr2(&[[14, 15, 16, 17], [18, 19, 20, 21]]),
-            arr2(&[[18, 19, 20, 21], [22, 23, 24, 25]]),
-            arr2(&[[22, 23, 24, 25], [26, 27, 28, 29]]),
-        ],
-    );
-}
-
-/// Simple test for iterating 3d-arrays via `Axis Windows`.
-#[test]
-fn test_axis_windows_3d() {
-    let a = Array::from_iter(0..27).into_shape((3, 3, 3)).unwrap();
-
-    itertools::assert_equal(
-        a.axis_windows(Axis(1), 2),
-        vec![
-            arr3(&[
-                [[0, 1, 2], [3, 4, 5]],
-                [[9, 10, 11], [12, 13, 14]],
-                [[18, 19, 20], [21, 22, 23]],
-            ]),
-            arr3(&[
-                [[3, 4, 5], [6, 7, 8]],
-                [[12, 13, 14], [15, 16, 17]],
-                [[21, 22, 23], [24, 25, 26]],
-            ]),
-        ],
-    );
-}
-
-
 #[test]
 fn test_window_neg_stride() {
     let array = Array::from_iter(1..10).into_shape((3, 3)).unwrap();