From 4bdd185fff7458a2062be5d9f783895c74847fd9 Mon Sep 17 00:00:00 2001 From: Jim Turner Date: Mon, 11 Mar 2019 18:52:14 -0400 Subject: [PATCH 01/56] Add examples of binary arithmetic operators (#589) * Add examples of binary arithmetic operators * Add note about element type implementing operator --- src/lib.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index d998be49f..7be810f9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -638,6 +638,25 @@ pub type Ixs = isize; /// - `B @ &A` which consumes `B`, updates it with the result, and returns it /// - `C @= &A` which performs an arithmetic operation in place /// +/// Note that the element type needs to implement the operator trait and the +/// `Clone` trait. +/// +/// ``` +/// use ndarray::{array, ArrayView1}; +/// +/// let owned1 = array![1, 2]; +/// let owned2 = array![3, 4]; +/// let view1 = ArrayView1::from(&[5, 6]); +/// let view2 = ArrayView1::from(&[7, 8]); +/// let mut mutable = array![9, 10]; +/// +/// let sum1 = &view1 + &view2; // Allocates a new array. Note the explicit `&`. +/// // let sum2 = view1 + &view2; // This doesn't work because `view1` is not an owned array. +/// let sum3 = owned1 + view1; // Consumes `owned1`, updates it, and returns it. +/// let sum4 = owned2 + &view2; // Consumes `owned2`, updates it, and returns it. +/// mutable += &view2; // Updates `mutable` in-place. +/// ``` +/// /// ### Binary Operators with Array and Scalar /// /// The trait [`ScalarOperand`](trait.ScalarOperand.html) marks types that can be used in arithmetic From 869d3a9e7c1cd6fbdfca3001f9554aed1be163ca Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Wed, 20 Mar 2019 16:18:32 +1100 Subject: [PATCH 02/56] Improve documentation for sum_axis and mean_axis Instead of using square initial matrices, use 2x3 matrices to make it clearer in the example which Axis is calculated over Signed-off-by: JP-Ellis --- src/numeric/impl_numeric.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/numeric/impl_numeric.rs b/src/numeric/impl_numeric.rs index 0016e21c5..f4fd3acef 100644 --- a/src/numeric/impl_numeric.rs +++ b/src/numeric/impl_numeric.rs @@ -88,13 +88,13 @@ impl ArrayBase /// ``` /// use ndarray::{aview0, aview1, arr2, Axis}; /// - /// let a = arr2(&[[1., 2.], - /// [3., 4.]]); + /// let a = arr2(&[[1., 2., 3.], + /// [4., 5., 6.]]); /// assert!( - /// a.sum_axis(Axis(0)) == aview1(&[4., 6.]) && - /// a.sum_axis(Axis(1)) == aview1(&[3., 7.]) && + /// a.sum_axis(Axis(0)) == aview1(&[5., 7., 9.]) && + /// a.sum_axis(Axis(1)) == aview1(&[6., 15.]) && /// - /// a.sum_axis(Axis(0)).sum_axis(Axis(0)) == aview0(&10.) + /// a.sum_axis(Axis(0)).sum_axis(Axis(0)) == aview0(&21.) /// ); /// ``` /// @@ -128,13 +128,15 @@ impl ArrayBase /// fails for the axis length. /// /// ``` - /// use ndarray::{aview1, arr2, Axis}; + /// use ndarray::{aview0, aview1, arr2, Axis}; /// - /// let a = arr2(&[[1., 2.], - /// [3., 4.]]); + /// let a = arr2(&[[1., 2., 3.], + /// [4., 5., 6.]]); /// assert!( - /// a.mean_axis(Axis(0)) == aview1(&[2.0, 3.0]) && - /// a.mean_axis(Axis(1)) == aview1(&[1.5, 3.5]) + /// a.mean_axis(Axis(0)) == aview1(&[2.5, 3.5, 4.5]) && + /// a.mean_axis(Axis(1)) == aview1(&[2., 5.]) && + /// + /// a.mean_axis(Axis(0)).mean_axis(Axis(0)) == aview0(&3.5) /// ); /// ``` pub fn mean_axis(&self, axis: Axis) -> Array From 20d372a17f768b8344981d17495289bee71f24c4 Mon Sep 17 00:00:00 2001 From: Jim Turner Date: Fri, 22 Mar 2019 09:41:13 -0400 Subject: [PATCH 03/56] Enlarge parentheses in math in std_axis docs (#604) This looks a little nicer. --- src/numeric/impl_numeric.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/numeric/impl_numeric.rs b/src/numeric/impl_numeric.rs index f4fd3acef..f8c1f04a3 100644 --- a/src/numeric/impl_numeric.rs +++ b/src/numeric/impl_numeric.rs @@ -228,9 +228,9 @@ impl ArrayBase /// The standard deviation is defined as: /// /// ```text - /// 1 n - /// stddev = sqrt ( ―――――――― ∑ (xᵢ - x̅)² ) - /// n - ddof i=1 + /// ⎛ 1 n ⎞ + /// stddev = sqrt ⎜ ―――――――― ∑ (xᵢ - x̅)²⎟ + /// ⎝ n - ddof i=1 ⎠ /// ``` /// /// where From fe4365c9035ebf268d9d0c0e0d3e6100426e1527 Mon Sep 17 00:00:00 2001 From: "andrei.papou" Date: Sun, 24 Mar 2019 21:53:33 +0300 Subject: [PATCH 04/56] Implemented a function for smarter debug formatting. --- src/arrayformat.rs | 192 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 190 insertions(+), 2 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index d13add8f7..ccd93454d 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -11,9 +11,197 @@ use super::{ Data, Dimension, NdProducer, + Ix }; use crate::dimension::IntoDimension; +#[derive(Debug)] +enum ArrayDisplayMode { + // Array is small enough to me printed without omitting any values. + Full, + // Omit central values of the nth axis. Since we print that axis horizontally, ellipses + // on each row do something like a split of the array into 2 parts vertically. + VSplit, + // Omit central values of the certain axis. Since we do it only once, ellipses on each row + // do something like a split of the array into 2 parts horizontally. + HSplit(Ix), + // Both `VSplit` and `HSplit` hold. + DoubleSplit(Ix), +} + +const PRINT_ELEMENTS_LIMIT: Ix = 5; + +impl ArrayDisplayMode { + fn from_array(arr: &ArrayBase, limit: usize) -> ArrayDisplayMode + where S: Data, + D: Dimension + { + let last_dim = arr.shape().len() - 1; + + let mut overflow_axis_pair: (Option, Option) = (None, None); + for (axis, axis_size) in arr.shape().iter().enumerate().rev() { + if *axis_size >= 2 * limit + 1 { + match overflow_axis_pair.0 { + Some(_) => { + if let None = overflow_axis_pair.1 { + overflow_axis_pair.1 = Some(axis); + } + }, + None => { + if axis != last_dim { + return ArrayDisplayMode::HSplit(axis); + } + overflow_axis_pair.0 = Some(axis); + } + } + } + } + + match overflow_axis_pair { + (Some(_), Some(h_axis)) => ArrayDisplayMode::DoubleSplit(h_axis), + (Some(_), None) => ArrayDisplayMode::VSplit, + (None, _) => ArrayDisplayMode::Full, + } + } + + fn h_split_offset(&self) -> Option { + match self { + ArrayDisplayMode::DoubleSplit(axis) | ArrayDisplayMode::HSplit(axis) => { + Some(axis + 1usize) + }, + _ => None + } + } +} + +fn format_array_v2(view: &ArrayBase, + f: &mut fmt::Formatter, + mut format: F, + limit: usize) -> fmt::Result + where F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result, + D: Dimension, + S: Data, +{ + let display_mode = ArrayDisplayMode::from_array(view, limit); + + let ndim = view.dim().into_dimension().slice().len(); + let nth_idx_max = view.shape().iter().last().unwrap(); + + // None will be an empty iter. + let mut last_index = match view.dim().into_dimension().first_index() { + None => view.dim().into_dimension().clone(), + Some(ix) => ix, + }; + for _ in 0..ndim { + write!(f, "[")?; + } + let mut first = true; + // Shows if ellipses for vertical split were printed. + let mut printed_ellipses_v = false; + // Shows if ellipses for horizontal split were printed. + let mut printed_ellipses_h = false; + // Shows if the row was printed for the first time after horizontal split. + let mut no_rows_after_skip_yet = false; + + // Simply use the indexed iterator, and take the index wraparounds + // as cues for when to add []'s and how many to add. + for (index, elt) in view.indexed_iter() { + let index = index.into_dimension(); + let take_n = if ndim == 0 { 1 } else { ndim - 1 }; + let mut update_index = false; + + let mut print_row = true; + match display_mode { + ArrayDisplayMode::HSplit(axis) | ArrayDisplayMode::DoubleSplit(axis) => { + let sa_idx_max = view.shape().iter().skip(axis).next().unwrap(); + let sa_idx_val = index.slice().iter().skip(axis).next().unwrap(); + if sa_idx_val >= &limit && sa_idx_val < &(sa_idx_max - &limit) { + print_row = false; + no_rows_after_skip_yet = true; + } + }, + _ => {} + } + + for (i, (a, b)) in index.slice() + .iter() + .take(take_n) + .zip(last_index.slice().iter()) + .enumerate() { + if a != b { + if print_row { + printed_ellipses_v = false; + // New row. + // # of ['s needed + let n = ndim - i - 1; + if !no_rows_after_skip_yet { + for _ in 0..n { + write!(f, "]")?; + } + write!(f, ",")?; + write!(f, "\n")?; + } + no_rows_after_skip_yet = false; + for _ in 0..ndim - n { + write!(f, " ")?; + } + for _ in 0..n { + write!(f, "[")?; + } + } else if !printed_ellipses_h { + let n = ndim - i - 1; + for _ in 0..n { + write!(f, "]")?; + } + write!(f, ",")?; + write!(f, "\n")?; + for _ in 0..display_mode.h_split_offset().unwrap() { + write!(f, " ")?; + } + write!(f, "...,\n")?; + printed_ellipses_h = true; + } + first = true; + update_index = true; + break; + } + } + + if print_row { + let mut print_elt = true; + let nth_idx_val = index.slice().iter().last().unwrap(); + match display_mode { + ArrayDisplayMode::VSplit | ArrayDisplayMode::DoubleSplit(_) => { + if nth_idx_val >= &limit && nth_idx_val < &(nth_idx_max - &limit) { + print_elt = false; + if !printed_ellipses_v { + write!(f, ", ...")?; + printed_ellipses_v = true; + } + } + } + _ => {} + } + + if print_elt { + if !first { + write!(f, ", ")?; + } + first = false; + format(elt, f)?; + } + } + + if update_index { + last_index = index; + } + } + for _ in 0..ndim { + write!(f, "]")?; + } + Ok(()) +} + fn format_array(view: &ArrayBase, f: &mut fmt::Formatter, mut format: F) -> fmt::Result @@ -92,7 +280,7 @@ impl<'a, A: fmt::Display, S, D: Dimension> fmt::Display for ArrayBase where S: Data, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - format_array(self, f, <_>::fmt) + format_array_v2(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT) } } @@ -105,7 +293,7 @@ impl<'a, A: fmt::Debug, S, D: Dimension> fmt::Debug for ArrayBase { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Add extra information for Debug - format_array(self, f, <_>::fmt)?; + format_array_v2(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT)?; write!(f, " shape={:?}, strides={:?}, layout={:?}", self.shape(), self.strides(), layout=self.view().layout())?; match D::NDIM { From 2450f5090c407425681839eab9d21f1c28d5d26f Mon Sep 17 00:00:00 2001 From: "andrei.papou" Date: Sun, 24 Mar 2019 22:37:20 +0300 Subject: [PATCH 05/56] Fixed existing tests --- src/arrayformat.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index ccd93454d..40f86e71d 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -36,7 +36,12 @@ impl ArrayDisplayMode { where S: Data, D: Dimension { - let last_dim = arr.shape().len() - 1; + let last_dim = match arr.shape().len().checked_sub(1) { + Some(v) => v, + None => { + return ArrayDisplayMode::Full; + } + }; let mut overflow_axis_pair: (Option, Option) = (None, None); for (axis, axis_size) in arr.shape().iter().enumerate().rev() { @@ -85,7 +90,7 @@ fn format_array_v2(view: &ArrayBase, let display_mode = ArrayDisplayMode::from_array(view, limit); let ndim = view.dim().into_dimension().slice().len(); - let nth_idx_max = view.shape().iter().last().unwrap(); + let nth_idx_max = if ndim > 0 { Some(view.shape().iter().last().unwrap()) } else { None }; // None will be an empty iter. let mut last_index = match view.dim().into_dimension().first_index() { @@ -169,10 +174,11 @@ fn format_array_v2(view: &ArrayBase, if print_row { let mut print_elt = true; - let nth_idx_val = index.slice().iter().last().unwrap(); + let nth_idx_op = index.slice().iter().last(); match display_mode { ArrayDisplayMode::VSplit | ArrayDisplayMode::DoubleSplit(_) => { - if nth_idx_val >= &limit && nth_idx_val < &(nth_idx_max - &limit) { + let nth_idx_val = nth_idx_op.unwrap(); + if nth_idx_val >= &limit && nth_idx_val < &(nth_idx_max.unwrap() - &limit) { print_elt = false; if !printed_ellipses_v { write!(f, ", ...")?; From 19a09caf747ee71a7014b75e5f9a73130b237dc4 Mon Sep 17 00:00:00 2001 From: Jim Turner Date: Mon, 25 Mar 2019 19:13:04 -0400 Subject: [PATCH 06/56] Improve docs of .raw_dim(), .shape(), and .strides() (#591) * Improve docs of .raw_dim(), .shape(), and .strides() * Add example of .shape()/.raw_dim() --- src/impl_methods.rs | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/impl_methods.rs b/src/impl_methods.rs index de74af795..444d98525 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -83,17 +83,53 @@ where } /// Return the shape of the array as it stored in the array. + /// + /// This is primarily useful for passing to other `ArrayBase` + /// functions, such as when creating another array of the same + /// shape and dimensionality. + /// + /// ``` + /// use ndarray::Array; + /// + /// let a = Array::from_elem((2, 3), 5.); + /// + /// // Create an array of zeros that's the same shape and dimensionality as `a`. + /// let b = Array::::zeros(a.raw_dim()); + /// ``` pub fn raw_dim(&self) -> D { self.dim.clone() } /// Return the shape of the array as a slice. - pub fn shape(&self) -> &[Ix] { + /// + /// Note that you probably don't want to use this to create an array of the + /// same shape as another array because creating an array with e.g. + /// [`Array::zeros()`](ArrayBase::zeros) using a shape of type `&[usize]` + /// results in a dynamic-dimensional array. If you want to create an array + /// that has the same shape and dimensionality as another array, use + /// [`.raw_dim()`](ArrayBase::raw_dim) instead: + /// + /// ```rust + /// use ndarray::{Array, Array2}; + /// + /// let a = Array2::::zeros((3, 4)); + /// let shape = a.shape(); + /// assert_eq!(shape, &[3, 4]); + /// + /// // Since `a.shape()` returned `&[usize]`, we get an `ArrayD` instance: + /// let b = Array::zeros(shape); + /// assert_eq!(a.clone().into_dyn(), b); + /// + /// // To get the same dimension type, use `.raw_dim()` instead: + /// let c = Array::zeros(a.raw_dim()); + /// assert_eq!(a, c); + /// ``` + pub fn shape(&self) -> &[usize] { self.dim.slice() } - /// Return the strides of the array as a slice - pub fn strides(&self) -> &[Ixs] { + /// Return the strides of the array as a slice. + pub fn strides(&self) -> &[isize] { let s = self.strides.slice(); // reinterpret unsigned integer as signed unsafe { From c569f75c9be3cae3e4ca798002966fbba58e5226 Mon Sep 17 00:00:00 2001 From: Jim Turner Date: Mon, 25 Mar 2019 01:30:28 -0400 Subject: [PATCH 07/56] Add docs about nested `Vec`s/`Array`s Fixes #609 (assuming we don't want to actually add conversion methods). --- src/lib.rs | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 7be810f9b..8b44c21d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -939,7 +939,8 @@ pub type Ixs = isize; /// 3Works only if the array is contiguous. /// /// The table above does not include all the constructors; it only shows -/// conversions to/from `Vec`s/slices. See below for more constructors. +/// conversions to/from `Vec`s/slices. See +/// [below](#constructor-methods-for-owned-arrays) for more constructors. /// /// [ArrayView::reborrow()]: type.ArrayView.html#method.reborrow /// [ArrayViewMut::reborrow()]: type.ArrayViewMut.html#method.reborrow @@ -952,6 +953,101 @@ pub type Ixs = isize; /// [.view()]: #method.view /// [.view_mut()]: #method.view_mut /// +/// ### Conversions from Nested `Vec`s/`Array`s +/// +/// It's generally a good idea to avoid nested `Vec`/`Array` types, such as +/// `Vec>` or `Vec>` because: +/// +/// * they require extra heap allocations compared to a single `Array`, +/// +/// * they can scatter data all over memory (because of multiple allocations), +/// +/// * they cause unnecessary indirection (traversing multiple pointers to reach +/// the data), +/// +/// * they don't enforce consistent shape within the nested +/// `Vec`s/`ArrayBase`s, and +/// +/// * they are generally more difficult to work with. +/// +/// The most common case where users might consider using nested +/// `Vec`s/`Array`s is when creating an array by appending rows/subviews in a +/// loop, where the rows/subviews are computed within the loop. However, there +/// are better ways than using nested `Vec`s/`Array`s. +/// +/// If you know ahead-of-time the shape of the final array, the cleanest +/// solution is to allocate the final array before the loop, and then assign +/// the data to it within the loop, like this: +/// +/// ```rust +/// use ndarray::{array, Array2, Axis}; +/// +/// let mut arr = Array2::zeros((2, 3)); +/// for (i, mut row) in arr.axis_iter_mut(Axis(0)).enumerate() { +/// // Perform calculations and assign to `row`; this is a trivial example: +/// row.fill(i); +/// } +/// assert_eq!(arr, array![[0, 0, 0], [1, 1, 1]]); +/// ``` +/// +/// If you don't know ahead-of-time the shape of the final array, then the +/// cleanest solution is generally to append the data to a flat `Vec`, and then +/// convert it to an `Array` at the end with +/// [`::from_shape_vec()`](#method.from_shape_vec). You just have to be careful +/// that the layout of the data (the order of the elements in the flat `Vec`) +/// is correct. +/// +/// ```rust +/// use ndarray::{array, Array2}; +/// +/// # fn main() -> Result<(), Box> { +/// let ncols = 3; +/// let mut data = Vec::new(); +/// let mut nrows = 0; +/// for i in 0..2 { +/// // Compute `row` and append it to `data`; this is a trivial example: +/// let row = vec![i; ncols]; +/// data.extend_from_slice(&row); +/// nrows += 1; +/// } +/// let arr = Array2::from_shape_vec((nrows, ncols), data)?; +/// assert_eq!(arr, array![[0, 0, 0], [1, 1, 1]]); +/// # Ok(()) +/// # } +/// ``` +/// +/// If neither of these options works for you, and you really need to convert +/// nested `Vec`/`Array` instances to an `Array`, the cleanest solution is +/// generally to use +/// [`Iterator::flatten()`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.flatten) +/// to get a flat `Vec`, and then convert the `Vec` to an `Array` with +/// [`::from_shape_vec()`](#method.from_shape_vec), like this: +/// +/// ```rust +/// use ndarray::{array, Array2, Array3}; +/// +/// # fn main() -> Result<(), Box> { +/// let nested: Vec> = vec![ +/// array![[1, 2, 3], [4, 5, 6]], +/// array![[7, 8, 9], [10, 11, 12]], +/// ]; +/// let inner_shape = nested[0].dim(); +/// let shape = (nested.len(), inner_shape.0, inner_shape.1); +/// let flat: Vec = nested.iter().flatten().cloned().collect(); +/// let arr = Array3::from_shape_vec(shape, flat)?; +/// assert_eq!(arr, array![ +/// [[1, 2, 3], [4, 5, 6]], +/// [[7, 8, 9], [10, 11, 12]], +/// ]); +/// # Ok(()) +/// # } +/// ``` +/// +/// Note that this implementation assumes that the nested `Vec`s are all the +/// same shape and that the `Vec` is non-empty. Depending on your application, +/// it may be a good idea to add checks for these assumptions and possibly +/// choose a different way to handle the empty case. +/// // # For implementors // // All methods must uphold the following constraints: From 3298cd53337f6d4f5a0821ca8c53b11cf0982a4e Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Thu, 10 Jan 2019 22:16:03 +0000 Subject: [PATCH 08/56] Add mean method --- Cargo.toml | 1 + src/numeric/impl_numeric.rs | 27 +++++++++++++++++++++++++++ tests/numeric.rs | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 tests/numeric.rs diff --git a/Cargo.toml b/Cargo.toml index 2f4f8bb0a..58ede0171 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ serde = { version = "1.0", optional = true } defmac = "0.2" quickcheck = { version = "0.7.2", default-features = false } rawpointer = "0.1" +approx = "0.3" [features] # Enable blas usage diff --git a/src/numeric/impl_numeric.rs b/src/numeric/impl_numeric.rs index f8c1f04a3..f357b734e 100644 --- a/src/numeric/impl_numeric.rs +++ b/src/numeric/impl_numeric.rs @@ -46,6 +46,33 @@ impl ArrayBase sum } + /// Returns the [arithmetic mean] x̅ of all elements in the array: + /// + /// ```text + /// 1 n + /// x̅ = ― ∑ xᵢ + /// n i=1 + /// ``` + /// + /// If the array is empty, `None` is returned. + /// + /// **Panics** if `A::from_usize()` fails to convert the number of elements in the array. + /// + /// [arithmetic mean]: https://en.wikipedia.org/wiki/Arithmetic_mean + pub fn mean(&self) -> Option + where + A: Clone + FromPrimitive + Add + Div + Zero + { + let n_elements = self.len(); + if n_elements == 0 { + None + } else { + let n_elements = A::from_usize(n_elements) + .expect("Converting number of elements to `A` must not fail."); + Some(self.sum() / n_elements) + } + } + /// Return the sum of all elements in the array. /// /// *This method has been renamed to `.sum()` and will be deprecated in the diff --git a/tests/numeric.rs b/tests/numeric.rs new file mode 100644 index 000000000..c7bf3ac47 --- /dev/null +++ b/tests/numeric.rs @@ -0,0 +1,35 @@ +extern crate approx; +use std::f64; +use ndarray::{Array1, array}; +use approx::abs_diff_eq; + +#[test] +fn test_mean_with_nan_values() { + let a = array![f64::NAN, 1.]; + assert!(a.mean().unwrap().is_nan()); +} + +#[test] +fn test_mean_with_empty_array_of_floats() { + let a: Array1 = array![]; + assert!(a.mean().is_none()); +} + +#[test] +fn test_mean_with_array_of_floats() { + let a: Array1 = array![ + 0.99889651, 0.0150731 , 0.28492482, 0.83819218, 0.48413156, + 0.80710412, 0.41762936, 0.22879429, 0.43997224, 0.23831807, + 0.02416466, 0.6269962 , 0.47420614, 0.56275487, 0.78995021, + 0.16060581, 0.64635041, 0.34876609, 0.78543249, 0.19938356, + 0.34429457, 0.88072369, 0.17638164, 0.60819363, 0.250392 , + 0.69912532, 0.78855523, 0.79140914, 0.85084218, 0.31839879, + 0.63381769, 0.22421048, 0.70760302, 0.99216018, 0.80199153, + 0.19239188, 0.61356023, 0.31505352, 0.06120481, 0.66417377, + 0.63608897, 0.84959691, 0.43599069, 0.77867775, 0.88267754, + 0.83003623, 0.67016118, 0.67547638, 0.65220036, 0.68043427 + ]; + // Computed using NumPy + let expected_mean = 0.5475494059146699; + abs_diff_eq!(a.mean().unwrap(), expected_mean, epsilon = f64::EPSILON); +} \ No newline at end of file From 6e37c46de22983498b5d8ca6fe9a635e7ad26cdc Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Thu, 10 Jan 2019 22:20:22 +0000 Subject: [PATCH 09/56] Make mean_axis return Option, like mean does --- examples/column_standardize.rs | 4 ++-- src/numeric/impl_numeric.rs | 25 ++++++++++++++++--------- tests/array.rs | 12 +++++------- tests/complex.rs | 2 +- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/examples/column_standardize.rs b/examples/column_standardize.rs index 032c520e3..80ca7332a 100644 --- a/examples/column_standardize.rs +++ b/examples/column_standardize.rs @@ -23,9 +23,9 @@ fn main() { [ 2., 2., 2.]]; println!("{:8.4}", data); - println!("{:8.4} (Mean axis=0)", data.mean_axis(Axis(0))); + println!("{:8.4} (Mean axis=0)", data.mean_axis(Axis(0)).unwrap()); - data -= &data.mean_axis(Axis(0)); + data -= &data.mean_axis(Axis(0)).unwrap(); println!("{:8.4}", data); data /= &std(&data, Axis(0)); diff --git a/src/numeric/impl_numeric.rs b/src/numeric/impl_numeric.rs index f357b734e..87e7be590 100644 --- a/src/numeric/impl_numeric.rs +++ b/src/numeric/impl_numeric.rs @@ -150,8 +150,9 @@ impl ArrayBase /// Return mean along `axis`. /// - /// **Panics** if `axis` is out of bounds, if the length of the axis is - /// zero and division by zero panics for type `A`, or if `A::from_usize()` + /// Return `None` if the length of the axis is zero. + /// + /// **Panics** if `axis` is out of bounds or if `A::from_usize()` /// fails for the axis length. /// /// ``` @@ -160,19 +161,25 @@ impl ArrayBase /// let a = arr2(&[[1., 2., 3.], /// [4., 5., 6.]]); /// assert!( - /// a.mean_axis(Axis(0)) == aview1(&[2.5, 3.5, 4.5]) && - /// a.mean_axis(Axis(1)) == aview1(&[2., 5.]) && + /// a.mean_axis(Axis(0)).unwrap() == aview1(&[2.5, 3.5, 4.5]) && + /// a.mean_axis(Axis(1)).unwrap() == aview1(&[2., 5.]) && /// - /// a.mean_axis(Axis(0)).mean_axis(Axis(0)) == aview0(&3.5) + /// a.mean_axis(Axis(0)).unwrap().mean_axis(Axis(0)).unwrap() == aview0(&3.5) /// ); /// ``` - pub fn mean_axis(&self, axis: Axis) -> Array + pub fn mean_axis(&self, axis: Axis) -> Option> where A: Clone + Zero + FromPrimitive + Add + Div, D: RemoveAxis, { - let n = A::from_usize(self.len_of(axis)).expect("Converting axis length to `A` must not fail."); - let sum = self.sum_axis(axis); - sum / &aview0(&n) + let axis_length = self.len_of(axis); + if axis_length == 0 { + None + } else { + let axis_length = A::from_usize(axis_length) + .expect("Converting axis length to `A` must not fail."); + let sum = self.sum_axis(axis); + Some(sum / &aview0(&axis_length)) + } } /// Return variance along `axis`. diff --git a/tests/array.rs b/tests/array.rs index 28e2e7fbc..e40be7c14 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -931,10 +931,10 @@ fn sum_mean() let a = arr2(&[[1., 2.], [3., 4.]]); assert_eq!(a.sum_axis(Axis(0)), arr1(&[4., 6.])); assert_eq!(a.sum_axis(Axis(1)), arr1(&[3., 7.])); - assert_eq!(a.mean_axis(Axis(0)), arr1(&[2., 3.])); - assert_eq!(a.mean_axis(Axis(1)), arr1(&[1.5, 3.5])); + assert_eq!(a.mean_axis(Axis(0)), Some(arr1(&[2., 3.]))); + assert_eq!(a.mean_axis(Axis(1)), Some(arr1(&[1.5, 3.5]))); assert_eq!(a.sum_axis(Axis(1)).sum_axis(Axis(0)), arr0(10.)); - assert_eq!(a.view().mean_axis(Axis(1)), aview1(&[1.5, 3.5])); + assert_eq!(a.view().mean_axis(Axis(1)).unwrap(), aview1(&[1.5, 3.5])); assert_eq!(a.sum(), 10.); } @@ -947,11 +947,9 @@ fn sum_mean_empty() { Array::zeros((2, 3)), ); let a = Array1::::ones(0).mean_axis(Axis(0)); - assert_eq!(a.shape(), &[]); - assert!(a[()].is_nan()); + assert_eq!(a, None); let a = Array3::::ones((2, 0, 3)).mean_axis(Axis(1)); - assert_eq!(a.shape(), &[2, 3]); - a.mapv(|x| assert!(x.is_nan())); + assert_eq!(a, None); } #[test] diff --git a/tests/complex.rs b/tests/complex.rs index 8da721c4d..a7449e9f8 100644 --- a/tests/complex.rs +++ b/tests/complex.rs @@ -22,5 +22,5 @@ fn complex_mat_mul() let r = a.dot(&e); println!("{}", a); assert_eq!(r, a); - assert_eq!(a.mean_axis(Axis(0)), arr1(&[c(1.5, 1.), c(2.5, 0.)])); + assert_eq!(a.mean_axis(Axis(0)).unwrap(), arr1(&[c(1.5, 1.), c(2.5, 0.)])); } From 64b3da7b97d121d2fd09133926eb057d943c0702 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Mon, 14 Jan 2019 09:15:21 +0000 Subject: [PATCH 10/56] Move numeric tests into the numeric test submodule --- tests/array.rs | 167 --------------------------------------------- tests/numeric.rs | 172 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 170 insertions(+), 169 deletions(-) diff --git a/tests/array.rs b/tests/array.rs index e40be7c14..bbe05bbda 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -925,173 +925,6 @@ fn assign() assert_eq!(a, arr2(&[[0, 0], [3, 4]])); } -#[test] -fn sum_mean() -{ - let a = arr2(&[[1., 2.], [3., 4.]]); - assert_eq!(a.sum_axis(Axis(0)), arr1(&[4., 6.])); - assert_eq!(a.sum_axis(Axis(1)), arr1(&[3., 7.])); - assert_eq!(a.mean_axis(Axis(0)), Some(arr1(&[2., 3.]))); - assert_eq!(a.mean_axis(Axis(1)), Some(arr1(&[1.5, 3.5]))); - assert_eq!(a.sum_axis(Axis(1)).sum_axis(Axis(0)), arr0(10.)); - assert_eq!(a.view().mean_axis(Axis(1)).unwrap(), aview1(&[1.5, 3.5])); - assert_eq!(a.sum(), 10.); -} - -#[test] -fn sum_mean_empty() { - assert_eq!(Array3::::ones((2, 0, 3)).sum(), 0.); - assert_eq!(Array1::::ones(0).sum_axis(Axis(0)), arr0(0.)); - assert_eq!( - Array3::::ones((2, 0, 3)).sum_axis(Axis(1)), - Array::zeros((2, 3)), - ); - let a = Array1::::ones(0).mean_axis(Axis(0)); - assert_eq!(a, None); - let a = Array3::::ones((2, 0, 3)).mean_axis(Axis(1)); - assert_eq!(a, None); -} - -#[test] -fn var_axis() { - let a = array![ - [ - [-9.76, -0.38, 1.59, 6.23], - [-8.57, -9.27, 5.76, 6.01], - [-9.54, 5.09, 3.21, 6.56], - ], - [ - [ 8.23, -9.63, 3.76, -3.48], - [-5.46, 5.86, -2.81, 1.35], - [-1.08, 4.66, 8.34, -0.73], - ], - ]; - assert!(a.var_axis(Axis(0), 1.5).all_close( - &aview2(&[ - [3.236401e+02, 8.556250e+01, 4.708900e+00, 9.428410e+01], - [9.672100e+00, 2.289169e+02, 7.344490e+01, 2.171560e+01], - [7.157160e+01, 1.849000e-01, 2.631690e+01, 5.314410e+01] - ]), - 1e-4, - )); - assert!(a.var_axis(Axis(1), 1.7).all_close( - &aview2(&[ - [0.61676923, 80.81092308, 6.79892308, 0.11789744], - [75.19912821, 114.25235897, 48.32405128, 9.03020513], - ]), - 1e-8, - )); - assert!(a.var_axis(Axis(2), 2.3).all_close( - &aview2(&[ - [ 79.64552941, 129.09663235, 95.98929412], - [109.64952941, 43.28758824, 36.27439706], - ]), - 1e-8, - )); - - let b = array![[1.1, 2.3, 4.7]]; - assert!(b.var_axis(Axis(0), 0.).all_close(&aview1(&[0., 0., 0.]), 1e-12)); - assert!(b.var_axis(Axis(1), 0.).all_close(&aview1(&[2.24]), 1e-12)); - - let c = array![[], []]; - assert_eq!(c.var_axis(Axis(0), 0.), aview1(&[])); - - let d = array![1.1, 2.7, 3.5, 4.9]; - assert!(d.var_axis(Axis(0), 0.).all_close(&aview0(&1.8875), 1e-12)); -} - -#[test] -fn std_axis() { - let a = array![ - [ - [ 0.22935481, 0.08030619, 0.60827517, 0.73684379], - [ 0.90339851, 0.82859436, 0.64020362, 0.2774583 ], - [ 0.44485313, 0.63316367, 0.11005111, 0.08656246] - ], - [ - [ 0.28924665, 0.44082454, 0.59837736, 0.41014531], - [ 0.08382316, 0.43259439, 0.1428889 , 0.44830176], - [ 0.51529756, 0.70111616, 0.20799415, 0.91851457] - ], - ]; - assert!(a.std_axis(Axis(0), 1.5).all_close( - &aview2(&[ - [ 0.05989184, 0.36051836, 0.00989781, 0.32669847], - [ 0.81957535, 0.39599997, 0.49731472, 0.17084346], - [ 0.07044443, 0.06795249, 0.09794304, 0.83195211], - ]), - 1e-4, - )); - assert!(a.std_axis(Axis(1), 1.7).all_close( - &aview2(&[ - [ 0.42698655, 0.48139215, 0.36874991, 0.41458724], - [ 0.26769097, 0.18941435, 0.30555015, 0.35118674], - ]), - 1e-8, - )); - assert!(a.std_axis(Axis(2), 2.3).all_close( - &aview2(&[ - [ 0.41117907, 0.37130425, 0.35332388], - [ 0.16905862, 0.25304841, 0.39978276], - ]), - 1e-8, - )); - - let b = array![[100000., 1., 0.01]]; - assert!(b.std_axis(Axis(0), 0.).all_close(&aview1(&[0., 0., 0.]), 1e-12)); - assert!( - b.std_axis(Axis(1), 0.).all_close(&aview1(&[47140.214021552769]), 1e-6), - ); - - let c = array![[], []]; - assert_eq!(c.std_axis(Axis(0), 0.), aview1(&[])); -} - -#[test] -#[should_panic] -fn var_axis_negative_ddof() { - let a = array![1., 2., 3.]; - a.var_axis(Axis(0), -1.); -} - -#[test] -#[should_panic] -fn var_axis_too_large_ddof() { - let a = array![1., 2., 3.]; - a.var_axis(Axis(0), 4.); -} - -#[test] -fn var_axis_nan_ddof() { - let a = Array2::::zeros((2, 3)); - let v = a.var_axis(Axis(1), ::std::f64::NAN); - assert_eq!(v.shape(), &[2]); - v.mapv(|x| assert!(x.is_nan())); -} - -#[test] -fn var_axis_empty_axis() { - let a = Array2::::zeros((2, 0)); - let v = a.var_axis(Axis(1), 0.); - assert_eq!(v.shape(), &[2]); - v.mapv(|x| assert!(x.is_nan())); -} - -#[test] -#[should_panic] -fn std_axis_bad_dof() { - let a = array![1., 2., 3.]; - a.std_axis(Axis(0), 4.); -} - -#[test] -fn std_axis_empty_axis() { - let a = Array2::::zeros((2, 0)); - let v = a.std_axis(Axis(1), 0.); - assert_eq!(v.shape(), &[2]); - v.mapv(|x| assert!(x.is_nan())); -} - #[test] fn iter_size_hint() { diff --git a/tests/numeric.rs b/tests/numeric.rs index c7bf3ac47..e73da4904 100644 --- a/tests/numeric.rs +++ b/tests/numeric.rs @@ -1,6 +1,6 @@ extern crate approx; use std::f64; -use ndarray::{Array1, array}; +use ndarray::{array, Axis, aview1, aview2, aview0, arr0, arr1, arr2, Array, Array1, Array2, Array3}; use approx::abs_diff_eq; #[test] @@ -32,4 +32,172 @@ fn test_mean_with_array_of_floats() { // Computed using NumPy let expected_mean = 0.5475494059146699; abs_diff_eq!(a.mean().unwrap(), expected_mean, epsilon = f64::EPSILON); -} \ No newline at end of file +} + +#[test] +fn sum_mean() +{ + let a = arr2(&[[1., 2.], [3., 4.]]); + assert_eq!(a.sum_axis(Axis(0)), arr1(&[4., 6.])); + assert_eq!(a.sum_axis(Axis(1)), arr1(&[3., 7.])); + assert_eq!(a.mean_axis(Axis(0)), Some(arr1(&[2., 3.]))); + assert_eq!(a.mean_axis(Axis(1)), Some(arr1(&[1.5, 3.5]))); + assert_eq!(a.sum_axis(Axis(1)).sum_axis(Axis(0)), arr0(10.)); + assert_eq!(a.view().mean_axis(Axis(1)).unwrap(), aview1(&[1.5, 3.5])); + assert_eq!(a.sum(), 10.); +} + +#[test] +fn sum_mean_empty() { + assert_eq!(Array3::::ones((2, 0, 3)).sum(), 0.); + assert_eq!(Array1::::ones(0).sum_axis(Axis(0)), arr0(0.)); + assert_eq!( + Array3::::ones((2, 0, 3)).sum_axis(Axis(1)), + Array::zeros((2, 3)), + ); + let a = Array1::::ones(0).mean_axis(Axis(0)); + assert_eq!(a, None); + let a = Array3::::ones((2, 0, 3)).mean_axis(Axis(1)); + assert_eq!(a, None); +} + +#[test] +fn var_axis() { + let a = array![ + [ + [-9.76, -0.38, 1.59, 6.23], + [-8.57, -9.27, 5.76, 6.01], + [-9.54, 5.09, 3.21, 6.56], + ], + [ + [ 8.23, -9.63, 3.76, -3.48], + [-5.46, 5.86, -2.81, 1.35], + [-1.08, 4.66, 8.34, -0.73], + ], + ]; + assert!(a.var_axis(Axis(0), 1.5).all_close( + &aview2(&[ + [3.236401e+02, 8.556250e+01, 4.708900e+00, 9.428410e+01], + [9.672100e+00, 2.289169e+02, 7.344490e+01, 2.171560e+01], + [7.157160e+01, 1.849000e-01, 2.631690e+01, 5.314410e+01] + ]), + 1e-4, + )); + assert!(a.var_axis(Axis(1), 1.7).all_close( + &aview2(&[ + [0.61676923, 80.81092308, 6.79892308, 0.11789744], + [75.19912821, 114.25235897, 48.32405128, 9.03020513], + ]), + 1e-8, + )); + assert!(a.var_axis(Axis(2), 2.3).all_close( + &aview2(&[ + [ 79.64552941, 129.09663235, 95.98929412], + [109.64952941, 43.28758824, 36.27439706], + ]), + 1e-8, + )); + + let b = array![[1.1, 2.3, 4.7]]; + assert!(b.var_axis(Axis(0), 0.).all_close(&aview1(&[0., 0., 0.]), 1e-12)); + assert!(b.var_axis(Axis(1), 0.).all_close(&aview1(&[2.24]), 1e-12)); + + let c = array![[], []]; + assert_eq!(c.var_axis(Axis(0), 0.), aview1(&[])); + + let d = array![1.1, 2.7, 3.5, 4.9]; + assert!(d.var_axis(Axis(0), 0.).all_close(&aview0(&1.8875), 1e-12)); +} + +#[test] +fn std_axis() { + let a = array![ + [ + [ 0.22935481, 0.08030619, 0.60827517, 0.73684379], + [ 0.90339851, 0.82859436, 0.64020362, 0.2774583 ], + [ 0.44485313, 0.63316367, 0.11005111, 0.08656246] + ], + [ + [ 0.28924665, 0.44082454, 0.59837736, 0.41014531], + [ 0.08382316, 0.43259439, 0.1428889 , 0.44830176], + [ 0.51529756, 0.70111616, 0.20799415, 0.91851457] + ], + ]; + assert!(a.std_axis(Axis(0), 1.5).all_close( + &aview2(&[ + [ 0.05989184, 0.36051836, 0.00989781, 0.32669847], + [ 0.81957535, 0.39599997, 0.49731472, 0.17084346], + [ 0.07044443, 0.06795249, 0.09794304, 0.83195211], + ]), + 1e-4, + )); + assert!(a.std_axis(Axis(1), 1.7).all_close( + &aview2(&[ + [ 0.42698655, 0.48139215, 0.36874991, 0.41458724], + [ 0.26769097, 0.18941435, 0.30555015, 0.35118674], + ]), + 1e-8, + )); + assert!(a.std_axis(Axis(2), 2.3).all_close( + &aview2(&[ + [ 0.41117907, 0.37130425, 0.35332388], + [ 0.16905862, 0.25304841, 0.39978276], + ]), + 1e-8, + )); + + let b = array![[100000., 1., 0.01]]; + assert!(b.std_axis(Axis(0), 0.).all_close(&aview1(&[0., 0., 0.]), 1e-12)); + assert!( + b.std_axis(Axis(1), 0.).all_close(&aview1(&[47140.214021552769]), 1e-6), + ); + + let c = array![[], []]; + assert_eq!(c.std_axis(Axis(0), 0.), aview1(&[])); +} + +#[test] +#[should_panic] +fn var_axis_negative_ddof() { + let a = array![1., 2., 3.]; + a.var_axis(Axis(0), -1.); +} + +#[test] +#[should_panic] +fn var_axis_too_large_ddof() { + let a = array![1., 2., 3.]; + a.var_axis(Axis(0), 4.); +} + +#[test] +fn var_axis_nan_ddof() { + let a = Array2::::zeros((2, 3)); + let v = a.var_axis(Axis(1), ::std::f64::NAN); + assert_eq!(v.shape(), &[2]); + v.mapv(|x| assert!(x.is_nan())); +} + +#[test] +fn var_axis_empty_axis() { + let a = Array2::::zeros((2, 0)); + let v = a.var_axis(Axis(1), 0.); + assert_eq!(v.shape(), &[2]); + v.mapv(|x| assert!(x.is_nan())); +} + +#[test] +#[should_panic] +fn std_axis_bad_dof() { + let a = array![1., 2., 3.]; + a.std_axis(Axis(0), 4.); +} + +#[test] +fn std_axis_empty_axis() { + let a = Array2::::zeros((2, 0)); + let v = a.std_axis(Axis(1), 0.); + assert_eq!(v.shape(), &[2]); + v.mapv(|x| assert!(x.is_nan())); +} + From 3474d3f43302f91e0c773f06002f6e80389966dc Mon Sep 17 00:00:00 2001 From: andrei-papou Date: Tue, 26 Mar 2019 14:34:15 +0300 Subject: [PATCH 11/56] Some fixes: - fixed typo in ArrayDisplayMode comment - updated ArrayDisplayMode constructor to use shape instead of array - fixed output for complex dimensions --- src/arrayformat.rs | 86 ++++++++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 30 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index 40f86e71d..0414e78c9 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -17,7 +17,7 @@ use crate::dimension::IntoDimension; #[derive(Debug)] enum ArrayDisplayMode { - // Array is small enough to me printed without omitting any values. + // Array is small enough to be printed without omitting any values. Full, // Omit central values of the nth axis. Since we print that axis horizontally, ellipses // on each row do something like a split of the array into 2 parts vertically. @@ -32,62 +32,66 @@ enum ArrayDisplayMode { const PRINT_ELEMENTS_LIMIT: Ix = 5; impl ArrayDisplayMode { - fn from_array(arr: &ArrayBase, limit: usize) -> ArrayDisplayMode - where S: Data, - D: Dimension + fn from_shape(shape: &[Ix], limit: Ix) -> ArrayDisplayMode { - let last_dim = match arr.shape().len().checked_sub(1) { + let last_dim = match shape.len().checked_sub(1) { Some(v) => v, None => { return ArrayDisplayMode::Full; } }; - let mut overflow_axis_pair: (Option, Option) = (None, None); - for (axis, axis_size) in arr.shape().iter().enumerate().rev() { + let mut overflow_axes: Vec = Vec::with_capacity(shape.len()); + for (axis, axis_size) in shape.iter().enumerate().rev() { if *axis_size >= 2 * limit + 1 { - match overflow_axis_pair.0 { - Some(_) => { - if let None = overflow_axis_pair.1 { - overflow_axis_pair.1 = Some(axis); - } - }, - None => { - if axis != last_dim { - return ArrayDisplayMode::HSplit(axis); - } - overflow_axis_pair.0 = Some(axis); - } - } + overflow_axes.push(axis); } } - match overflow_axis_pair { - (Some(_), Some(h_axis)) => ArrayDisplayMode::DoubleSplit(h_axis), - (Some(_), None) => ArrayDisplayMode::VSplit, - (None, _) => ArrayDisplayMode::Full, + if overflow_axes.is_empty() { + return ArrayDisplayMode::Full; + } + + let min_ovf_axis = *overflow_axes.iter().min().unwrap(); + let max_ovf_axis = *overflow_axes.iter().max().unwrap(); + + if max_ovf_axis == last_dim { + if min_ovf_axis != max_ovf_axis { + ArrayDisplayMode::DoubleSplit(min_ovf_axis) + } else { + ArrayDisplayMode::VSplit + } + } else { + ArrayDisplayMode::HSplit(min_ovf_axis) } } - fn h_split_offset(&self) -> Option { + fn h_split_axis(&self) -> Option { match self { ArrayDisplayMode::DoubleSplit(axis) | ArrayDisplayMode::HSplit(axis) => { - Some(axis + 1usize) + Some(*axis) }, _ => None } } + + fn h_split_offset(&self) -> Option { + match self.h_split_axis() { + Some(axis) => Some(axis + 1usize), + None => None + } + } } fn format_array_v2(view: &ArrayBase, - f: &mut fmt::Formatter, - mut format: F, - limit: usize) -> fmt::Result + f: &mut fmt::Formatter, + mut format: F, + limit: Ix) -> fmt::Result where F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result, D: Dimension, S: Data, { - let display_mode = ArrayDisplayMode::from_array(view, limit); + let display_mode = ArrayDisplayMode::from_shape(view.shape(), limit); let ndim = view.dim().into_dimension().slice().len(); let nth_idx_max = if ndim > 0 { Some(view.shape().iter().last().unwrap()) } else { None }; @@ -134,6 +138,12 @@ fn format_array_v2(view: &ArrayBase, .zip(last_index.slice().iter()) .enumerate() { if a != b { + if let Some(axis) = display_mode.h_split_axis() { + if i < axis { + printed_ellipses_h = false; + } + } + if print_row { printed_ellipses_v = false; // New row. @@ -356,3 +366,19 @@ impl<'a, A: fmt::Binary, S, D: Dimension> fmt::Binary for ArrayBase format_array(self, f, <_>::fmt) } } + +#[cfg(test)] +mod format_tests { + use super::*; + + fn test_array_display_mode_from_shape() { + let mode = ArrayDisplayMode::from_shape(&[4, 4], 2); + assert_eq!(mode, ArrayDisplayMode::Full); + + let mode = ArrayDisplayMode::from_shape(&[3, 6], 2); + assert_eq!(mode, ArrayDisplayMode::VSplit); + + let mode = ArrayDisplayMode::from_shape(&[5, 6, 3], 2); + assert_eq!(mode, ArrayDisplayMode::HSplit(1)); + } +} From 2a8a7c7ec0c913db7046cbde8f368d8c963229a8 Mon Sep 17 00:00:00 2001 From: andrei-papou Date: Tue, 26 Mar 2019 19:04:35 +0300 Subject: [PATCH 12/56] Implemented axis shrinking for n-dim arrays --- src/arrayformat.rs | 111 +++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 60 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index 0414e78c9..9fc4e6b2b 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -6,6 +6,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. use std::fmt; +use std::slice::Iter; use super::{ ArrayBase, Data, @@ -15,25 +16,24 @@ use super::{ }; use crate::dimension::IntoDimension; -#[derive(Debug)] +#[derive(Debug, PartialEq)] enum ArrayDisplayMode { // Array is small enough to be printed without omitting any values. Full, - // Omit central values of the nth axis. Since we print that axis horizontally, ellipses - // on each row do something like a split of the array into 2 parts vertically. - VSplit, - // Omit central values of the certain axis. Since we do it only once, ellipses on each row - // do something like a split of the array into 2 parts horizontally. - HSplit(Ix), - // Both `VSplit` and `HSplit` hold. - DoubleSplit(Ix), + // Omit central values of the nth axis. + OmitV, + // Omit central values of certain axes (but not the last one). + // Vector is guaranteed to be non-empty. + OmitH(Vec), + // Both `OmitV` and `OmitH` hold. + OmitBoth(Vec), } -const PRINT_ELEMENTS_LIMIT: Ix = 5; +const PRINT_ELEMENTS_LIMIT: Ix = 2; impl ArrayDisplayMode { - fn from_shape(shape: &[Ix], limit: Ix) -> ArrayDisplayMode - { + + fn from_shape(shape: &[Ix], limit: Ix) -> ArrayDisplayMode { let last_dim = match shape.len().checked_sub(1) { Some(v) => v, None => { @@ -41,46 +41,36 @@ impl ArrayDisplayMode { } }; + let last_axis_ovf = shape[last_dim] >= 2 * limit + 1; let mut overflow_axes: Vec = Vec::with_capacity(shape.len()); for (axis, axis_size) in shape.iter().enumerate().rev() { + if axis == last_dim { + continue; + } if *axis_size >= 2 * limit + 1 { overflow_axes.push(axis); } } - if overflow_axes.is_empty() { - return ArrayDisplayMode::Full; - } - - let min_ovf_axis = *overflow_axes.iter().min().unwrap(); - let max_ovf_axis = *overflow_axes.iter().max().unwrap(); - - if max_ovf_axis == last_dim { - if min_ovf_axis != max_ovf_axis { - ArrayDisplayMode::DoubleSplit(min_ovf_axis) - } else { - ArrayDisplayMode::VSplit - } + if !overflow_axes.is_empty() && last_axis_ovf { + ArrayDisplayMode::OmitBoth(overflow_axes) + } else if !overflow_axes.is_empty() { + ArrayDisplayMode::OmitH(overflow_axes) + } else if last_axis_ovf { + ArrayDisplayMode::OmitV } else { - ArrayDisplayMode::HSplit(min_ovf_axis) + ArrayDisplayMode::Full } } - fn h_split_axis(&self) -> Option { + fn h_axes_iter(&self) -> Option> { match self { - ArrayDisplayMode::DoubleSplit(axis) | ArrayDisplayMode::HSplit(axis) => { - Some(*axis) + ArrayDisplayMode::OmitH(v) | ArrayDisplayMode::OmitBoth(v) => { + Some(v.iter()) }, _ => None } } - - fn h_split_offset(&self) -> Option { - match self.h_split_axis() { - Some(axis) => Some(axis + 1usize), - None => None - } - } } fn format_array_v2(view: &ArrayBase, @@ -108,7 +98,7 @@ fn format_array_v2(view: &ArrayBase, // Shows if ellipses for vertical split were printed. let mut printed_ellipses_v = false; // Shows if ellipses for horizontal split were printed. - let mut printed_ellipses_h = false; + let mut printed_ellipses_h = vec![false; ndim]; // Shows if the row was printed for the first time after horizontal split. let mut no_rows_after_skip_yet = false; @@ -119,17 +109,20 @@ fn format_array_v2(view: &ArrayBase, let take_n = if ndim == 0 { 1 } else { ndim - 1 }; let mut update_index = false; - let mut print_row = true; - match display_mode { - ArrayDisplayMode::HSplit(axis) | ArrayDisplayMode::DoubleSplit(axis) => { - let sa_idx_max = view.shape().iter().skip(axis).next().unwrap(); - let sa_idx_val = index.slice().iter().skip(axis).next().unwrap(); - if sa_idx_val >= &limit && sa_idx_val < &(sa_idx_max - &limit) { - print_row = false; - no_rows_after_skip_yet = true; - } + let skip_row_for_axis = match display_mode.h_axes_iter() { + Some(iter) => { + iter.filter(|axis| { + let sa_idx_max = view.shape().iter().skip(**axis).next().unwrap(); + let sa_idx_val = index.slice().iter().skip(**axis).next().unwrap(); + sa_idx_val >= &limit && sa_idx_val < &(sa_idx_max - &limit) + }) + .min() + .map(|v| *v) }, - _ => {} + None => None + }; + if let Some(_) = skip_row_for_axis { + no_rows_after_skip_yet = true; } for (i, (a, b)) in index.slice() @@ -138,13 +131,9 @@ fn format_array_v2(view: &ArrayBase, .zip(last_index.slice().iter()) .enumerate() { if a != b { - if let Some(axis) = display_mode.h_split_axis() { - if i < axis { - printed_ellipses_h = false; - } - } + printed_ellipses_h.iter_mut().skip(i + 1).for_each(|e| { *e = false; }); - if print_row { + if skip_row_for_axis.is_none() { printed_ellipses_v = false; // New row. // # of ['s needed @@ -163,18 +152,19 @@ fn format_array_v2(view: &ArrayBase, for _ in 0..n { write!(f, "[")?; } - } else if !printed_ellipses_h { + } else if !printed_ellipses_h[skip_row_for_axis.unwrap()] { + let ax = skip_row_for_axis.unwrap(); let n = ndim - i - 1; for _ in 0..n { write!(f, "]")?; } write!(f, ",")?; write!(f, "\n")?; - for _ in 0..display_mode.h_split_offset().unwrap() { + for _ in 0..(ax + 1) { write!(f, " ")?; } write!(f, "...,\n")?; - printed_ellipses_h = true; + printed_ellipses_h[ax] = true; } first = true; update_index = true; @@ -182,11 +172,11 @@ fn format_array_v2(view: &ArrayBase, } } - if print_row { + if skip_row_for_axis.is_none() { let mut print_elt = true; let nth_idx_op = index.slice().iter().last(); match display_mode { - ArrayDisplayMode::VSplit | ArrayDisplayMode::DoubleSplit(_) => { + ArrayDisplayMode::OmitV | ArrayDisplayMode::OmitBoth(_) => { let nth_idx_val = nth_idx_op.unwrap(); if nth_idx_val >= &limit && nth_idx_val < &(nth_idx_max.unwrap() - &limit) { print_elt = false; @@ -371,14 +361,15 @@ impl<'a, A: fmt::Binary, S, D: Dimension> fmt::Binary for ArrayBase mod format_tests { use super::*; + #[test] fn test_array_display_mode_from_shape() { let mode = ArrayDisplayMode::from_shape(&[4, 4], 2); assert_eq!(mode, ArrayDisplayMode::Full); let mode = ArrayDisplayMode::from_shape(&[3, 6], 2); - assert_eq!(mode, ArrayDisplayMode::VSplit); + assert_eq!(mode, ArrayDisplayMode::OmitV); let mode = ArrayDisplayMode::from_shape(&[5, 6, 3], 2); - assert_eq!(mode, ArrayDisplayMode::HSplit(1)); + assert_eq!(mode, ArrayDisplayMode::OmitH(vec![1, 0])); } } From 1b5da678ca2fe27adf67454b2133ee02da5c9b32 Mon Sep 17 00:00:00 2001 From: "andrei.papou" Date: Wed, 27 Mar 2019 09:57:38 +0300 Subject: [PATCH 13/56] All the PR issues are fixed. Tests are needed. --- src/arrayformat.rs | 163 +++++++++++---------------------------------- 1 file changed, 40 insertions(+), 123 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index 9fc4e6b2b..c7b7421a6 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -6,7 +6,6 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. use std::fmt; -use std::slice::Iter; use super::{ ArrayBase, Data, @@ -16,63 +15,8 @@ use super::{ }; use crate::dimension::IntoDimension; -#[derive(Debug, PartialEq)] -enum ArrayDisplayMode { - // Array is small enough to be printed without omitting any values. - Full, - // Omit central values of the nth axis. - OmitV, - // Omit central values of certain axes (but not the last one). - // Vector is guaranteed to be non-empty. - OmitH(Vec), - // Both `OmitV` and `OmitH` hold. - OmitBoth(Vec), -} - const PRINT_ELEMENTS_LIMIT: Ix = 2; -impl ArrayDisplayMode { - - fn from_shape(shape: &[Ix], limit: Ix) -> ArrayDisplayMode { - let last_dim = match shape.len().checked_sub(1) { - Some(v) => v, - None => { - return ArrayDisplayMode::Full; - } - }; - - let last_axis_ovf = shape[last_dim] >= 2 * limit + 1; - let mut overflow_axes: Vec = Vec::with_capacity(shape.len()); - for (axis, axis_size) in shape.iter().enumerate().rev() { - if axis == last_dim { - continue; - } - if *axis_size >= 2 * limit + 1 { - overflow_axes.push(axis); - } - } - - if !overflow_axes.is_empty() && last_axis_ovf { - ArrayDisplayMode::OmitBoth(overflow_axes) - } else if !overflow_axes.is_empty() { - ArrayDisplayMode::OmitH(overflow_axes) - } else if last_axis_ovf { - ArrayDisplayMode::OmitV - } else { - ArrayDisplayMode::Full - } - } - - fn h_axes_iter(&self) -> Option> { - match self { - ArrayDisplayMode::OmitH(v) | ArrayDisplayMode::OmitBoth(v) => { - Some(v.iter()) - }, - _ => None - } - } -} - fn format_array_v2(view: &ArrayBase, f: &mut fmt::Formatter, mut format: F, @@ -81,19 +25,27 @@ fn format_array_v2(view: &ArrayBase, D: Dimension, S: Data, { - let display_mode = ArrayDisplayMode::from_shape(view.shape(), limit); + if view.shape().is_empty() { + // Handle weird 0-dimensional array case first + return writeln!(f, "[]") + } + + let overflow_axes: Vec = view.shape().iter() + .enumerate() + .rev() + .filter(|(_, axis_size)| **axis_size > 2 * limit) + .map(|(axis, _)| axis) + .collect(); let ndim = view.dim().into_dimension().slice().len(); - let nth_idx_max = if ndim > 0 { Some(view.shape().iter().last().unwrap()) } else { None }; + let nth_idx_max = view.shape().iter().last().unwrap(); // None will be an empty iter. let mut last_index = match view.dim().into_dimension().first_index() { None => view.dim().into_dimension().clone(), Some(ix) => ix, }; - for _ in 0..ndim { - write!(f, "[")?; - } + write!(f, "{}", "[".repeat(ndim))?; let mut first = true; // Shows if ellipses for vertical split were printed. let mut printed_ellipses_v = false; @@ -109,18 +61,17 @@ fn format_array_v2(view: &ArrayBase, let take_n = if ndim == 0 { 1 } else { ndim - 1 }; let mut update_index = false; - let skip_row_for_axis = match display_mode.h_axes_iter() { - Some(iter) => { - iter.filter(|axis| { - let sa_idx_max = view.shape().iter().skip(**axis).next().unwrap(); - let sa_idx_val = index.slice().iter().skip(**axis).next().unwrap(); - sa_idx_val >= &limit && sa_idx_val < &(sa_idx_max - &limit) - }) - .min() - .map(|v| *v) - }, - None => None - }; + let skip_row_for_axis = overflow_axes.iter() + .filter(|axis| { + if **axis == ndim - 1 { + return false + }; + let sa_idx_max = view.shape().iter().skip(**axis).next().unwrap(); + let sa_idx_val = index.slice().iter().skip(**axis).next().unwrap(); + sa_idx_val >= &limit && sa_idx_val < &(sa_idx_max - &limit) + }) + .min() + .map(|v| *v); if let Some(_) = skip_row_for_axis { no_rows_after_skip_yet = true; } @@ -139,31 +90,19 @@ fn format_array_v2(view: &ArrayBase, // # of ['s needed let n = ndim - i - 1; if !no_rows_after_skip_yet { - for _ in 0..n { - write!(f, "]")?; - } - write!(f, ",")?; - write!(f, "\n")?; + write!(f, "{}", "]".repeat(n))?; + writeln!(f, ",")?; } no_rows_after_skip_yet = false; - for _ in 0..ndim - n { - write!(f, " ")?; - } - for _ in 0..n { - write!(f, "[")?; - } + write!(f, "{}", " ".repeat(ndim - n))?; + write!(f, "{}", "[".repeat(n))?; } else if !printed_ellipses_h[skip_row_for_axis.unwrap()] { let ax = skip_row_for_axis.unwrap(); let n = ndim - i - 1; - for _ in 0..n { - write!(f, "]")?; - } - write!(f, ",")?; - write!(f, "\n")?; - for _ in 0..(ax + 1) { - write!(f, " ")?; - } - write!(f, "...,\n")?; + write!(f, "{}", "]".repeat(n))?; + writeln!(f, ",")?; + write!(f, "{}", " ".repeat(ax + 1))?; + writeln!(f, "...,")?; printed_ellipses_h[ax] = true; } first = true; @@ -175,18 +114,15 @@ fn format_array_v2(view: &ArrayBase, if skip_row_for_axis.is_none() { let mut print_elt = true; let nth_idx_op = index.slice().iter().last(); - match display_mode { - ArrayDisplayMode::OmitV | ArrayDisplayMode::OmitBoth(_) => { - let nth_idx_val = nth_idx_op.unwrap(); - if nth_idx_val >= &limit && nth_idx_val < &(nth_idx_max.unwrap() - &limit) { - print_elt = false; - if !printed_ellipses_v { - write!(f, ", ...")?; - printed_ellipses_v = true; - } + if overflow_axes.contains(&(ndim - 1)) { + let nth_idx_val = nth_idx_op.unwrap(); + if nth_idx_val >= &limit && nth_idx_val < &(nth_idx_max - &limit) { + print_elt = false; + if !printed_ellipses_v { + write!(f, ", ...")?; + printed_ellipses_v = true; } } - _ => {} } if print_elt { @@ -202,9 +138,7 @@ fn format_array_v2(view: &ArrayBase, last_index = index; } } - for _ in 0..ndim { - write!(f, "]")?; - } + write!(f, "{}", "]".repeat(ndim))?; Ok(()) } @@ -356,20 +290,3 @@ impl<'a, A: fmt::Binary, S, D: Dimension> fmt::Binary for ArrayBase format_array(self, f, <_>::fmt) } } - -#[cfg(test)] -mod format_tests { - use super::*; - - #[test] - fn test_array_display_mode_from_shape() { - let mode = ArrayDisplayMode::from_shape(&[4, 4], 2); - assert_eq!(mode, ArrayDisplayMode::Full); - - let mode = ArrayDisplayMode::from_shape(&[3, 6], 2); - assert_eq!(mode, ArrayDisplayMode::OmitV); - - let mode = ArrayDisplayMode::from_shape(&[5, 6, 3], 2); - assert_eq!(mode, ArrayDisplayMode::OmitH(vec![1, 0])); - } -} From fbf8cac8235a1561358a1a0458472426a1fdb97e Mon Sep 17 00:00:00 2001 From: andrei-papou Date: Wed, 27 Mar 2019 11:20:46 +0300 Subject: [PATCH 14/56] Fixed the tests --- src/arrayformat.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index c7b7421a6..f751330c9 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -26,8 +26,8 @@ fn format_array_v2(view: &ArrayBase, S: Data, { if view.shape().is_empty() { - // Handle weird 0-dimensional array case first - return writeln!(f, "[]") + // Handle 0-dimensional array case first + return format(view.iter().next().unwrap(), f) } let overflow_axes: Vec = view.shape().iter() From 51c4e11fb6888eb73bfeac03ee842fa1d6e4eed0 Mon Sep 17 00:00:00 2001 From: andrei-papou Date: Wed, 27 Mar 2019 12:53:04 +0300 Subject: [PATCH 15/56] Added tests for 1- and 2-dimensional array with overflow --- src/arrayformat.rs | 11 ++-- src/lib.rs | 2 + tests/format.rs | 128 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 135 insertions(+), 6 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index f751330c9..bfdaf8068 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -15,7 +15,7 @@ use super::{ }; use crate::dimension::IntoDimension; -const PRINT_ELEMENTS_LIMIT: Ix = 2; +pub const PRINT_ELEMENTS_LIMIT: Ix = 3; fn format_array_v2(view: &ArrayBase, f: &mut fmt::Formatter, @@ -142,6 +142,7 @@ fn format_array_v2(view: &ArrayBase, Ok(()) } +#[allow(dead_code)] fn format_array(view: &ArrayBase, f: &mut fmt::Formatter, mut format: F) -> fmt::Result @@ -252,7 +253,7 @@ impl<'a, A: fmt::LowerExp, S, D: Dimension> fmt::LowerExp for ArrayBase where S: Data, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - format_array(self, f, <_>::fmt) + format_array_v2(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT) } } @@ -264,7 +265,7 @@ impl<'a, A: fmt::UpperExp, S, D: Dimension> fmt::UpperExp for ArrayBase where S: Data, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - format_array(self, f, <_>::fmt) + format_array_v2(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT) } } /// Format the array using `LowerHex` and apply the formatting parameters used @@ -275,7 +276,7 @@ impl<'a, A: fmt::LowerHex, S, D: Dimension> fmt::LowerHex for ArrayBase where S: Data, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - format_array(self, f, <_>::fmt) + format_array_v2(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT) } } @@ -287,6 +288,6 @@ impl<'a, A: fmt::Binary, S, D: Dimension> fmt::Binary for ArrayBase where S: Data, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - format_array(self, f, <_>::fmt) + format_array_v2(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT) } } diff --git a/src/lib.rs b/src/lib.rs index 7be810f9b..846d01452 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -149,6 +149,8 @@ mod array_serde; mod arrayformat; mod data_traits; +pub use arrayformat::PRINT_ELEMENTS_LIMIT; + pub use crate::aliases::*; #[allow(deprecated)] diff --git a/tests/format.rs b/tests/format.rs index 4dbc939cc..335326ae5 100644 --- a/tests/format.rs +++ b/tests/format.rs @@ -1,7 +1,7 @@ extern crate ndarray; use ndarray::prelude::*; -use ndarray::rcarr1; +use ndarray::{rcarr1, PRINT_ELEMENTS_LIMIT}; #[test] fn formatting() @@ -36,6 +36,132 @@ fn formatting() assert_eq!(s, "[01, ff, fe]"); } +#[cfg(test)] +mod formatting_with_omit { + use super::*; + + fn print_output_diff(expected: &str, actual: &str) { + println!("Expected output:\n{}\nActual output:\n{}", expected, actual); + } + + #[test] + fn dim_1() { + let overflow: usize = 5; + let a = Array1::from_elem((PRINT_ELEMENTS_LIMIT * 2 + overflow, ), 1); + let mut expected_output = String::from("["); + a.iter() + .take(PRINT_ELEMENTS_LIMIT) + .for_each(|elem| { expected_output.push_str(format!("{}, ", elem).as_str()) }); + expected_output.push_str("..."); + a.iter() + .skip(PRINT_ELEMENTS_LIMIT + overflow) + .for_each(|elem| { expected_output.push_str(format!(", {}", elem).as_str()) }); + expected_output.push(']'); + let actual_output = format!("{}", a); + + print_output_diff(&expected_output, &actual_output); + assert_eq!(actual_output, expected_output); + } + + #[test] + fn dim_2_last_axis_overflow() { + let overflow: usize = 3; + let a = Array2::from_elem((PRINT_ELEMENTS_LIMIT, PRINT_ELEMENTS_LIMIT * 2 + overflow), 1); + let mut expected_output = String::from("["); + + for i in 0..PRINT_ELEMENTS_LIMIT { + expected_output.push_str(format!("[{}", a[(i, 0)]).as_str()); + for j in 1..PRINT_ELEMENTS_LIMIT { + expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); + } + expected_output.push_str(", ..."); + for j in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow { + expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); + } + expected_output.push_str(if i < PRINT_ELEMENTS_LIMIT - 1 { "],\n " } else { "]" }); + } + expected_output.push(']'); + let actual_output = format!("{}", a); + + print_output_diff(&expected_output, &actual_output); + assert_eq!(actual_output, expected_output); + } + + #[test] + fn dim_2_non_last_axis_overflow() { + let overflow: usize = 5; + let a = Array2::from_elem((PRINT_ELEMENTS_LIMIT * 2 + overflow, PRINT_ELEMENTS_LIMIT), 1); + let mut expected_output = String::from("["); + + for i in 0..PRINT_ELEMENTS_LIMIT { + expected_output.push_str(format!("[{}", a[(i, 0)]).as_str()); + for j in 1..PRINT_ELEMENTS_LIMIT { + expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); + } + expected_output.push_str("],\n "); + } + expected_output.push_str("...,\n "); + for i in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow { + expected_output.push_str(format!("[{}", a[(i, 0)]).as_str()); + for j in 1..PRINT_ELEMENTS_LIMIT { + expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); + } + expected_output.push_str(if i == PRINT_ELEMENTS_LIMIT * 2 + overflow - 1 { + "]" + } else { + "],\n " + }); + } + expected_output.push(']'); + let actual_output = format!("{}", a); + + print_output_diff(&expected_output, &actual_output); + assert_eq!(actual_output, expected_output); + } + + #[test] + fn dim_2_multi_directional_overflow() { + let overflow: usize = 5; + let a = Array2::from_elem( + (PRINT_ELEMENTS_LIMIT * 2 + overflow, PRINT_ELEMENTS_LIMIT * 2 + overflow), 1 + ); + let mut expected_output = String::from("["); + + for i in 0..PRINT_ELEMENTS_LIMIT { + expected_output.push_str(format!("[{}", a[(i, 0)]).as_str()); + for j in 1..PRINT_ELEMENTS_LIMIT { + expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); + } + expected_output.push_str(", ..."); + for j in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow { + expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); + } + expected_output.push_str("],\n "); + } + expected_output.push_str("...,\n "); + for i in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow { + expected_output.push_str(format!("[{}", a[(i, 0)]).as_str()); + for j in 1..PRINT_ELEMENTS_LIMIT { + expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); + } + expected_output.push_str(", ..."); + for j in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow { + expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); + } + expected_output.push_str(if i == PRINT_ELEMENTS_LIMIT * 2 + overflow - 1 { + "]" + } else { + "],\n " + }); + } + expected_output.push(']'); + let actual_output = format!("{}", a); + + print_output_diff(&expected_output, &actual_output); + assert_eq!(actual_output, expected_output); + } +} + #[test] fn debug_format() { let a = Array2::::zeros((3, 4)); From 0d11708851a973e469b548a6139c95c4267db019 Mon Sep 17 00:00:00 2001 From: andrei-papou Date: Wed, 27 Mar 2019 13:16:14 +0300 Subject: [PATCH 16/56] Try to fix 1.31 build failing --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 846d01452..d8032be7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -149,8 +149,6 @@ mod array_serde; mod arrayformat; mod data_traits; -pub use arrayformat::PRINT_ELEMENTS_LIMIT; - pub use crate::aliases::*; #[allow(deprecated)] @@ -192,6 +190,7 @@ pub use crate::zip::{ }; pub use crate::layout::Layout; +pub use crate::arrayformat::PRINT_ELEMENTS_LIMIT; /// Implementation's prelude. Common types used everywhere. mod imp_prelude { From 18c868a8195d38a459ab008f887c9bc5029effa1 Mon Sep 17 00:00:00 2001 From: andrei-papou Date: Thu, 28 Mar 2019 12:04:05 +0300 Subject: [PATCH 17/56] PRINT_ELEMENTS_LIMIT is now private --- src/arrayformat.rs | 129 ++++++++++++++++++++++++++++++++++++++++++++- src/lib.rs | 1 - tests/format.rs | 128 +------------------------------------------- 3 files changed, 129 insertions(+), 129 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index bfdaf8068..a6028ebdd 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -15,7 +15,7 @@ use super::{ }; use crate::dimension::IntoDimension; -pub const PRINT_ELEMENTS_LIMIT: Ix = 3; +const PRINT_ELEMENTS_LIMIT: Ix = 3; fn format_array_v2(view: &ArrayBase, f: &mut fmt::Formatter, @@ -291,3 +291,130 @@ impl<'a, A: fmt::Binary, S, D: Dimension> fmt::Binary for ArrayBase format_array_v2(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT) } } + +#[cfg(test)] +mod formatting_with_omit { + use crate::prelude::*; + use super::*; + + fn print_output_diff(expected: &str, actual: &str) { + println!("Expected output:\n{}\nActual output:\n{}", expected, actual); + } + + #[test] + fn dim_1() { + let overflow: usize = 5; + let a = Array1::from_elem((PRINT_ELEMENTS_LIMIT * 2 + overflow, ), 1); + let mut expected_output = String::from("["); + a.iter() + .take(PRINT_ELEMENTS_LIMIT) + .for_each(|elem| { expected_output.push_str(format!("{}, ", elem).as_str()) }); + expected_output.push_str("..."); + a.iter() + .skip(PRINT_ELEMENTS_LIMIT + overflow) + .for_each(|elem| { expected_output.push_str(format!(", {}", elem).as_str()) }); + expected_output.push(']'); + let actual_output = format!("{}", a); + + print_output_diff(&expected_output, &actual_output); + assert_eq!(actual_output, expected_output); + } + + #[test] + fn dim_2_last_axis_overflow() { + let overflow: usize = 3; + let a = Array2::from_elem((PRINT_ELEMENTS_LIMIT, PRINT_ELEMENTS_LIMIT * 2 + overflow), 1); + let mut expected_output = String::from("["); + + for i in 0..PRINT_ELEMENTS_LIMIT { + expected_output.push_str(format!("[{}", a[(i, 0)]).as_str()); + for j in 1..PRINT_ELEMENTS_LIMIT { + expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); + } + expected_output.push_str(", ..."); + for j in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow { + expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); + } + expected_output.push_str(if i < PRINT_ELEMENTS_LIMIT - 1 { "],\n " } else { "]" }); + } + expected_output.push(']'); + let actual_output = format!("{}", a); + + print_output_diff(&expected_output, &actual_output); + assert_eq!(actual_output, expected_output); + } + + #[test] + fn dim_2_non_last_axis_overflow() { + let overflow: usize = 5; + let a = Array2::from_elem((PRINT_ELEMENTS_LIMIT * 2 + overflow, PRINT_ELEMENTS_LIMIT), 1); + let mut expected_output = String::from("["); + + for i in 0..PRINT_ELEMENTS_LIMIT { + expected_output.push_str(format!("[{}", a[(i, 0)]).as_str()); + for j in 1..PRINT_ELEMENTS_LIMIT { + expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); + } + expected_output.push_str("],\n "); + } + expected_output.push_str("...,\n "); + for i in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow { + expected_output.push_str(format!("[{}", a[(i, 0)]).as_str()); + for j in 1..PRINT_ELEMENTS_LIMIT { + expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); + } + expected_output.push_str(if i == PRINT_ELEMENTS_LIMIT * 2 + overflow - 1 { + "]" + } else { + "],\n " + }); + } + expected_output.push(']'); + let actual_output = format!("{}", a); + + print_output_diff(&expected_output, &actual_output); + assert_eq!(actual_output, expected_output); + } + + #[test] + fn dim_2_multi_directional_overflow() { + let overflow: usize = 5; + let a = Array2::from_elem( + (PRINT_ELEMENTS_LIMIT * 2 + overflow, PRINT_ELEMENTS_LIMIT * 2 + overflow), 1 + ); + let mut expected_output = String::from("["); + + for i in 0..PRINT_ELEMENTS_LIMIT { + expected_output.push_str(format!("[{}", a[(i, 0)]).as_str()); + for j in 1..PRINT_ELEMENTS_LIMIT { + expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); + } + expected_output.push_str(", ..."); + for j in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow { + expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); + } + expected_output.push_str("],\n "); + } + expected_output.push_str("...,\n "); + for i in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow { + expected_output.push_str(format!("[{}", a[(i, 0)]).as_str()); + for j in 1..PRINT_ELEMENTS_LIMIT { + expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); + } + expected_output.push_str(", ..."); + for j in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow { + expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); + } + expected_output.push_str(if i == PRINT_ELEMENTS_LIMIT * 2 + overflow - 1 { + "]" + } else { + "],\n " + }); + } + expected_output.push(']'); + let actual_output = format!("{}", a); + + print_output_diff(&expected_output, &actual_output); + assert_eq!(actual_output, expected_output); + } +} diff --git a/src/lib.rs b/src/lib.rs index d8032be7d..7be810f9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,7 +190,6 @@ pub use crate::zip::{ }; pub use crate::layout::Layout; -pub use crate::arrayformat::PRINT_ELEMENTS_LIMIT; /// Implementation's prelude. Common types used everywhere. mod imp_prelude { diff --git a/tests/format.rs b/tests/format.rs index 335326ae5..4dbc939cc 100644 --- a/tests/format.rs +++ b/tests/format.rs @@ -1,7 +1,7 @@ extern crate ndarray; use ndarray::prelude::*; -use ndarray::{rcarr1, PRINT_ELEMENTS_LIMIT}; +use ndarray::rcarr1; #[test] fn formatting() @@ -36,132 +36,6 @@ fn formatting() assert_eq!(s, "[01, ff, fe]"); } -#[cfg(test)] -mod formatting_with_omit { - use super::*; - - fn print_output_diff(expected: &str, actual: &str) { - println!("Expected output:\n{}\nActual output:\n{}", expected, actual); - } - - #[test] - fn dim_1() { - let overflow: usize = 5; - let a = Array1::from_elem((PRINT_ELEMENTS_LIMIT * 2 + overflow, ), 1); - let mut expected_output = String::from("["); - a.iter() - .take(PRINT_ELEMENTS_LIMIT) - .for_each(|elem| { expected_output.push_str(format!("{}, ", elem).as_str()) }); - expected_output.push_str("..."); - a.iter() - .skip(PRINT_ELEMENTS_LIMIT + overflow) - .for_each(|elem| { expected_output.push_str(format!(", {}", elem).as_str()) }); - expected_output.push(']'); - let actual_output = format!("{}", a); - - print_output_diff(&expected_output, &actual_output); - assert_eq!(actual_output, expected_output); - } - - #[test] - fn dim_2_last_axis_overflow() { - let overflow: usize = 3; - let a = Array2::from_elem((PRINT_ELEMENTS_LIMIT, PRINT_ELEMENTS_LIMIT * 2 + overflow), 1); - let mut expected_output = String::from("["); - - for i in 0..PRINT_ELEMENTS_LIMIT { - expected_output.push_str(format!("[{}", a[(i, 0)]).as_str()); - for j in 1..PRINT_ELEMENTS_LIMIT { - expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); - } - expected_output.push_str(", ..."); - for j in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow { - expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); - } - expected_output.push_str(if i < PRINT_ELEMENTS_LIMIT - 1 { "],\n " } else { "]" }); - } - expected_output.push(']'); - let actual_output = format!("{}", a); - - print_output_diff(&expected_output, &actual_output); - assert_eq!(actual_output, expected_output); - } - - #[test] - fn dim_2_non_last_axis_overflow() { - let overflow: usize = 5; - let a = Array2::from_elem((PRINT_ELEMENTS_LIMIT * 2 + overflow, PRINT_ELEMENTS_LIMIT), 1); - let mut expected_output = String::from("["); - - for i in 0..PRINT_ELEMENTS_LIMIT { - expected_output.push_str(format!("[{}", a[(i, 0)]).as_str()); - for j in 1..PRINT_ELEMENTS_LIMIT { - expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); - } - expected_output.push_str("],\n "); - } - expected_output.push_str("...,\n "); - for i in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow { - expected_output.push_str(format!("[{}", a[(i, 0)]).as_str()); - for j in 1..PRINT_ELEMENTS_LIMIT { - expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); - } - expected_output.push_str(if i == PRINT_ELEMENTS_LIMIT * 2 + overflow - 1 { - "]" - } else { - "],\n " - }); - } - expected_output.push(']'); - let actual_output = format!("{}", a); - - print_output_diff(&expected_output, &actual_output); - assert_eq!(actual_output, expected_output); - } - - #[test] - fn dim_2_multi_directional_overflow() { - let overflow: usize = 5; - let a = Array2::from_elem( - (PRINT_ELEMENTS_LIMIT * 2 + overflow, PRINT_ELEMENTS_LIMIT * 2 + overflow), 1 - ); - let mut expected_output = String::from("["); - - for i in 0..PRINT_ELEMENTS_LIMIT { - expected_output.push_str(format!("[{}", a[(i, 0)]).as_str()); - for j in 1..PRINT_ELEMENTS_LIMIT { - expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); - } - expected_output.push_str(", ..."); - for j in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow { - expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); - } - expected_output.push_str("],\n "); - } - expected_output.push_str("...,\n "); - for i in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow { - expected_output.push_str(format!("[{}", a[(i, 0)]).as_str()); - for j in 1..PRINT_ELEMENTS_LIMIT { - expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); - } - expected_output.push_str(", ..."); - for j in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow { - expected_output.push_str(format!(", {}", a[(i, j)]).as_str()); - } - expected_output.push_str(if i == PRINT_ELEMENTS_LIMIT * 2 + overflow - 1 { - "]" - } else { - "],\n " - }); - } - expected_output.push(']'); - let actual_output = format!("{}", a); - - print_output_diff(&expected_output, &actual_output); - assert_eq!(actual_output, expected_output); - } -} - #[test] fn debug_format() { let a = Array2::::zeros((3, 4)); From 2924f2ee3beaa4e7e7938c0f81824b4c783a576f Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 9 Apr 2019 05:37:22 +0300 Subject: [PATCH 18/56] Support zero-length `axis` in `.map_axis/_mut()` (#612) --- Cargo.toml | 1 + src/impl_methods.rs | 38 ++++++++++++++++++++++++-------------- tests/array.rs | 23 ++++++++++++++++++++++- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 58ede0171..3c416e497 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ serde = { version = "1.0", optional = true } defmac = "0.2" quickcheck = { version = "0.7.2", default-features = false } rawpointer = "0.1" +itertools = { version = "0.7.0", default-features = false, features = ["use_std"] } approx = "0.3" [features] diff --git a/src/impl_methods.rs b/src/impl_methods.rs index 444d98525..ce5a52e77 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -2102,13 +2102,18 @@ where { let view_len = self.len_of(axis); let view_stride = self.strides.axis(axis); - // use the 0th subview as a map to each 1d array view extended from - // the 0th element. - self.index_axis(axis, 0).map(|first_elt| { - unsafe { - mapping(ArrayView::new_(first_elt, Ix1(view_len), Ix1(view_stride))) - } - }) + if view_len == 0 { + let new_dim = self.dim.remove_axis(axis); + Array::from_shape_fn(new_dim, move |_| mapping(ArrayView::from(&[]))) + } else { + // use the 0th subview as a map to each 1d array view extended from + // the 0th element. + self.index_axis(axis, 0).map(|first_elt| { + unsafe { + mapping(ArrayView::new_(first_elt, Ix1(view_len), Ix1(view_stride))) + } + }) + } } /// Reduce the values along an axis into just one value, producing a new @@ -2130,12 +2135,17 @@ where { let view_len = self.len_of(axis); let view_stride = self.strides.axis(axis); - // use the 0th subview as a map to each 1d array view extended from - // the 0th element. - self.index_axis_mut(axis, 0).map_mut(|first_elt: &mut A| { - unsafe { - mapping(ArrayViewMut::new_(first_elt, Ix1(view_len), Ix1(view_stride))) - } - }) + if view_len == 0 { + let new_dim = self.dim.remove_axis(axis); + Array::from_shape_fn(new_dim, move |_| mapping(ArrayViewMut::from(&mut []))) + } else { + // use the 0th subview as a map to each 1d array view extended from + // the 0th element. + self.index_axis_mut(axis, 0).map_mut(|first_elt| { + unsafe { + mapping(ArrayViewMut::new_(first_elt, Ix1(view_len), Ix1(view_stride))) + } + }) + } } } diff --git a/tests/array.rs b/tests/array.rs index bbe05bbda..ef5b1058d 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -13,7 +13,7 @@ use ndarray::{ }; use ndarray::indices; use defmac::defmac; -use itertools::{enumerate, zip}; +use itertools::{enumerate, zip, Itertools}; macro_rules! assert_panics { ($body:expr) => { @@ -1833,6 +1833,27 @@ fn test_map_axis() { let c = a.map_axis(Axis(1), |view| view.sum()); let answer2 = arr1(&[6, 15, 24, 33]); assert_eq!(c, answer2); + + // Test zero-length axis case + let arr = Array3::::zeros((3, 0, 4)); + let mut counter = 0; + let result = arr.map_axis(Axis(1), |x| { + assert_eq!(x.shape(), &[0]); + counter += 1; + counter + }); + assert_eq!(result.shape(), &[3, 4]); + itertools::assert_equal(result.iter().cloned().sorted(), 1..=3 * 4); + + let mut arr = Array3::::zeros((3, 0, 4)); + let mut counter = 0; + let result = arr.map_axis_mut(Axis(1), |x| { + assert_eq!(x.shape(), &[0]); + counter += 1; + counter + }); + assert_eq!(result.shape(), &[3, 4]); + itertools::assert_equal(result.iter().cloned().sorted(), 1..=3 * 4); } #[test] From b36f56a6dbf6006712f5ba6978626106df6a11b0 Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Thu, 11 Apr 2019 00:13:35 +0200 Subject: [PATCH 19/56] Implement Zip::all() Test if every element of the iterator matches a predicate. This is useful, for instance, to test if two matrices are elementwise "similar" (within a given threshold) without modifying the matrices. --- src/zip/mod.rs | 16 ++++++++++++++++ tests/azip.rs | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/zip/mod.rs b/src/zip/mod.rs index b26e2aebb..5492505c0 100644 --- a/src/zip/mod.rs +++ b/src/zip/mod.rs @@ -738,6 +738,22 @@ macro_rules! map_impl { }) } + /// Tests if every element of the iterator matches a predicate. + /// + /// Returns `true` if `predicate` evaluates to `true` for all elements. + pub fn all(mut self, mut predicate: F) -> bool + where F: FnMut($($p::Item),*) -> bool + { + self.apply_core(true, move |_, args| { + let ($($p,)*) = args; + if predicate($($p),*) { + FoldWhile::Continue(true) + } else { + FoldWhile::Done(false) + } + }).into_inner() + } + expand_if!(@bool [$notlast] /// Include the producer `p` in the Zip. diff --git a/tests/azip.rs b/tests/azip.rs index a5e53e0e0..3364f3c67 100644 --- a/tests/azip.rs +++ b/tests/azip.rs @@ -271,3 +271,19 @@ fn test_indices_split_1() { } } } + +#[test] +fn test_zip_all() { + let a = Array::::zeros(62); + let b = Array::::ones(62); + assert_eq!(true, Zip::from(&a).and(&b).all(|&x, &y| x + y == 1.0)); + assert_eq!(false, Zip::from(&a).and(&b).all(|&x, &y| x == y)); +} + +#[test] +fn test_zip_all_empty_array() { + let a = Array::::zeros(0); + let b = Array::::ones(0); + assert_eq!(true, Zip::from(&a).and(&b).all(|&_x, &_y| true)); + assert_eq!(true, Zip::from(&a).and(&b).all(|&_x, &_y| false)); +} From 48275c40655d37bfb9cac48b75b99e9648a55e67 Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Sat, 13 Apr 2019 16:17:36 +0200 Subject: [PATCH 20/56] Enhance documentation and test cases for Zip::all() --- src/zip/mod.rs | 10 ++++++++++ tests/azip.rs | 3 +++ 2 files changed, 13 insertions(+) diff --git a/src/zip/mod.rs b/src/zip/mod.rs index 5492505c0..fc8b99c7d 100644 --- a/src/zip/mod.rs +++ b/src/zip/mod.rs @@ -741,6 +741,16 @@ macro_rules! map_impl { /// Tests if every element of the iterator matches a predicate. /// /// Returns `true` if `predicate` evaluates to `true` for all elements. + /// Returns `true` if the input arrays are empty. + /// + /// Example: + /// + /// ``` + /// use ndarray::{array, Zip}; + /// let a = array![1, 2, 3]; + /// let b = array![1, 4, 9]; + /// assert!(Zip::from(&a).and(&b).all(|&a, &b| a * a == b)); + /// ``` pub fn all(mut self, mut predicate: F) -> bool where F: FnMut($($p::Item),*) -> bool { diff --git a/tests/azip.rs b/tests/azip.rs index 3364f3c67..39a5d2991 100644 --- a/tests/azip.rs +++ b/tests/azip.rs @@ -276,8 +276,11 @@ fn test_indices_split_1() { fn test_zip_all() { let a = Array::::zeros(62); let b = Array::::ones(62); + let mut c = Array::::ones(62); + c[5] = 0.0; assert_eq!(true, Zip::from(&a).and(&b).all(|&x, &y| x + y == 1.0)); assert_eq!(false, Zip::from(&a).and(&b).all(|&x, &y| x == y)); + assert_eq!(false, Zip::from(&a).and(&c).all(|&x, &y| x + y == 1.0)); } #[test] From 3aa8abe2ccb1c3d6f5e5f764597b7ac12efc573e Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Tue, 16 Apr 2019 20:39:59 +0100 Subject: [PATCH 21/56] Remove dead code --- src/arrayformat.rs | 70 ---------------------------------------------- 1 file changed, 70 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index a6028ebdd..04c5f70b8 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -142,76 +142,6 @@ fn format_array_v2(view: &ArrayBase, Ok(()) } -#[allow(dead_code)] -fn format_array(view: &ArrayBase, f: &mut fmt::Formatter, - mut format: F) - -> fmt::Result - where F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result, - D: Dimension, - S: Data, -{ - let ndim = view.dim.slice().len(); - /* private nowadays - if ndim > 0 && f.width.is_none() { - f.width = Some(4) - } - */ - // None will be an empty iter. - let mut last_index = match view.dim.first_index() { - None => view.dim.clone(), - Some(ix) => ix, - }; - for _ in 0..ndim { - write!(f, "[")?; - } - let mut first = true; - // Simply use the indexed iterator, and take the index wraparounds - // as cues for when to add []'s and how many to add. - for (index, elt) in view.indexed_iter() { - let index = index.into_dimension(); - let take_n = if ndim == 0 { 1 } else { ndim - 1 }; - let mut update_index = false; - for (i, (a, b)) in index.slice() - .iter() - .take(take_n) - .zip(last_index.slice().iter()) - .enumerate() { - if a != b { - // New row. - // # of ['s needed - let n = ndim - i - 1; - for _ in 0..n { - write!(f, "]")?; - } - write!(f, ",")?; - write!(f, "\n")?; - for _ in 0..ndim - n { - write!(f, " ")?; - } - for _ in 0..n { - write!(f, "[")?; - } - first = true; - update_index = true; - break; - } - } - if !first { - write!(f, ", ")?; - } - first = false; - format(elt, f)?; - - if update_index { - last_index = index; - } - } - for _ in 0..ndim { - write!(f, "]")?; - } - Ok(()) -} - // NOTE: We can impl other fmt traits here /// Format the array using `Display` and apply the formatting parameters used /// to each element. From 17f86bb2c76e0c42123d58af18bbe753c7ba4ca5 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Tue, 16 Apr 2019 20:43:09 +0100 Subject: [PATCH 22/56] Rename format_array_v2 to format_array --- src/arrayformat.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index 04c5f70b8..e89d9e9b1 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -17,10 +17,10 @@ use crate::dimension::IntoDimension; const PRINT_ELEMENTS_LIMIT: Ix = 3; -fn format_array_v2(view: &ArrayBase, - f: &mut fmt::Formatter, - mut format: F, - limit: Ix) -> fmt::Result +fn format_array(view: &ArrayBase, + f: &mut fmt::Formatter, + mut format: F, + limit: Ix) -> fmt::Result where F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result, D: Dimension, S: Data, @@ -151,7 +151,7 @@ impl<'a, A: fmt::Display, S, D: Dimension> fmt::Display for ArrayBase where S: Data, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - format_array_v2(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT) + format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT) } } @@ -164,7 +164,7 @@ impl<'a, A: fmt::Debug, S, D: Dimension> fmt::Debug for ArrayBase { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Add extra information for Debug - format_array_v2(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT)?; + format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT)?; write!(f, " shape={:?}, strides={:?}, layout={:?}", self.shape(), self.strides(), layout=self.view().layout())?; match D::NDIM { @@ -183,7 +183,7 @@ impl<'a, A: fmt::LowerExp, S, D: Dimension> fmt::LowerExp for ArrayBase where S: Data, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - format_array_v2(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT) + format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT) } } @@ -195,7 +195,7 @@ impl<'a, A: fmt::UpperExp, S, D: Dimension> fmt::UpperExp for ArrayBase where S: Data, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - format_array_v2(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT) + format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT) } } /// Format the array using `LowerHex` and apply the formatting parameters used @@ -206,7 +206,7 @@ impl<'a, A: fmt::LowerHex, S, D: Dimension> fmt::LowerHex for ArrayBase where S: Data, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - format_array_v2(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT) + format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT) } } @@ -218,7 +218,7 @@ impl<'a, A: fmt::Binary, S, D: Dimension> fmt::Binary for ArrayBase where S: Data, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - format_array_v2(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT) + format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT) } } From d48c5a9265efaf4d9b570c863087d3d7211d5c22 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Tue, 16 Apr 2019 20:46:32 +0100 Subject: [PATCH 23/56] Simplify code using ndim --- src/arrayformat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index e89d9e9b1..440aa9828 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -37,7 +37,7 @@ fn format_array(view: &ArrayBase, .map(|(axis, _)| axis) .collect(); - let ndim = view.dim().into_dimension().slice().len(); + let ndim = view.ndim(); let nth_idx_max = view.shape().iter().last().unwrap(); // None will be an empty iter. From 6c636dc3f3381750f7165a9222cd07a08fb283eb Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Tue, 16 Apr 2019 20:47:33 +0100 Subject: [PATCH 24/56] Access last element directly --- src/arrayformat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index 440aa9828..174f9fce3 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -38,7 +38,7 @@ fn format_array(view: &ArrayBase, .collect(); let ndim = view.ndim(); - let nth_idx_max = view.shape().iter().last().unwrap(); + let nth_idx_max = view.shape()[ndim-1]; // None will be an empty iter. let mut last_index = match view.dim().into_dimension().first_index() { From 88d53e38bb48ae2ca27476bb84341983e05472cd Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Tue, 16 Apr 2019 21:48:06 +0100 Subject: [PATCH 25/56] Add a test to specify behaviour for zero-dimensional arrays --- src/arrayformat.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index 174f9fce3..286fb30f9 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -231,6 +231,15 @@ mod formatting_with_omit { println!("Expected output:\n{}\nActual output:\n{}", expected, actual); } + #[test] + fn dim_0() { + let element = 12; + let a = arr0(element); + let actual_output = format!("{}", a); + let expected_output = format!("{}", element); + assert_eq!(actual_output, expected_output); + } + #[test] fn dim_1() { let overflow: usize = 5; From b962a7bb1aed8b8e0eb4cf02a22924c86dd5a5dd Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Tue, 16 Apr 2019 21:55:33 +0100 Subject: [PATCH 26/56] Add a test for empty arrays --- src/arrayformat.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index 286fb30f9..8767248ca 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -231,6 +231,14 @@ mod formatting_with_omit { println!("Expected output:\n{}\nActual output:\n{}", expected, actual); } + #[test] + fn empty_arrays() { + let a: Array2 = arr2(&[[], []]); + let actual_output = format!("{}", a); + let expected_output = String::from("[[]]"); + assert_eq!(actual_output, expected_output); + } + #[test] fn dim_0() { let element = 12; From 29a8f7037c2812bb9855806ba45b76eef5d964a2 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Tue, 16 Apr 2019 21:59:13 +0100 Subject: [PATCH 27/56] Test zero-length axes --- src/arrayformat.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index 8767248ca..54fc27595 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -239,6 +239,14 @@ mod formatting_with_omit { assert_eq!(actual_output, expected_output); } + #[test] + fn zero_length_axes() { + let a = Array3::::zeros((3, 0, 4)); + let actual_output = format!("{}", a); + let expected_output = String::from("[[[]]]"); + assert_eq!(actual_output, expected_output); + } + #[test] fn dim_0() { let element = 12; From 27c4a92c18033d79233d4bde34091e67999a307e Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Tue, 16 Apr 2019 22:02:25 +0100 Subject: [PATCH 28/56] We already know that ndim > 0 --- src/arrayformat.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index 54fc27595..f11827b5a 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -58,7 +58,6 @@ fn format_array(view: &ArrayBase, // as cues for when to add []'s and how many to add. for (index, elt) in view.indexed_iter() { let index = index.into_dimension(); - let take_n = if ndim == 0 { 1 } else { ndim - 1 }; let mut update_index = false; let skip_row_for_axis = overflow_axes.iter() @@ -78,7 +77,7 @@ fn format_array(view: &ArrayBase, for (i, (a, b)) in index.slice() .iter() - .take(take_n) + .take(ndim-1) .zip(last_index.slice().iter()) .enumerate() { if a != b { From b535f28440a2c6fdcc8cd9fdfc5dd267cb6eb4a6 Mon Sep 17 00:00:00 2001 From: andrei-papou Date: Wed, 17 Apr 2019 13:52:21 +0300 Subject: [PATCH 29/56] Made the formatting logic more modular --- src/arrayformat.rs | 137 +++++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 66 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index f11827b5a..35a8a2a20 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -17,6 +17,42 @@ use crate::dimension::IntoDimension; const PRINT_ELEMENTS_LIMIT: Ix = 3; +fn get_overflow_axes(shape: &[Ix], limit: usize) -> Vec { + shape.iter() + .enumerate() + .rev() + .filter(|(_, axis_size)| **axis_size > 2 * limit) + .map(|(axis, _)| axis) + .collect() +} + +fn get_highest_axis_to_skip(overflow_axes: &Vec, + shape: &[Ix], + index: &[Ix], + limit: &usize) -> Option { + overflow_axes.iter() + .filter(|axis| { + if **axis == shape.len() - 1 { + return false + }; + let sa_idx_max = shape.iter().skip(**axis).next().unwrap(); + let sa_idx_val = index.iter().skip(**axis).next().unwrap(); + sa_idx_val >= limit && sa_idx_val < &(sa_idx_max - limit) + }) + .min() + .map(|v| *v) +} + +fn get_highest_changed_axis(index: &[Ix], prev_index: &[Ix]) -> Option { + index.iter() + .take(index.len() - 1) + .zip(prev_index.iter()) + .enumerate() + .filter(|(_, (a, b))| a != b) + .map(|(i, _)| i) + .next() +} + fn format_array(view: &ArrayBase, f: &mut fmt::Formatter, mut format: F, @@ -30,12 +66,7 @@ fn format_array(view: &ArrayBase, return format(view.iter().next().unwrap(), f) } - let overflow_axes: Vec = view.shape().iter() - .enumerate() - .rev() - .filter(|(_, axis_size)| **axis_size > 2 * limit) - .map(|(axis, _)| axis) - .collect(); + let overflow_axes: Vec = get_overflow_axes(view.shape(), limit); let ndim = view.ndim(); let nth_idx_max = view.shape()[ndim-1]; @@ -46,9 +77,6 @@ fn format_array(view: &ArrayBase, Some(ix) => ix, }; write!(f, "{}", "[".repeat(ndim))?; - let mut first = true; - // Shows if ellipses for vertical split were printed. - let mut printed_ellipses_v = false; // Shows if ellipses for horizontal split were printed. let mut printed_ellipses_h = vec![false; ndim]; // Shows if the row was printed for the first time after horizontal split. @@ -58,83 +86,60 @@ fn format_array(view: &ArrayBase, // as cues for when to add []'s and how many to add. for (index, elt) in view.indexed_iter() { let index = index.into_dimension(); - let mut update_index = false; - - let skip_row_for_axis = overflow_axes.iter() - .filter(|axis| { - if **axis == ndim - 1 { - return false - }; - let sa_idx_max = view.shape().iter().skip(**axis).next().unwrap(); - let sa_idx_val = index.slice().iter().skip(**axis).next().unwrap(); - sa_idx_val >= &limit && sa_idx_val < &(sa_idx_max - &limit) - }) - .min() - .map(|v| *v); - if let Some(_) = skip_row_for_axis { + + let skip_row_for_axis = get_highest_axis_to_skip( + &overflow_axes, + view.shape(), + index.slice(), + &limit + ); + if skip_row_for_axis.is_some() { no_rows_after_skip_yet = true; } - for (i, (a, b)) in index.slice() - .iter() - .take(ndim-1) - .zip(last_index.slice().iter()) - .enumerate() { - if a != b { - printed_ellipses_h.iter_mut().skip(i + 1).for_each(|e| { *e = false; }); - - if skip_row_for_axis.is_none() { - printed_ellipses_v = false; - // New row. - // # of ['s needed - let n = ndim - i - 1; - if !no_rows_after_skip_yet { - write!(f, "{}", "]".repeat(n))?; - writeln!(f, ",")?; - } - no_rows_after_skip_yet = false; - write!(f, "{}", " ".repeat(ndim - n))?; - write!(f, "{}", "[".repeat(n))?; - } else if !printed_ellipses_h[skip_row_for_axis.unwrap()] { - let ax = skip_row_for_axis.unwrap(); - let n = ndim - i - 1; + let max_changed_idx = get_highest_changed_axis(index.slice(), last_index.slice()); + if let Some(i) = max_changed_idx { + printed_ellipses_h.iter_mut().skip(i + 1).for_each(|e| { *e = false; }); + + if skip_row_for_axis.is_none() { + // New row. + // # of ['s needed + let n = ndim - i - 1; + if !no_rows_after_skip_yet { write!(f, "{}", "]".repeat(n))?; writeln!(f, ",")?; - write!(f, "{}", " ".repeat(ax + 1))?; - writeln!(f, "...,")?; - printed_ellipses_h[ax] = true; } - first = true; - update_index = true; - break; + no_rows_after_skip_yet = false; + write!(f, "{}", " ".repeat(ndim - n))?; + write!(f, "{}", "[".repeat(n))?; + } else if !printed_ellipses_h[skip_row_for_axis.unwrap()] { + let ax = skip_row_for_axis.unwrap(); + let n = ndim - i - 1; + write!(f, "{}", "]".repeat(n))?; + writeln!(f, ",")?; + write!(f, "{}", " ".repeat(ax + 1))?; + writeln!(f, "...,")?; + printed_ellipses_h[ax] = true; } + last_index = index.clone(); } if skip_row_for_axis.is_none() { - let mut print_elt = true; let nth_idx_op = index.slice().iter().last(); if overflow_axes.contains(&(ndim - 1)) { let nth_idx_val = nth_idx_op.unwrap(); if nth_idx_val >= &limit && nth_idx_val < &(nth_idx_max - &limit) { - print_elt = false; - if !printed_ellipses_v { + if nth_idx_val == &limit { write!(f, ", ...")?; - printed_ellipses_v = true; } + continue; } } - if print_elt { - if !first { - write!(f, ", ")?; - } - first = false; - format(elt, f)?; + if max_changed_idx.is_none() && !index.slice().iter().all(|x| *x == 0) { + write!(f, ", ")?; } - } - - if update_index { - last_index = index; + format(elt, f)?; } } write!(f, "{}", "]".repeat(ndim))?; From f566d8cfb997d7d114eb5f544117bed177f511d3 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 19 Apr 2019 23:33:00 +0300 Subject: [PATCH 30/56] Forward more iterator methods (#614) Forwarded some methods to `inner` iterator: - nth - collect - any - all - find - find_map - count - last - position --- src/iterators/mod.rs | 97 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/src/iterators/mod.rs b/src/iterators/mod.rs index b9aab1aa9..c7e153864 100644 --- a/src/iterators/mod.rs +++ b/src/iterators/mod.rs @@ -13,6 +13,7 @@ mod windows; mod lanes; pub mod iter; +use std::iter::FromIterator; use std::marker::PhantomData; use std::ptr; @@ -388,6 +389,54 @@ impl<'a, A, D: Dimension> Iterator for Iter<'a, A, D> { { either!(self.inner, iter => iter.fold(init, g)) } + + fn nth(&mut self, n: usize) -> Option { + either_mut!(self.inner, iter => iter.nth(n)) + } + + fn collect(self) -> B + where B: FromIterator + { + either!(self.inner, iter => iter.collect()) + } + + fn all(&mut self, f: F) -> bool + where F: FnMut(Self::Item) -> bool + { + either_mut!(self.inner, iter => iter.all(f)) + } + + fn any(&mut self, f: F) -> bool + where F: FnMut(Self::Item) -> bool + { + either_mut!(self.inner, iter => iter.any(f)) + } + + fn find

(&mut self, predicate: P) -> Option + where P: FnMut(&Self::Item) -> bool + { + either_mut!(self.inner, iter => iter.find(predicate)) + } + + fn find_map(&mut self, f: F) -> Option + where F: FnMut(Self::Item) -> Option + { + either_mut!(self.inner, iter => iter.find_map(f)) + } + + fn count(self) -> usize { + either!(self.inner, iter => iter.count()) + } + + fn last(self) -> Option { + either!(self.inner, iter => iter.last()) + } + + fn position

(&mut self, predicate: P) -> Option + where P: FnMut(Self::Item) -> bool, + { + either_mut!(self.inner, iter => iter.position(predicate)) + } } impl<'a, A> DoubleEndedIterator for Iter<'a, A, Ix1> { @@ -455,6 +504,54 @@ impl<'a, A, D: Dimension> Iterator for IterMut<'a, A, D> { { either!(self.inner, iter => iter.fold(init, g)) } + + fn nth(&mut self, n: usize) -> Option { + either_mut!(self.inner, iter => iter.nth(n)) + } + + fn collect(self) -> B + where B: FromIterator + { + either!(self.inner, iter => iter.collect()) + } + + fn all(&mut self, f: F) -> bool + where F: FnMut(Self::Item) -> bool + { + either_mut!(self.inner, iter => iter.all(f)) + } + + fn any(&mut self, f: F) -> bool + where F: FnMut(Self::Item) -> bool + { + either_mut!(self.inner, iter => iter.any(f)) + } + + fn find

(&mut self, predicate: P) -> Option + where P: FnMut(&Self::Item) -> bool + { + either_mut!(self.inner, iter => iter.find(predicate)) + } + + fn find_map(&mut self, f: F) -> Option + where F: FnMut(Self::Item) -> Option + { + either_mut!(self.inner, iter => iter.find_map(f)) + } + + fn count(self) -> usize { + either!(self.inner, iter => iter.count()) + } + + fn last(self) -> Option { + either!(self.inner, iter => iter.last()) + } + + fn position

(&mut self, predicate: P) -> Option + where P: FnMut(Self::Item) -> bool, + { + either_mut!(self.inner, iter => iter.position(predicate)) + } } impl<'a, A> DoubleEndedIterator for IterMut<'a, A, Ix1> { From 2b8dcf33be9448e467c23dd7ff89e345c7381225 Mon Sep 17 00:00:00 2001 From: Jim Turner Date: Fri, 19 Apr 2019 16:38:46 -0400 Subject: [PATCH 31/56] Add safety check for size after broadcasting (#613) --- src/impl_methods.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/impl_methods.rs b/src/impl_methods.rs index ce5a52e77..6192c54ef 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -1512,6 +1512,12 @@ where /// **Note:** Cannot be used for mutable iterators, since repeating /// elements would create aliasing pointers. fn upcast(to: &D, from: &E, stride: &E) -> Option { + // Make sure the product of non-zero axis lengths does not exceed + // `isize::MAX`. This is the only safety check we need to perform + // because all the other constraints of `ArrayBase` are guaranteed + // to be met since we're starting from a valid `ArrayBase`. + let _ = size_of_shape_checked(to).ok()?; + let mut new_stride = to.clone(); // begin at the back (the least significant dimension) // size of the axis has to either agree or `from` has to be 1 From 2a8a3b0918781476d882bb7077ca12ec4de0ca52 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sat, 20 Apr 2019 15:17:32 +0100 Subject: [PATCH 32/56] Simplify element access --- src/arrayformat.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index 35a8a2a20..0bd274f0f 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -31,13 +31,13 @@ fn get_highest_axis_to_skip(overflow_axes: &Vec, index: &[Ix], limit: &usize) -> Option { overflow_axes.iter() - .filter(|axis| { - if **axis == shape.len() - 1 { + .filter(|&axis| { + if *axis == shape.len() - 1 { return false }; - let sa_idx_max = shape.iter().skip(**axis).next().unwrap(); - let sa_idx_val = index.iter().skip(**axis).next().unwrap(); - sa_idx_val >= limit && sa_idx_val < &(sa_idx_max - limit) + let sa_idx_max = shape[*axis]; + let sa_idx_val = index[*axis]; + sa_idx_val >= *limit && sa_idx_val < sa_idx_max - *limit }) .min() .map(|v| *v) From 5fd9b2f77d7f447356fd1aa225296309ddd57449 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sat, 20 Apr 2019 17:03:14 +0100 Subject: [PATCH 33/56] Almost there --- src/arrayformat.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index 0bd274f0f..9b238aeeb 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -8,12 +8,14 @@ use std::fmt; use super::{ ArrayBase, + Axis, Data, Dimension, NdProducer, Ix }; use crate::dimension::IntoDimension; +use crate::aliases::ArrayViewD; const PRINT_ELEMENTS_LIMIT: Ix = 3; @@ -53,6 +55,73 @@ fn get_highest_changed_axis(index: &[Ix], prev_index: &[Ix]) -> Option { .next() } +fn format_1d_array( + view: &ArrayBase, + f: &mut fmt::Formatter, + mut format: F, + limit: Ix) -> fmt::Result + where + F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result, + D: Dimension, + S: Data, +{ + unimplemented!() +} + +fn format_multidimensional_array( + view: &ArrayBase, + f: &mut fmt::Formatter, + mut format: F, + limit: Ix) -> fmt::Result + where + F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result, + D: Dimension, + S: Data, +{ + unimplemented!() +} + +fn format_array_v2( + view: &ArrayBase, + f: &mut fmt::Formatter, + mut format: F, + limit: Ix) -> fmt::Result +where + F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result, + D: Dimension, + S: Data, +{ + let view = view.view().into_dyn(); + match view.shape() { + [] => format(view.iter().next().unwrap(), f)?, + [_] => format_1d_array(&view, f, format, limit)?, + shape => { + let first_axis_length = shape[0]; + let indexes_to_be_printed: Vec> = if first_axis_length <= 2 * limit { + (0..first_axis_length).map(|x| Some(x)).collect() + } else { + let mut v: Vec> = (0..limit).map(|x| Some(x)).collect(); + v.push(None); + v.extend((first_axis_length-limit..first_axis_length).map(|x| Some(x))); + v + }; + write!(f, "[")?; + for index in indexes_to_be_printed { + match index { + Some(i) => format_array_v2( + &view.index_axis(Axis(0), i), f, format, limit + )?, + None => { + writeln!(f, "...,")? + } + } + } + write!(f, "]")?; + } + } + Ok(()) +} + fn format_array(view: &ArrayBase, f: &mut fmt::Formatter, mut format: F, From f60185ce59415de3e008f9ef4e36f238c127855e Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sat, 20 Apr 2019 17:07:57 +0100 Subject: [PATCH 34/56] Clone for the win --- src/arrayformat.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index 9b238aeeb..32bf90286 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -87,7 +87,7 @@ fn format_array_v2( mut format: F, limit: Ix) -> fmt::Result where - F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result, + F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result + Clone, D: Dimension, S: Data, { @@ -109,7 +109,7 @@ where for index in indexes_to_be_printed { match index { Some(i) => format_array_v2( - &view.index_axis(Axis(0), i), f, format, limit + &view.index_axis(Axis(0), i), f, format.clone(), limit )?, None => { writeln!(f, "...,")? From 198d6899ac83347836143d921d10429bd3d3d4c7 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sat, 20 Apr 2019 17:11:49 +0100 Subject: [PATCH 35/56] Cast to 1d array --- src/arrayformat.rs | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index 32bf90286..a0a4b0c2c 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -15,7 +15,7 @@ use super::{ Ix }; use crate::dimension::IntoDimension; -use crate::aliases::ArrayViewD; +use crate::aliases::Ix1; const PRINT_ELEMENTS_LIMIT: Ix = 3; @@ -55,27 +55,13 @@ fn get_highest_changed_axis(index: &[Ix], prev_index: &[Ix]) -> Option { .next() } -fn format_1d_array( - view: &ArrayBase, - f: &mut fmt::Formatter, - mut format: F, - limit: Ix) -> fmt::Result - where - F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result, - D: Dimension, - S: Data, -{ - unimplemented!() -} - -fn format_multidimensional_array( - view: &ArrayBase, +fn format_1d_array( + view: &ArrayBase, f: &mut fmt::Formatter, mut format: F, limit: Ix) -> fmt::Result where F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result, - D: Dimension, S: Data, { unimplemented!() @@ -94,7 +80,7 @@ where let view = view.view().into_dyn(); match view.shape() { [] => format(view.iter().next().unwrap(), f)?, - [_] => format_1d_array(&view, f, format, limit)?, + [_] => format_1d_array(&view.into_dimensionality::().unwrap(), f, format, limit)?, shape => { let first_axis_length = shape[0]; let indexes_to_be_printed: Vec> = if first_axis_length <= 2 * limit { From c1036fb273e017bce2d138b562d02020af531aeb Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sat, 20 Apr 2019 17:30:04 +0100 Subject: [PATCH 36/56] First and last elements have to be special cased. Done for multidimensional --- src/arrayformat.rs | 167 ++++++++------------------------------------- 1 file changed, 29 insertions(+), 138 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index a0a4b0c2c..d0dec54ed 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -14,47 +14,10 @@ use super::{ NdProducer, Ix }; -use crate::dimension::IntoDimension; use crate::aliases::Ix1; const PRINT_ELEMENTS_LIMIT: Ix = 3; -fn get_overflow_axes(shape: &[Ix], limit: usize) -> Vec { - shape.iter() - .enumerate() - .rev() - .filter(|(_, axis_size)| **axis_size > 2 * limit) - .map(|(axis, _)| axis) - .collect() -} - -fn get_highest_axis_to_skip(overflow_axes: &Vec, - shape: &[Ix], - index: &[Ix], - limit: &usize) -> Option { - overflow_axes.iter() - .filter(|&axis| { - if *axis == shape.len() - 1 { - return false - }; - let sa_idx_max = shape[*axis]; - let sa_idx_val = index[*axis]; - sa_idx_val >= *limit && sa_idx_val < sa_idx_max - *limit - }) - .min() - .map(|v| *v) -} - -fn get_highest_changed_axis(index: &[Ix], prev_index: &[Ix]) -> Option { - index.iter() - .take(index.len() - 1) - .zip(prev_index.iter()) - .enumerate() - .filter(|(_, (a, b))| a != b) - .map(|(i, _)| i) - .next() -} - fn format_1d_array( view: &ArrayBase, f: &mut fmt::Formatter, @@ -64,10 +27,27 @@ fn format_1d_array( F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result, S: Data, { - unimplemented!() + let n = view.len(); + let indexes_to_be_printed: Vec> = if n <= 2 * limit { + (0..n).map(|x| Some(x)).collect() + } else { + let mut v: Vec> = (0..limit).map(|x| Some(x)).collect(); + v.push(None); + v.extend((n-limit..n).map(|x| Some(x))); + v + }; + write!(f, "[")?; + for index in indexes_to_be_printed { + match index { + Some(i) => format(&view[i], f)?, + None => write!(f, ", ..., ")?, + } + } + write!(f, "]")?; + Ok(()) } -fn format_array_v2( +fn format_array( view: &ArrayBase, f: &mut fmt::Formatter, mut format: F, @@ -91,113 +71,24 @@ where v.extend((first_axis_length-limit..first_axis_length).map(|x| Some(x))); v }; - write!(f, "[")?; + writeln!(f, "[")?; for index in indexes_to_be_printed { match index { - Some(i) => format_array_v2( - &view.index_axis(Axis(0), i), f, format.clone(), limit - )?, + Some(i) => { + write!(f, " ")?; + format_array( + &view.index_axis(Axis(0), i), f, format.clone(), limit + )?; + writeln!(f, ",")? + }, None => { - writeln!(f, "...,")? + writeln!(f, " ...,")? } } } - write!(f, "]")?; - } - } - Ok(()) -} - -fn format_array(view: &ArrayBase, - f: &mut fmt::Formatter, - mut format: F, - limit: Ix) -> fmt::Result - where F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result, - D: Dimension, - S: Data, -{ - if view.shape().is_empty() { - // Handle 0-dimensional array case first - return format(view.iter().next().unwrap(), f) - } - - let overflow_axes: Vec = get_overflow_axes(view.shape(), limit); - - let ndim = view.ndim(); - let nth_idx_max = view.shape()[ndim-1]; - - // None will be an empty iter. - let mut last_index = match view.dim().into_dimension().first_index() { - None => view.dim().into_dimension().clone(), - Some(ix) => ix, - }; - write!(f, "{}", "[".repeat(ndim))?; - // Shows if ellipses for horizontal split were printed. - let mut printed_ellipses_h = vec![false; ndim]; - // Shows if the row was printed for the first time after horizontal split. - let mut no_rows_after_skip_yet = false; - - // Simply use the indexed iterator, and take the index wraparounds - // as cues for when to add []'s and how many to add. - for (index, elt) in view.indexed_iter() { - let index = index.into_dimension(); - - let skip_row_for_axis = get_highest_axis_to_skip( - &overflow_axes, - view.shape(), - index.slice(), - &limit - ); - if skip_row_for_axis.is_some() { - no_rows_after_skip_yet = true; - } - - let max_changed_idx = get_highest_changed_axis(index.slice(), last_index.slice()); - if let Some(i) = max_changed_idx { - printed_ellipses_h.iter_mut().skip(i + 1).for_each(|e| { *e = false; }); - - if skip_row_for_axis.is_none() { - // New row. - // # of ['s needed - let n = ndim - i - 1; - if !no_rows_after_skip_yet { - write!(f, "{}", "]".repeat(n))?; - writeln!(f, ",")?; - } - no_rows_after_skip_yet = false; - write!(f, "{}", " ".repeat(ndim - n))?; - write!(f, "{}", "[".repeat(n))?; - } else if !printed_ellipses_h[skip_row_for_axis.unwrap()] { - let ax = skip_row_for_axis.unwrap(); - let n = ndim - i - 1; - write!(f, "{}", "]".repeat(n))?; - writeln!(f, ",")?; - write!(f, "{}", " ".repeat(ax + 1))?; - writeln!(f, "...,")?; - printed_ellipses_h[ax] = true; - } - last_index = index.clone(); - } - - if skip_row_for_axis.is_none() { - let nth_idx_op = index.slice().iter().last(); - if overflow_axes.contains(&(ndim - 1)) { - let nth_idx_val = nth_idx_op.unwrap(); - if nth_idx_val >= &limit && nth_idx_val < &(nth_idx_max - &limit) { - if nth_idx_val == &limit { - write!(f, ", ...")?; - } - continue; - } - } - - if max_changed_idx.is_none() && !index.slice().iter().all(|x| *x == 0) { - write!(f, ", ")?; - } - format(elt, f)?; + writeln!(f, "]")?; } } - write!(f, "{}", "]".repeat(ndim))?; Ok(()) } From 5336b1065db9b1f2434beab36ca23b0cb83a2a14 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sun, 21 Apr 2019 09:56:13 +0100 Subject: [PATCH 37/56] Extract in a separate function --- src/arrayformat.rs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index d0dec54ed..1ebbc76a3 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -28,14 +28,7 @@ fn format_1d_array( S: Data, { let n = view.len(); - let indexes_to_be_printed: Vec> = if n <= 2 * limit { - (0..n).map(|x| Some(x)).collect() - } else { - let mut v: Vec> = (0..limit).map(|x| Some(x)).collect(); - v.push(None); - v.extend((n-limit..n).map(|x| Some(x))); - v - }; + let indexes_to_be_printed = indexes_to_be_printed(n, limit); write!(f, "[")?; for index in indexes_to_be_printed { match index { @@ -47,6 +40,17 @@ fn format_1d_array( Ok(()) } +fn indexes_to_be_printed(length: usize, limit: usize) -> Vec> { + if length <= 2 * limit { + (0..length).map(|x| Some(x)).collect() + } else { + let mut v: Vec> = (0..limit).map(|x| Some(x)).collect(); + v.push(None); + v.extend((length-limit..length).map(|x| Some(x))); + v + } +} + fn format_array( view: &ArrayBase, f: &mut fmt::Formatter, @@ -63,14 +67,7 @@ where [_] => format_1d_array(&view.into_dimensionality::().unwrap(), f, format, limit)?, shape => { let first_axis_length = shape[0]; - let indexes_to_be_printed: Vec> = if first_axis_length <= 2 * limit { - (0..first_axis_length).map(|x| Some(x)).collect() - } else { - let mut v: Vec> = (0..limit).map(|x| Some(x)).collect(); - v.push(None); - v.extend((first_axis_length-limit..first_axis_length).map(|x| Some(x))); - v - }; + let indexes_to_be_printed = indexes_to_be_printed(first_axis_length, limit); writeln!(f, "[")?; for index in indexes_to_be_printed { match index { From 4fa8a62d6295017e10549bbdc652e91841d1a27a Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sun, 21 Apr 2019 15:56:34 +0100 Subject: [PATCH 38/56] Tests are passing --- src/arrayformat.rs | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index 1ebbc76a3..b6018734d 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -29,11 +29,17 @@ fn format_1d_array( { let n = view.len(); let indexes_to_be_printed = indexes_to_be_printed(n, limit); + let last_index = indexes_to_be_printed.len(); write!(f, "[")?; - for index in indexes_to_be_printed { + for (j, index) in indexes_to_be_printed.into_iter().enumerate() { match index { - Some(i) => format(&view[i], f)?, - None => write!(f, ", ..., ")?, + Some(i) => { + format(&view[i], f)?; + if j != (last_index-1) { + write!(f, ", ")?; + } + }, + None => write!(f, "..., ")?, } } write!(f, "]")?; @@ -61,29 +67,35 @@ where D: Dimension, S: Data, { - let view = view.view().into_dyn(); + if view.shape().iter().any(|&x| x == 0) { + write!(f, "{}{}", "[".repeat(view.ndim()), "]".repeat(view.ndim()))?; + return Ok(()) + } match view.shape() { [] => format(view.iter().next().unwrap(), f)?, - [_] => format_1d_array(&view.into_dimensionality::().unwrap(), f, format, limit)?, + [_] => format_1d_array(&view.view().into_dimensionality::().unwrap(), f, format, limit)?, shape => { + let view = view.view().into_dyn(); let first_axis_length = shape[0]; let indexes_to_be_printed = indexes_to_be_printed(first_axis_length, limit); - writeln!(f, "[")?; - for index in indexes_to_be_printed { + let n_to_be_printed = indexes_to_be_printed.len(); + write!(f, "[")?; + for (j, index) in indexes_to_be_printed.into_iter().enumerate() { match index { Some(i) => { - write!(f, " ")?; format_array( &view.index_axis(Axis(0), i), f, format.clone(), limit )?; - writeln!(f, ",")? + if j != (n_to_be_printed -1) { + write!(f, ",\n ")? + } }, None => { - writeln!(f, " ...,")? + write!(f, "...,\n ")? } } } - writeln!(f, "]")?; + write!(f, "]")?; } } Ok(()) @@ -183,7 +195,8 @@ mod formatting_with_omit { let a: Array2 = arr2(&[[], []]); let actual_output = format!("{}", a); let expected_output = String::from("[[]]"); - assert_eq!(actual_output, expected_output); + print_output_diff(&expected_output, &actual_output); + assert_eq!(expected_output, actual_output); } #[test] @@ -191,7 +204,8 @@ mod formatting_with_omit { let a = Array3::::zeros((3, 0, 4)); let actual_output = format!("{}", a); let expected_output = String::from("[[[]]]"); - assert_eq!(actual_output, expected_output); + print_output_diff(&expected_output, &actual_output); + assert_eq!(expected_output, actual_output); } #[test] @@ -200,7 +214,8 @@ mod formatting_with_omit { let a = arr0(element); let actual_output = format!("{}", a); let expected_output = format!("{}", element); - assert_eq!(actual_output, expected_output); + print_output_diff(&expected_output, &actual_output); + assert_eq!(expected_output, actual_output); } #[test] From e659390e57827c1191d887ece1a21f542ed4db93 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sun, 21 Apr 2019 16:10:01 +0100 Subject: [PATCH 39/56] Minor improvements --- src/arrayformat.rs | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index b6018734d..a7e87f32f 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -28,14 +28,15 @@ fn format_1d_array( S: Data, { let n = view.len(); - let indexes_to_be_printed = indexes_to_be_printed(n, limit); - let last_index = indexes_to_be_printed.len(); + let to_be_printed = to_be_printed(n, limit); + let n_to_be_printed = to_be_printed.len(); + let is_last = |j| j == n_to_be_printed - 1; write!(f, "[")?; - for (j, index) in indexes_to_be_printed.into_iter().enumerate() { + for (j, index) in to_be_printed.into_iter().enumerate() { match index { Some(i) => { format(&view[i], f)?; - if j != (last_index-1) { + if !is_last(j) { write!(f, ", ")?; } }, @@ -46,7 +47,10 @@ fn format_1d_array( Ok(()) } -fn indexes_to_be_printed(length: usize, limit: usize) -> Vec> { +// Returns what indexes should be printed for a certain axis. +// If the axis is longer than 2 * limit, a `None` is inserted +// where indexes are being omitted. +fn to_be_printed(length: usize, limit: usize) -> Vec> { if length <= 2 * limit { (0..length).map(|x| Some(x)).collect() } else { @@ -67,32 +71,44 @@ where D: Dimension, S: Data, { + // If any of the axes has 0 length, we return the same empty array representation + // e.g. [[]] for 2-d arrays if view.shape().iter().any(|&x| x == 0) { write!(f, "{}{}", "[".repeat(view.ndim()), "]".repeat(view.ndim()))?; return Ok(()) } match view.shape() { + // If it's 0 dimensional, we just print out the scalar [] => format(view.iter().next().unwrap(), f)?, + // We delegate 1-dimensional arrays to a specialized function [_] => format_1d_array(&view.view().into_dimensionality::().unwrap(), f, format, limit)?, + // For n-dimensional arrays, we proceed recursively shape => { + // Cast into a dynamically dimensioned view + // This is required to be able to use `index_axis` let view = view.view().into_dyn(); - let first_axis_length = shape[0]; - let indexes_to_be_printed = indexes_to_be_printed(first_axis_length, limit); - let n_to_be_printed = indexes_to_be_printed.len(); + // We start by checking what indexes from the first axis should be printed + // We put a `None` in the middle if we are omitting elements + let to_be_printed = to_be_printed(shape[0], limit); + + let n_to_be_printed = to_be_printed.len(); + let is_last = |j| j == n_to_be_printed - 1; + write!(f, "[")?; - for (j, index) in indexes_to_be_printed.into_iter().enumerate() { + for (j, index) in to_be_printed.into_iter().enumerate() { match index { Some(i) => { + // Proceed recursively with the (n-1)-dimensional slice format_array( &view.index_axis(Axis(0), i), f, format.clone(), limit )?; - if j != (n_to_be_printed -1) { + // We need to add a separator after each slice, + // apart from the last one + if !is_last(j) { write!(f, ",\n ")? } }, - None => { - write!(f, "...,\n ")? - } + None => write!(f, "...,\n ")? } } write!(f, "]")?; From 0abff2ad521b115d519ae32f3f9abe957e3717f0 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sun, 21 Apr 2019 16:12:42 +0100 Subject: [PATCH 40/56] Formatting --- src/arrayformat.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index a7e87f32f..c2af80ef7 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -27,10 +27,11 @@ fn format_1d_array( F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result, S: Data, { - let n = view.len(); - let to_be_printed = to_be_printed(n, limit); + let to_be_printed = to_be_printed(view.len(), limit); + let n_to_be_printed = to_be_printed.len(); let is_last = |j| j == n_to_be_printed - 1; + write!(f, "[")?; for (j, index) in to_be_printed.into_iter().enumerate() { match index { From e049abd1ddef2ae2e2ab9d50445ff04528d32270 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Tue, 23 Apr 2019 20:16:42 +0100 Subject: [PATCH 41/56] Remove closure --- src/arrayformat.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index c2af80ef7..7613e863b 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -30,14 +30,13 @@ fn format_1d_array( let to_be_printed = to_be_printed(view.len(), limit); let n_to_be_printed = to_be_printed.len(); - let is_last = |j| j == n_to_be_printed - 1; write!(f, "[")?; for (j, index) in to_be_printed.into_iter().enumerate() { match index { Some(i) => { format(&view[i], f)?; - if !is_last(j) { + if j != n_to_be_printed - 1 { write!(f, ", ")?; } }, @@ -93,7 +92,6 @@ where let to_be_printed = to_be_printed(shape[0], limit); let n_to_be_printed = to_be_printed.len(); - let is_last = |j| j == n_to_be_printed - 1; write!(f, "[")?; for (j, index) in to_be_printed.into_iter().enumerate() { @@ -105,7 +103,7 @@ where )?; // We need to add a separator after each slice, // apart from the last one - if !is_last(j) { + if j != n_to_be_printed - 1 { write!(f, ",\n ")? } }, From 83c9f08cbf332bff28f0f87b9642950fbacc8370 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Tue, 23 Apr 2019 20:22:11 +0100 Subject: [PATCH 42/56] Use custom enum --- src/arrayformat.rs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/arrayformat.rs b/src/arrayformat.rs index 7613e863b..894215d8c 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -34,29 +34,34 @@ fn format_1d_array( write!(f, "[")?; for (j, index) in to_be_printed.into_iter().enumerate() { match index { - Some(i) => { + PrintableCell::ElementIndex(i) => { format(&view[i], f)?; if j != n_to_be_printed - 1 { write!(f, ", ")?; } }, - None => write!(f, "..., ")?, + PrintableCell::Ellipses => write!(f, "..., ")?, } } write!(f, "]")?; Ok(()) } +enum PrintableCell { + ElementIndex(usize), + Ellipses, +} + // Returns what indexes should be printed for a certain axis. -// If the axis is longer than 2 * limit, a `None` is inserted +// If the axis is longer than 2 * limit, a `Ellipses` is inserted // where indexes are being omitted. -fn to_be_printed(length: usize, limit: usize) -> Vec> { +fn to_be_printed(length: usize, limit: usize) -> Vec { if length <= 2 * limit { - (0..length).map(|x| Some(x)).collect() + (0..length).map(|x| PrintableCell::ElementIndex(x)).collect() } else { - let mut v: Vec> = (0..limit).map(|x| Some(x)).collect(); - v.push(None); - v.extend((length-limit..length).map(|x| Some(x))); + let mut v: Vec = (0..limit).map(|x| PrintableCell::ElementIndex(x)).collect(); + v.push(PrintableCell::Ellipses); + v.extend((length-limit..length).map(|x| PrintableCell::ElementIndex(x))); v } } @@ -96,7 +101,7 @@ where write!(f, "[")?; for (j, index) in to_be_printed.into_iter().enumerate() { match index { - Some(i) => { + PrintableCell::ElementIndex(i) => { // Proceed recursively with the (n-1)-dimensional slice format_array( &view.index_axis(Axis(0), i), f, format.clone(), limit @@ -107,7 +112,7 @@ where write!(f, ",\n ")? } }, - None => write!(f, "...,\n ")? + PrintableCell::Ellipses => write!(f, "...,\n ")? } } write!(f, "]")?; From 3656ddfc104d147c201583c4ed471837f6437e95 Mon Sep 17 00:00:00 2001 From: Joshua Ellis Date: Sun, 5 May 2019 00:54:24 +1000 Subject: [PATCH 43/56] Add `logspace` and `geomspace` constructors (#617) * Add `logspace` constructor for Array1 Signed-off-by: JP-Ellis * Add logspace tests Signed-off-by: JP-Ellis * More extensive logspace tests Signed-off-by: JP-Ellis * Remove print statements in doc tests Signed-off-by: JP-Ellis * Add `geomspace` and make `logspace` compatible with NumPy Signed-off-by: JP-Ellis --- src/geomspace.rs | 173 +++++++++++++++++++++++++++++++++++++++ src/impl_constructors.rs | 59 +++++++++++-- src/iterators/mod.rs | 30 +++---- src/lib.rs | 9 +- src/logspace.rs | 145 ++++++++++++++++++++++++++++++++ 5 files changed, 393 insertions(+), 23 deletions(-) create mode 100644 src/geomspace.rs create mode 100644 src/logspace.rs diff --git a/src/geomspace.rs b/src/geomspace.rs new file mode 100644 index 000000000..f04d44889 --- /dev/null +++ b/src/geomspace.rs @@ -0,0 +1,173 @@ +// Copyright 2014-2016 bluss and ndarray developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +use num_traits::Float; + +/// An iterator of a sequence of geometrically spaced floats. +/// +/// Iterator element type is `F`. +pub struct Geomspace { + sign: F, + start: F, + step: F, + index: usize, + len: usize, +} + +impl Iterator for Geomspace +where + F: Float, +{ + type Item = F; + + #[inline] + fn next(&mut self) -> Option { + if self.index >= self.len { + None + } else { + // Calculate the value just like numpy.linspace does + let i = self.index; + self.index += 1; + let exponent = self.start + self.step * F::from(i).unwrap(); + Some(self.sign * exponent.exp()) + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let n = self.len - self.index; + (n, Some(n)) + } +} + +impl DoubleEndedIterator for Geomspace +where + F: Float, +{ + #[inline] + fn next_back(&mut self) -> Option { + if self.index >= self.len { + None + } else { + // Calculate the value just like numpy.linspace does + self.len -= 1; + let i = self.len; + let exponent = self.start + self.step * F::from(i).unwrap(); + Some(self.sign * exponent.exp()) + } + } +} + +impl ExactSizeIterator for Geomspace where Geomspace: Iterator {} + +/// An iterator of a sequence of geometrically spaced values. +/// +/// The `Geomspace` has `n` elements, where the first element is `a` and the +/// last element is `b`. +/// +/// Iterator element type is `F`, where `F` must be either `f32` or `f64`. +/// +/// **Panics** if the interval `[a, b]` contains zero (including the end points). +#[inline] +pub fn geomspace(a: F, b: F, n: usize) -> Geomspace +where + F: Float, +{ + assert!( + a != F::zero() && b != F::zero(), + "Start and/or end of geomspace cannot be zero.", + ); + assert!( + a.is_sign_negative() == b.is_sign_negative(), + "Logarithmic interval cannot cross 0." + ); + + let log_a = a.abs().ln(); + let log_b = b.abs().ln(); + let step = if n > 1 { + let nf: F = F::from(n).unwrap(); + (log_b - log_a) / (nf - F::one()) + } else { + F::zero() + }; + Geomspace { + sign: a.signum(), + start: log_a, + step: step, + index: 0, + len: n, + } +} + +#[cfg(test)] +mod tests { + use super::geomspace; + use crate::{arr1, Array1}; + + #[test] + fn valid() { + let array: Array1<_> = geomspace(1e0, 1e3, 4).collect(); + assert!(array.all_close(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); + + let array: Array1<_> = geomspace(1e3, 1e0, 4).collect(); + assert!(array.all_close(&arr1(&[1e3, 1e2, 1e1, 1e0]), 1e-5)); + + let array: Array1<_> = geomspace(-1e3, -1e0, 4).collect(); + assert!(array.all_close(&arr1(&[-1e3, -1e2, -1e1, -1e0]), 1e-5)); + + let array: Array1<_> = geomspace(-1e0, -1e3, 4).collect(); + assert!(array.all_close(&arr1(&[-1e0, -1e1, -1e2, -1e3]), 1e-5)); + } + + #[test] + fn iter_forward() { + let mut iter = geomspace(1.0f64, 1e3, 4); + + assert!(iter.size_hint() == (4, Some(4))); + + assert!((iter.next().unwrap() - 1e0).abs() < 1e-5); + assert!((iter.next().unwrap() - 1e1).abs() < 1e-5); + assert!((iter.next().unwrap() - 1e2).abs() < 1e-5); + assert!((iter.next().unwrap() - 1e3).abs() < 1e-5); + assert!(iter.next().is_none()); + + assert!(iter.size_hint() == (0, Some(0))); + } + + #[test] + fn iter_backward() { + let mut iter = geomspace(1.0f64, 1e3, 4); + + assert!(iter.size_hint() == (4, Some(4))); + + assert!((iter.next_back().unwrap() - 1e3).abs() < 1e-5); + assert!((iter.next_back().unwrap() - 1e2).abs() < 1e-5); + assert!((iter.next_back().unwrap() - 1e1).abs() < 1e-5); + assert!((iter.next_back().unwrap() - 1e0).abs() < 1e-5); + assert!(iter.next_back().is_none()); + + assert!(iter.size_hint() == (0, Some(0))); + } + + #[test] + #[should_panic] + fn zero_lower() { + geomspace(0.0, 1.0, 4); + } + + #[test] + #[should_panic] + fn zero_upper() { + geomspace(1.0, 0.0, 4); + } + + #[test] + #[should_panic] + fn zero_included() { + geomspace(-1.0, 1.0, 4); + } +} diff --git a/src/impl_constructors.rs b/src/impl_constructors.rs index 1c2b84439..a57dcfff8 100644 --- a/src/impl_constructors.rs +++ b/src/impl_constructors.rs @@ -10,18 +10,18 @@ //! //! -use num_traits::{Zero, One, Float}; +use num_traits::{Float, One, Zero}; use std::isize; use std::mem; -use crate::imp_prelude::*; -use crate::StrideShape; use crate::dimension; -use crate::linspace; use crate::error::{self, ShapeError}; -use crate::indices; +use crate::imp_prelude::*; use crate::indexes; +use crate::indices; use crate::iterators::{to_vec, to_vec_mapped}; +use crate::StrideShape; +use crate::{linspace, geomspace, logspace}; /// # Constructor Methods for Owned Arrays /// @@ -101,6 +101,55 @@ impl ArrayBase { Self::from_vec(to_vec(linspace::range(start, end, step))) } + + /// Create a one-dimensional array with `n` elements logarithmically spaced, + /// with the starting value being `base.powf(start)` and the final one being + /// `base.powf(end)`. `A` must be a floating point type. + /// + /// If `base` is negative, all values will be negative. + /// + /// **Panics** if the length is greater than `isize::MAX`. + /// + /// ```rust + /// use ndarray::{Array, arr1}; + /// + /// let array = Array::logspace(10.0, 0.0, 3.0, 4); + /// assert!(array.all_close(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); + /// + /// let array = Array::logspace(-10.0, 3.0, 0.0, 4); + /// assert!(array.all_close(&arr1(&[-1e3, -1e2, -1e1, -1e0]), 1e-5)); + /// ``` + pub fn logspace(base: A, start: A, end: A, n: usize) -> Self + where + A: Float, + { + Self::from_vec(to_vec(logspace::logspace(base, start, end, n))) + } + + /// Create a one-dimensional array from the inclusive interval `[start, + /// end]` with `n` elements geometrically spaced. `A` must be a floating + /// point type. + /// + /// The interval can be either all positive or all negative; however, it + /// cannot contain 0 (including the end points). + /// + /// **Panics** if `n` is greater than `isize::MAX`. + /// + /// ```rust + /// use ndarray::{Array, arr1}; + /// + /// let array = Array::geomspace(1e0, 1e3, 4); + /// assert!(array.all_close(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); + /// + /// let array = Array::geomspace(-1e3, -1e0, 4); + /// assert!(array.all_close(&arr1(&[-1e3, -1e2, -1e1, -1e0]), 1e-5)); + /// ``` + pub fn geomspace(start: A, end: A, n: usize) -> Self + where + A: Float, + { + Self::from_vec(to_vec(geomspace::geomspace(start, end, n))) + } } /// ## Constructor methods for two-dimensional arrays. diff --git a/src/iterators/mod.rs b/src/iterators/mod.rs index c7e153864..959d51092 100644 --- a/src/iterators/mod.rs +++ b/src/iterators/mod.rs @@ -1311,25 +1311,25 @@ send_sync_read_write!(ElementsBaseMut); /// (Trait used internally) An iterator that we trust /// to deliver exactly as many items as it said it would. -pub unsafe trait TrustedIterator { } +pub unsafe trait TrustedIterator {} -use std; -use crate::linspace::Linspace; -use crate::iter::IndicesIter; use crate::indexes::IndicesIterF; +use crate::iter::IndicesIter; +use crate::{geomspace::Geomspace, linspace::Linspace, logspace::Logspace}; +use std; -unsafe impl TrustedIterator for Linspace { } -unsafe impl<'a, A, D> TrustedIterator for Iter<'a, A, D> { } -unsafe impl<'a, A, D> TrustedIterator for IterMut<'a, A, D> { } -unsafe impl TrustedIterator for std::iter::Map - where I: TrustedIterator { } -unsafe impl<'a, A> TrustedIterator for slice::Iter<'a, A> { } -unsafe impl<'a, A> TrustedIterator for slice::IterMut<'a, A> { } -unsafe impl TrustedIterator for ::std::ops::Range { } +unsafe impl TrustedIterator for Geomspace {} +unsafe impl TrustedIterator for Linspace {} +unsafe impl TrustedIterator for Logspace {} +unsafe impl<'a, A, D> TrustedIterator for Iter<'a, A, D> {} +unsafe impl<'a, A, D> TrustedIterator for IterMut<'a, A, D> {} +unsafe impl TrustedIterator for std::iter::Map where I: TrustedIterator {} +unsafe impl<'a, A> TrustedIterator for slice::Iter<'a, A> {} +unsafe impl<'a, A> TrustedIterator for slice::IterMut<'a, A> {} +unsafe impl TrustedIterator for ::std::ops::Range {} // FIXME: These indices iter are dubious -- size needs to be checked up front. -unsafe impl TrustedIterator for IndicesIter where D: Dimension { } -unsafe impl TrustedIterator for IndicesIterF where D: Dimension { } - +unsafe impl TrustedIterator for IndicesIter where D: Dimension {} +unsafe impl TrustedIterator for IndicesIterF where D: Dimension {} /// Like Iterator::collect, but only for trusted length iterators pub fn to_vec(iter: I) -> Vec diff --git a/src/lib.rs b/src/lib.rs index 8b44c21d2..5cb59ae5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -167,15 +167,18 @@ mod free_functions; pub use crate::free_functions::*; pub use crate::iterators::iter; -#[macro_use] mod slice; -mod layout; +mod error; +mod geomspace; mod indexes; mod iterators; +mod layout; mod linalg_traits; mod linspace; +mod logspace; mod numeric_util; -mod error; mod shape_builder; +#[macro_use] +mod slice; mod stacking; #[macro_use] mod zip; diff --git a/src/logspace.rs b/src/logspace.rs new file mode 100644 index 000000000..fe62e9990 --- /dev/null +++ b/src/logspace.rs @@ -0,0 +1,145 @@ +// Copyright 2014-2016 bluss and ndarray developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +use num_traits::Float; + +/// An iterator of a sequence of logarithmically spaced number. +/// +/// Iterator element type is `F`. +pub struct Logspace { + sign: F, + base: F, + start: F, + step: F, + index: usize, + len: usize, +} + +impl Iterator for Logspace +where + F: Float, +{ + type Item = F; + + #[inline] + fn next(&mut self) -> Option { + if self.index >= self.len { + None + } else { + // Calculate the value just like numpy.linspace does + let i = self.index; + self.index += 1; + let exponent = self.start + self.step * F::from(i).unwrap(); + Some(self.sign * self.base.powf(exponent)) + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let n = self.len - self.index; + (n, Some(n)) + } +} + +impl DoubleEndedIterator for Logspace +where + F: Float, +{ + #[inline] + fn next_back(&mut self) -> Option { + if self.index >= self.len { + None + } else { + // Calculate the value just like numpy.linspace does + self.len -= 1; + let i = self.len; + let exponent = self.start + self.step * F::from(i).unwrap(); + Some(self.sign * self.base.powf(exponent)) + } + } +} + +impl ExactSizeIterator for Logspace where Logspace: Iterator {} + +/// An iterator of a sequence of logarithmically spaced number. +/// +/// The `Logspace` has `n` elements, where the first element is `base.powf(a)` +/// and the last element is `base.powf(b)`. If `base` is negative, this +/// iterator will return all negative values. +/// +/// Iterator element type is `F`, where `F` must be either `f32` or `f64`. +#[inline] +pub fn logspace(base: F, a: F, b: F, n: usize) -> Logspace +where + F: Float, +{ + let step = if n > 1 { + let nf: F = F::from(n).unwrap(); + (b - a) / (nf - F::one()) + } else { + F::zero() + }; + Logspace { + sign: base.signum(), + base: base.abs(), + start: a, + step: step, + index: 0, + len: n, + } +} + +#[cfg(test)] +mod tests { + use super::logspace; + use crate::{arr1, Array1}; + + #[test] + fn valid() { + let array: Array1<_> = logspace(10.0, 0.0, 3.0, 4).collect(); + assert!(array.all_close(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); + + let array: Array1<_> = logspace(10.0, 3.0, 0.0, 4).collect(); + assert!(array.all_close(&arr1(&[1e3, 1e2, 1e1, 1e0]), 1e-5)); + + let array: Array1<_> = logspace(-10.0, 3.0, 0.0, 4).collect(); + assert!(array.all_close(&arr1(&[-1e3, -1e2, -1e1, -1e0]), 1e-5)); + + let array: Array1<_> = logspace(-10.0, 0.0, 3.0, 4).collect(); + assert!(array.all_close(&arr1(&[-1e0, -1e1, -1e2, -1e3]), 1e-5)); + } + + #[test] + fn iter_forward() { + let mut iter = logspace(10.0f64, 0.0, 3.0, 4); + + assert!(iter.size_hint() == (4, Some(4))); + + assert!((iter.next().unwrap() - 1e0).abs() < 1e-5); + assert!((iter.next().unwrap() - 1e1).abs() < 1e-5); + assert!((iter.next().unwrap() - 1e2).abs() < 1e-5); + assert!((iter.next().unwrap() - 1e3).abs() < 1e-5); + assert!(iter.next().is_none()); + + assert!(iter.size_hint() == (0, Some(0))); + } + + #[test] + fn iter_backward() { + let mut iter = logspace(10.0f64, 0.0, 3.0, 4); + + assert!(iter.size_hint() == (4, Some(4))); + + assert!((iter.next_back().unwrap() - 1e3).abs() < 1e-5); + assert!((iter.next_back().unwrap() - 1e2).abs() < 1e-5); + assert!((iter.next_back().unwrap() - 1e1).abs() < 1e-5); + assert!((iter.next_back().unwrap() - 1e0).abs() < 1e-5); + assert!(iter.next_back().is_none()); + + assert!(iter.size_hint() == (0, Some(0))); + } +} From cbb07a46454ee1b73d80b6bbae08b8e4b7ff911a Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sat, 4 May 2019 17:09:24 +0100 Subject: [PATCH 44/56] Use Zip::all where possible --- src/array_approx.rs | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/src/array_approx.rs b/src/array_approx.rs index f8adeb09e..75f48600d 100644 --- a/src/array_approx.rs +++ b/src/array_approx.rs @@ -22,14 +22,7 @@ where } Zip::from(self) .and(other) - .fold_while(true, |_, a, b| { - if A::abs_diff_ne(a, b, epsilon.clone()) { - FoldWhile::Done(false) - } else { - FoldWhile::Continue(true) - } - }) - .into_inner() + .all(A::abs_diff_ne(a, b, epsilon.clone())) } } @@ -56,14 +49,7 @@ where } Zip::from(self) .and(other) - .fold_while(true, |_, a, b| { - if A::relative_ne(a, b, epsilon.clone(), max_relative.clone()) { - FoldWhile::Done(false) - } else { - FoldWhile::Continue(true) - } - }) - .into_inner() + .all(A::relative_ne(a, b, epsilon.clone(), max_relative.clone())) } } @@ -85,14 +71,7 @@ where } Zip::from(self) .and(other) - .fold_while(true, |_, a, b| { - if A::ulps_ne(a, b, epsilon.clone(), max_ulps) { - FoldWhile::Done(false) - } else { - FoldWhile::Continue(true) - } - }) - .into_inner() + .all(A::ulps_ne(a, b, epsilon.clone(), max_ulps)) } } From d21fe5cdf3af32e9703b9cd920b51d1cab4fb5ec Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sat, 4 May 2019 17:14:37 +0100 Subject: [PATCH 45/56] Mark `all_close` as deprecated --- src/numeric/impl_numeric.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/numeric/impl_numeric.rs b/src/numeric/impl_numeric.rs index 87e7be590..849bd2b65 100644 --- a/src/numeric/impl_numeric.rs +++ b/src/numeric/impl_numeric.rs @@ -306,6 +306,7 @@ impl ArrayBase /// If their shapes disagree, `rhs` is broadcast to the shape of `self`. /// /// **Panics** if broadcasting to the same shape isn’t possible. + #[deprecated(note="Use `abs_diff_eq` - it requires the `approx` crate feature", since="0.13")] pub fn all_close(&self, rhs: &ArrayBase, tol: A) -> bool where A: Float, S2: Data, From 5e309240d3db1a2d0d8c045cbc534b0fa80b1f34 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sat, 4 May 2019 17:25:02 +0100 Subject: [PATCH 46/56] Fix implementation. --- src/array_approx.rs | 8 ++++---- src/logspace.rs | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/array_approx.rs b/src/array_approx.rs index 75f48600d..100a7aa38 100644 --- a/src/array_approx.rs +++ b/src/array_approx.rs @@ -1,5 +1,5 @@ use crate::imp_prelude::*; -use crate::{FoldWhile, Zip}; +use crate::Zip; use approx::{AbsDiffEq, RelativeEq, UlpsEq}; /// **Requires crate feature `"approx"`** @@ -22,7 +22,7 @@ where } Zip::from(self) .and(other) - .all(A::abs_diff_ne(a, b, epsilon.clone())) + .all(|a, b| A::abs_diff_eq(a, b, epsilon.clone())) } } @@ -49,7 +49,7 @@ where } Zip::from(self) .and(other) - .all(A::relative_ne(a, b, epsilon.clone(), max_relative.clone())) + .all(|a, b| A::relative_eq(a, b, epsilon.clone(), max_relative.clone())) } } @@ -71,7 +71,7 @@ where } Zip::from(self) .and(other) - .all(A::ulps_ne(a, b, epsilon.clone(), max_ulps)) + .all(|a, b| A::ulps_eq(a, b, epsilon.clone(), max_ulps)) } } diff --git a/src/logspace.rs b/src/logspace.rs index fe62e9990..d27b8170b 100644 --- a/src/logspace.rs +++ b/src/logspace.rs @@ -97,11 +97,12 @@ where mod tests { use super::logspace; use crate::{arr1, Array1}; + use approx::AbsDiffEq; #[test] fn valid() { let array: Array1<_> = logspace(10.0, 0.0, 3.0, 4).collect(); - assert!(array.all_close(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); + assert!(array.abs_diff_eq(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); let array: Array1<_> = logspace(10.0, 3.0, 0.0, 4).collect(); assert!(array.all_close(&arr1(&[1e3, 1e2, 1e1, 1e0]), 1e-5)); From fa603767f0b77890301bb94935d2b2de7aadc055 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sat, 4 May 2019 17:41:30 +0100 Subject: [PATCH 47/56] Fix issues with conditional execution based on activated features --- src/geomspace.rs | 9 +++++---- src/impl_constructors.rs | 7 +++++-- src/logspace.rs | 8 ++++---- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/geomspace.rs b/src/geomspace.rs index f04d44889..93b73329b 100644 --- a/src/geomspace.rs +++ b/src/geomspace.rs @@ -109,18 +109,19 @@ mod tests { use crate::{arr1, Array1}; #[test] + #[cfg(approx)] fn valid() { let array: Array1<_> = geomspace(1e0, 1e3, 4).collect(); - assert!(array.all_close(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); + assert!(array.abs_diff_eq(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); let array: Array1<_> = geomspace(1e3, 1e0, 4).collect(); - assert!(array.all_close(&arr1(&[1e3, 1e2, 1e1, 1e0]), 1e-5)); + assert!(array.abs_diff_eq(&arr1(&[1e3, 1e2, 1e1, 1e0]), 1e-5)); let array: Array1<_> = geomspace(-1e3, -1e0, 4).collect(); - assert!(array.all_close(&arr1(&[-1e3, -1e2, -1e1, -1e0]), 1e-5)); + assert!(array.abs_diff_eq(&arr1(&[-1e3, -1e2, -1e1, -1e0]), 1e-5)); let array: Array1<_> = geomspace(-1e0, -1e3, 4).collect(); - assert!(array.all_close(&arr1(&[-1e0, -1e1, -1e2, -1e3]), 1e-5)); + assert!(array.abs_diff_eq(&arr1(&[-1e0, -1e1, -1e2, -1e3]), 1e-5)); } #[test] diff --git a/src/impl_constructors.rs b/src/impl_constructors.rs index a57dcfff8..66a70cf3f 100644 --- a/src/impl_constructors.rs +++ b/src/impl_constructors.rs @@ -112,12 +112,15 @@ impl ArrayBase /// /// ```rust /// use ndarray::{Array, arr1}; + /// use approx::AbsDiffEq; /// + /// # #[cfg(feature = "approx")] { /// let array = Array::logspace(10.0, 0.0, 3.0, 4); - /// assert!(array.all_close(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); + /// assert!(array.abs_diff_eq(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); /// /// let array = Array::logspace(-10.0, 3.0, 0.0, 4); - /// assert!(array.all_close(&arr1(&[-1e3, -1e2, -1e1, -1e0]), 1e-5)); + /// assert!(array.abs_diff_eq(&arr1(&[-1e3, -1e2, -1e1, -1e0]), 1e-5)); + /// # } /// ``` pub fn logspace(base: A, start: A, end: A, n: usize) -> Self where diff --git a/src/logspace.rs b/src/logspace.rs index d27b8170b..7aa6d11e2 100644 --- a/src/logspace.rs +++ b/src/logspace.rs @@ -97,21 +97,21 @@ where mod tests { use super::logspace; use crate::{arr1, Array1}; - use approx::AbsDiffEq; #[test] + #[cfg(approx)] fn valid() { let array: Array1<_> = logspace(10.0, 0.0, 3.0, 4).collect(); assert!(array.abs_diff_eq(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); let array: Array1<_> = logspace(10.0, 3.0, 0.0, 4).collect(); - assert!(array.all_close(&arr1(&[1e3, 1e2, 1e1, 1e0]), 1e-5)); + assert!(array.abs_diff_eq(&arr1(&[1e3, 1e2, 1e1, 1e0]), 1e-5)); let array: Array1<_> = logspace(-10.0, 3.0, 0.0, 4).collect(); - assert!(array.all_close(&arr1(&[-1e3, -1e2, -1e1, -1e0]), 1e-5)); + assert!(array.abs_diff_eq(&arr1(&[-1e3, -1e2, -1e1, -1e0]), 1e-5)); let array: Array1<_> = logspace(-10.0, 0.0, 3.0, 4).collect(); - assert!(array.all_close(&arr1(&[-1e0, -1e1, -1e2, -1e3]), 1e-5)); + assert!(array.abs_diff_eq(&arr1(&[-1e0, -1e1, -1e2, -1e3]), 1e-5)); } #[test] From 8fb8aa4edf9391efcf9cd243c446533771717afe Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sat, 4 May 2019 17:43:47 +0100 Subject: [PATCH 48/56] Remove all_close from all doc tests --- src/impl_constructors.rs | 7 +++++-- src/impl_methods.rs | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/impl_constructors.rs b/src/impl_constructors.rs index 66a70cf3f..fca687a0c 100644 --- a/src/impl_constructors.rs +++ b/src/impl_constructors.rs @@ -140,12 +140,15 @@ impl ArrayBase /// /// ```rust /// use ndarray::{Array, arr1}; + /// use approx::AbsDiffEq; /// + /// # #[cfg(feature = "approx")] { /// let array = Array::geomspace(1e0, 1e3, 4); - /// assert!(array.all_close(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); + /// assert!(array.abs_diff_eq(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); /// /// let array = Array::geomspace(-1e3, -1e0, 4); - /// assert!(array.all_close(&arr1(&[-1e3, -1e2, -1e1, -1e0]), 1e-5)); + /// assert!(array.abs_diff_eq(&arr1(&[-1e3, -1e2, -1e1, -1e0]), 1e-5)); + /// # } /// ``` pub fn geomspace(start: A, end: A, n: usize) -> Self where diff --git a/src/impl_methods.rs b/src/impl_methods.rs index 6192c54ef..5887bebcb 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -2040,14 +2040,17 @@ where /// /// ``` /// use ndarray::arr2; + /// use approx::AbsDiffEq; /// + /// # #[cfg(feature = "approx")] { /// let mut a = arr2(&[[ 0., 1.], /// [-1., 2.]]); /// a.mapv_inplace(f32::exp); /// assert!( - /// a.all_close(&arr2(&[[1.00000, 2.71828], - /// [0.36788, 7.38906]]), 1e-5) + /// a.abs_diff_eq(&arr2(&[[1.00000, 2.71828], + /// [0.36788, 7.38906]]), 1e-5) /// ); + /// # } /// ``` pub fn mapv_inplace(&mut self, mut f: F) where S: DataMut, From 3e47c5ca08c06964ea05be1ae6759428f825c0f3 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sat, 4 May 2019 17:48:03 +0100 Subject: [PATCH 49/56] Update guide for NumPy users --- src/doc/ndarray_for_numpy_users/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/ndarray_for_numpy_users/mod.rs b/src/doc/ndarray_for_numpy_users/mod.rs index b268c9b24..79acad788 100644 --- a/src/doc/ndarray_for_numpy_users/mod.rs +++ b/src/doc/ndarray_for_numpy_users/mod.rs @@ -473,7 +473,7 @@ //! //! //! -//! [`a.all_close(&b, 1e-8)`][.all_close()] +//! [`a.abs_diff_eq(&b, 1e-8)`][.abs_diff_eq()] //! //! //! @@ -557,7 +557,7 @@ //! `a[:,4]` | [`a.column(4)`][.column()] or [`a.column_mut(4)`][.column_mut()] | view (or mutable view) of column 4 in a 2-D array //! `a.shape[0] == a.shape[1]` | [`a.is_square()`][.is_square()] | check if the array is square //! -//! [.all_close()]: ../../struct.ArrayBase.html#method.all_close +//! [.abs_diff_eq()]: ../../struct.ArrayBase.html#impl-AbsDiffEq> //! [ArcArray]: ../../type.ArcArray.html //! [arr2()]: ../../fn.arr2.html //! [array!]: ../../macro.array.html From 9b831753a74f3bef35cca07789e1749e47e71ccd Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sat, 4 May 2019 17:52:06 +0100 Subject: [PATCH 50/56] Replace all_close with abs_diff_eq in tests (currently failing) --- tests/array.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/array.rs b/tests/array.rs index ef5b1058d..2c0f0be27 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -12,6 +12,7 @@ use ndarray::{ multislice, }; use ndarray::indices; +use approx::AbsDiffEq; use defmac::defmac; use itertools::{enumerate, zip, Itertools}; @@ -680,6 +681,7 @@ fn test_sub_oob_1() { #[test] +#[cfg(feature = "approx")] fn test_select(){ // test for 2-d array let x = arr2(&[[0., 1.], [1.,0.],[1.,0.],[1.,0.],[1.,0.],[0., 1.],[0., 1.]]); @@ -687,8 +689,8 @@ fn test_select(){ let c = x.select(Axis(1),&[1]); let r_target = arr2(&[[1.,0.],[1.,0.],[0., 1.]]); let c_target = arr2(&[[1.,0.,0.,0.,0., 1., 1.]]); - assert!(r.all_close(&r_target,1e-8)); - assert!(c.all_close(&c_target.t(),1e-8)); + assert!(r.abs_diff_eq(&r_target,1e-8)); + assert!(c.abs_diff_eq(&c_target.t(),1e-8)); // test for 3-d array let y = arr3(&[[[1., 2., 3.], @@ -699,8 +701,8 @@ fn test_select(){ let c = y.select(Axis(2),&[1]); let r_target = arr3(&[[[1.5, 1.5, 3.]], [[1., 2.5, 3.]]]); let c_target = arr3(&[[[2.],[1.5]],[[2.],[2.5]]]); - assert!(r.all_close(&r_target,1e-8)); - assert!(c.all_close(&c_target,1e-8)); + assert!(r.abs_diff_eq(&r_target,1e-8)); + assert!(c.abs_diff_eq(&c_target,1e-8)); } @@ -1733,13 +1735,14 @@ fn test_contiguous() { } #[test] +#[cfg(feature = "approx")] fn test_all_close() { let c = arr3(&[[[1., 2., 3.], [1.5, 1.5, 3.]], [[1., 2., 3.], [1., 2.5, 3.]]]); - assert!(c.all_close(&aview1(&[1., 2., 3.]), 1.)); - assert!(!c.all_close(&aview1(&[1., 2., 3.]), 0.1)); + assert!(c.abs_diff_eq(&aview1(&[1., 2., 3.]), 1.)); + assert!(c.abs_diff_neq(&aview1(&[1., 2., 3.]), 0.1)); } #[test] From e0f390d9d82445ce415bb006e87d9fe0549c563c Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sat, 4 May 2019 17:54:52 +0100 Subject: [PATCH 51/56] Fix typo, pin 0.3.2 to get latest changes --- Cargo.toml | 4 ++-- tests/array.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c75dc92d6..cd6939a33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ itertools = { version = "0.7.0", default-features = false } rayon = { version = "1.0.3", optional = true } -approx = { version = "0.3", optional = true } +approx = { version = "0.3.2", optional = true } # Use via the `blas` crate feature! cblas-sys = { version = "0.1.4", optional = true, default-features = false } @@ -50,7 +50,7 @@ defmac = "0.2" quickcheck = { version = "0.7.2", default-features = false } rawpointer = "0.1" itertools = { version = "0.7.0", default-features = false, features = ["use_std"] } -approx = "0.3" +approx = "0.3.2" [features] # Enable blas usage diff --git a/tests/array.rs b/tests/array.rs index 2c0f0be27..73eb08754 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -1742,7 +1742,7 @@ fn test_all_close() { [[1., 2., 3.], [1., 2.5, 3.]]]); assert!(c.abs_diff_eq(&aview1(&[1., 2., 3.]), 1.)); - assert!(c.abs_diff_neq(&aview1(&[1., 2., 3.]), 0.1)); + assert!(c.abs_diff_ne(&aview1(&[1., 2., 3.]), 0.1)); } #[test] From ac67ea5e9248afbf72b2e21270e3719ea16a3d68 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sun, 5 May 2019 10:18:58 +0100 Subject: [PATCH 52/56] Allow comparison between arrays with different ownership properties --- src/array_approx.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/array_approx.rs b/src/array_approx.rs index 100a7aa38..eb0a32fde 100644 --- a/src/array_approx.rs +++ b/src/array_approx.rs @@ -3,11 +3,12 @@ use crate::Zip; use approx::{AbsDiffEq, RelativeEq, UlpsEq}; /// **Requires crate feature `"approx"`** -impl AbsDiffEq for ArrayBase +impl AbsDiffEq> for ArrayBase where A: AbsDiffEq, A::Epsilon: Clone, S: Data, + T: Data, D: Dimension, { type Epsilon = A::Epsilon; @@ -16,7 +17,7 @@ where A::default_epsilon() } - fn abs_diff_eq(&self, other: &ArrayBase, epsilon: A::Epsilon) -> bool { + fn abs_diff_eq(&self, other: &ArrayBase, epsilon: A::Epsilon) -> bool { if self.shape() != other.shape() { return false; } @@ -27,11 +28,12 @@ where } /// **Requires crate feature `"approx"`** -impl RelativeEq for ArrayBase +impl RelativeEq> for ArrayBase where A: RelativeEq, A::Epsilon: Clone, S: Data, + T: Data, D: Dimension, { fn default_max_relative() -> A::Epsilon { @@ -40,7 +42,7 @@ where fn relative_eq( &self, - other: &ArrayBase, + other: &ArrayBase, epsilon: A::Epsilon, max_relative: A::Epsilon, ) -> bool { @@ -54,18 +56,19 @@ where } /// **Requires crate feature `"approx"`** -impl UlpsEq for ArrayBase +impl UlpsEq> for ArrayBase where A: UlpsEq, A::Epsilon: Clone, S: Data, + T: Data, D: Dimension, { fn default_max_ulps() -> u32 { A::default_max_ulps() } - fn ulps_eq(&self, other: &ArrayBase, epsilon: A::Epsilon, max_ulps: u32) -> bool { + fn ulps_eq(&self, other: &ArrayBase, epsilon: A::Epsilon, max_ulps: u32) -> bool { if self.shape() != other.shape() { return false; } From 35915b86588968dba84ce4dafe39e618153649a1 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sun, 5 May 2019 10:23:05 +0100 Subject: [PATCH 53/56] Fix assertions --- tests/array.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/array.rs b/tests/array.rs index 73eb08754..f9d42638d 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -1741,8 +1741,12 @@ fn test_all_close() { [1.5, 1.5, 3.]], [[1., 2., 3.], [1., 2.5, 3.]]]); - assert!(c.abs_diff_eq(&aview1(&[1., 2., 3.]), 1.)); - assert!(c.abs_diff_ne(&aview1(&[1., 2., 3.]), 0.1)); + assert!( + c.abs_diff_eq(&aview1(&[1., 2., 3.]).broadcast(c.raw_dim()).unwrap(), 1.) + ); + assert!( + c.abs_diff_ne(&aview1(&[1., 2., 3.]).broadcast(c.raw_dim()).unwrap(), 0.1) + ); } #[test] From 3d8196735616dfea5aeaf9864ad1c176ae0bb34d Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sun, 5 May 2019 10:24:39 +0100 Subject: [PATCH 54/56] Fix test --- tests/par_rayon.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/par_rayon.rs b/tests/par_rayon.rs index 70b69e0eb..6dda0a1d1 100644 --- a/tests/par_rayon.rs +++ b/tests/par_rayon.rs @@ -24,12 +24,13 @@ fn test_axis_iter() { } #[test] +#[cfg(features = "approx")] fn test_axis_iter_mut() { let mut a = Array::linspace(0., 1.0f64, M * N).into_shape((M, N)).unwrap(); let b = a.mapv(|x| x.exp()); a.axis_iter_mut(Axis(0)).into_par_iter().for_each(|mut v| v.mapv_inplace(|x| x.exp())); println!("{:?}", a.slice(s![..10, ..5])); - assert!(a.all_close(&b, 0.001)); + assert_abs_diff_eq!(a, &b, 0.001); } #[test] From cf646e2f2e78a3e547dc3548f01297510f9267ef Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sun, 5 May 2019 10:30:04 +0100 Subject: [PATCH 55/56] Move tests from all_close to approx --- tests/array-construct.rs | 3 ++- tests/azip.rs | 9 ++++++--- tests/numeric.rs | 20 +++++++++++--------- tests/par_azip.rs | 3 ++- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/tests/array-construct.rs b/tests/array-construct.rs index 7e320c314..e14b9f9fd 100644 --- a/tests/array-construct.rs +++ b/tests/array-construct.rs @@ -21,6 +21,7 @@ fn test_dimension_zero() { } #[test] +#[cfg(features = "approx")] fn test_arc_into_owned() { let a = Array2::from_elem((5, 5), 1.).into_shared(); let mut b = a.clone(); @@ -28,7 +29,7 @@ fn test_arc_into_owned() { let mut c = b.into_owned(); c.fill(2.); // test that they are unshared - assert!(!a.all_close(&c, 0.01)); + assert_abs_diff_ne!(a, &c, 0.01); } #[test] diff --git a/tests/azip.rs b/tests/azip.rs index 39a5d2991..2d9166e2a 100644 --- a/tests/azip.rs +++ b/tests/azip.rs @@ -45,17 +45,19 @@ fn test_azip2_3() { } #[test] +#[cfg(features = "approx")] fn test_azip2_sum() { let c = Array::from_shape_fn((5, 10), |(i, j)| f32::exp((i + j) as f32)); for i in 0..2 { let ax = Axis(i); let mut b = Array::zeros(c.len_of(ax)); azip!(mut b, ref c (c.axis_iter(ax)) in { *b = c.sum() }); - assert!(b.all_close(&c.sum_axis(Axis(1 - i)), 1e-6)); + assert_abs_diff_eq!(b, c.sum_axis(Axis(1 - i)), 1e-6); } } #[test] +#[cfg(features = "approx")] fn test_azip3_slices() { let mut a = [0.; 32]; let mut b = [0.; 32]; @@ -69,10 +71,11 @@ fn test_azip3_slices() { *c = a.sin(); }); let res = Array::linspace(0., 3.1, 32).mapv_into(f32::sin); - assert!(res.all_close(&ArrayView::from(&c), 1e-4)); + assert_abs_diff_eq!(res, &ArrayView::from(&c), 1e-4); } #[test] +#[cfg(features = "approx")] fn test_broadcast() { let n = 16; let mut a = Array::::zeros((n, n)); @@ -90,7 +93,7 @@ fn test_broadcast() { .and_broadcast(&e); z.apply(|x, &y, &z, &w| *x = y + z + w); } - assert!(a.all_close(&(&b + &d + &e), 1e-4)); + assert_abs_diff_eq!(a, &(&b + &d + &e), 1e-4); } #[should_panic] diff --git a/tests/numeric.rs b/tests/numeric.rs index e73da4904..16ad9f707 100644 --- a/tests/numeric.rs +++ b/tests/numeric.rs @@ -62,6 +62,7 @@ fn sum_mean_empty() { } #[test] +#[cfg(features = "approx")] fn var_axis() { let a = array![ [ @@ -75,38 +76,39 @@ fn var_axis() { [-1.08, 4.66, 8.34, -0.73], ], ]; - assert!(a.var_axis(Axis(0), 1.5).all_close( + assert_abs_diff_eq!( + a.var_axis(Axis(0), 1.5), &aview2(&[ [3.236401e+02, 8.556250e+01, 4.708900e+00, 9.428410e+01], [9.672100e+00, 2.289169e+02, 7.344490e+01, 2.171560e+01], [7.157160e+01, 1.849000e-01, 2.631690e+01, 5.314410e+01] ]), 1e-4, - )); - assert!(a.var_axis(Axis(1), 1.7).all_close( + ); + assert_abs_diff_eq!(a.var_axis(Axis(1), 1.7), &aview2(&[ [0.61676923, 80.81092308, 6.79892308, 0.11789744], [75.19912821, 114.25235897, 48.32405128, 9.03020513], ]), 1e-8, - )); - assert!(a.var_axis(Axis(2), 2.3).all_close( + ); + assert_abs_diff_eq!(a.var_axis(Axis(2), 2.3), &aview2(&[ [ 79.64552941, 129.09663235, 95.98929412], [109.64952941, 43.28758824, 36.27439706], ]), 1e-8, - )); + ); let b = array![[1.1, 2.3, 4.7]]; - assert!(b.var_axis(Axis(0), 0.).all_close(&aview1(&[0., 0., 0.]), 1e-12)); - assert!(b.var_axis(Axis(1), 0.).all_close(&aview1(&[2.24]), 1e-12)); + assert_abs_diff_eq!(b.var_axis(Axis(0), 0.), &aview1(&[0., 0., 0.]), 1e-12); + assert_abs_diff_eq!(b.var_axis(Axis(1), 0.).all_close(&aview1(&[2.24]), 1e-12)); let c = array![[], []]; assert_eq!(c.var_axis(Axis(0), 0.), aview1(&[])); let d = array![1.1, 2.7, 3.5, 4.9]; - assert!(d.var_axis(Axis(0), 0.).all_close(&aview0(&1.8875), 1e-12)); + assert_abs_diff_eq!(d.var_axis(Axis(0), 0.), &aview0(&1.8875), 1e-12); } #[test] diff --git a/tests/par_azip.rs b/tests/par_azip.rs index 4ffe5b347..d373345e4 100644 --- a/tests/par_azip.rs +++ b/tests/par_azip.rs @@ -25,6 +25,7 @@ fn test_par_azip2() { } #[test] +#[cfg(features = "approx")] fn test_par_azip3() { let mut a = [0.; 32]; let mut b = [0.; 32]; @@ -38,7 +39,7 @@ fn test_par_azip3() { *c = a.sin(); }); let res = Array::linspace(0., 3.1, 32).mapv_into(f32::sin); - assert!(res.all_close(&ArrayView::from(&c), 1e-4)); + assert_abs_diff_eq!(res, &ArrayView::from(&c), 1e-4); } #[should_panic] From e6c6ff100924947fedde5f0f9a31a4773ac43b7b Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sun, 5 May 2019 10:33:33 +0100 Subject: [PATCH 56/56] Move tests from all_close to approx --- tests/azip.rs | 2 +- tests/numeric.rs | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/tests/azip.rs b/tests/azip.rs index 2d9166e2a..9e4efabce 100644 --- a/tests/azip.rs +++ b/tests/azip.rs @@ -4,7 +4,7 @@ extern crate itertools; use ndarray::prelude::*; use ndarray::Zip; -use itertools::{assert_equal, cloned, enumerate}; +use itertools::{assert_equal, cloned}; use std::mem::swap; diff --git a/tests/numeric.rs b/tests/numeric.rs index 16ad9f707..a9aaf7c91 100644 --- a/tests/numeric.rs +++ b/tests/numeric.rs @@ -1,6 +1,6 @@ extern crate approx; use std::f64; -use ndarray::{array, Axis, aview1, aview2, aview0, arr0, arr1, arr2, Array, Array1, Array2, Array3}; +use ndarray::{array, Axis, aview1, arr0, arr1, arr2, Array, Array1, Array2, Array3}; use approx::abs_diff_eq; #[test] @@ -102,7 +102,7 @@ fn var_axis() { let b = array![[1.1, 2.3, 4.7]]; assert_abs_diff_eq!(b.var_axis(Axis(0), 0.), &aview1(&[0., 0., 0.]), 1e-12); - assert_abs_diff_eq!(b.var_axis(Axis(1), 0.).all_close(&aview1(&[2.24]), 1e-12)); + assert_abs_diff_eq!(b.var_axis(Axis(1), 0.), &aview1(&[2.24]), 1e-12); let c = array![[], []]; assert_eq!(c.var_axis(Axis(0), 0.), aview1(&[])); @@ -112,6 +112,7 @@ fn var_axis() { } #[test] +#[cfg(features = "approx")] fn std_axis() { let a = array![ [ @@ -125,33 +126,33 @@ fn std_axis() { [ 0.51529756, 0.70111616, 0.20799415, 0.91851457] ], ]; - assert!(a.std_axis(Axis(0), 1.5).all_close( + assert_abs_diff_eq!(a.std_axis(Axis(0), 1.5), &aview2(&[ [ 0.05989184, 0.36051836, 0.00989781, 0.32669847], [ 0.81957535, 0.39599997, 0.49731472, 0.17084346], [ 0.07044443, 0.06795249, 0.09794304, 0.83195211], ]), 1e-4, - )); - assert!(a.std_axis(Axis(1), 1.7).all_close( + ); + assert_abs_diff_eq!(a.std_axis(Axis(1), 1.7), &aview2(&[ [ 0.42698655, 0.48139215, 0.36874991, 0.41458724], [ 0.26769097, 0.18941435, 0.30555015, 0.35118674], ]), 1e-8, - )); - assert!(a.std_axis(Axis(2), 2.3).all_close( + ); + assert_abs_diff_eq!(a.std_axis(Axis(2), 2.3), &aview2(&[ [ 0.41117907, 0.37130425, 0.35332388], [ 0.16905862, 0.25304841, 0.39978276], ]), 1e-8, - )); + ); let b = array![[100000., 1., 0.01]]; - assert!(b.std_axis(Axis(0), 0.).all_close(&aview1(&[0., 0., 0.]), 1e-12)); - assert!( - b.std_axis(Axis(1), 0.).all_close(&aview1(&[47140.214021552769]), 1e-6), + assert_abs_diff_eq!(b.std_axis(Axis(0), 0.), &aview1(&[0., 0., 0.]), 1e-12); + assert_abs_diff_eq!( + b.std_axis(Axis(1), 0.), &aview1(&[47140.214021552769]), 1e-6, ); let c = array![[], []];