Repository: rust-ndarray/ndarray
Pull request #790 (2020-03-21 18:11:43)

Added implementation of `var` and `std` methods for ArrayBase
I have found that ArrayBase is missing single-dimensional implementations for `var` and `std`. While you can use `var_axis(Axis(0), 1.)`, it can become verbose and unnecessary in code with lots of single-dimensional arrays. I think `sum_axis` and `mean_axis` have `sum` and `mean` for this reason too.

Please consider merging this pull request.

kdubovikov: I see that the build fails at `cargo clippy` check un an unrelated file. Unsure if I need to fix that.
bluss: Thanks! Looks similar to #753 I don't mind whichever gets merged first
ZuseZ4:  @jturner314 and @bluss I think it makes sense to merge this?
If we merge #848 first and rerun Travis here it shouldn't fail anymore. Just to be sure. 
kdubovikov: @jturner314 , @bluss , @ZuseZ4 anything else I should do to help with the merge?
bluss: Looks great. I've rebased to current master so that tests will run properly with new CI. Given the commit history, I'd merge this with squash to clean it up a bit

Patch:
diff --git a/RELEASES.md b/RELEASES.md
index 95d82171..2f410473 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,3 +1,56 @@
+Version 0.15.0 (Not released yet)
+=================================
+
+New features
+------------
+
+- Support for compiling ndarray as `no_std` (using core and alloc) by [@xd009642]
+
+  https://github.com/rust-ndarray/ndarray/pull/861
+
+- New methods `.cell_view()` and `ArrayViewMut::into_cell_view` that enable
+  new ways of working with array elements as if they were in Cells - setting
+  elements through shared views and broadcast views.
+
+  https://github.com/rust-ndarray/ndarray/pull/877
+
+
+Enhancements
+------------
+
+- Fix `Zip` for the 0-dimensional case by [@jturner314]
+
+  https://github.com/rust-ndarray/ndarray/pull/862
+
+API changes
+-----------
+
+- Removed deprecated methods by [@bluss]:
+
+  - Remove deprecated `.all_close()` - use approx feature and methods like  `.abs_diff_eq` instead
+  - Mark `.scalar_sum()` as deprecated - use `.sum()` instead
+  - Remove deprecated `DataClone` - use `Data + RawDataClone` instead
+  - Remove deprecated `ArrayView::into_slice` - use `to_slice()` instead.
+
+  https://github.com/rust-ndarray/ndarray/pull/874
+
+- Remove deprecated methods: rows, cols (for row and column count; the new
+  names are nrows and ncols) by [@bluss]
+
+  https://github.com/rust-ndarray/ndarray/pull/872
+
+- Renamed methods (old names are now deprecated) by [@bluss]
+
+  - `genrows/_mut` -> `rows/_mut`
+  - `gencolumns/_mut` -> `columns/_mut`
+
+  https://github.com/rust-ndarray/ndarray/pull/872
+
+Other changes
+-------------
+
+
+
 Version 0.14.0 (2020-11-28)
 ===========================
 
diff --git a/src/argument_traits.rs b/src/argument_traits.rs
index a93e33a1..82d4869a 100644
--- a/src/argument_traits.rs
+++ b/src/argument_traits.rs
@@ -1,6 +1,7 @@
 use std::cell::Cell;
 use std::mem::MaybeUninit;
 
+use crate::math_cell::MathCell;
 
 /// A producer element that can be assigned to once
 pub trait AssignElem<T> {
@@ -22,6 +23,13 @@ impl<'a, T> AssignElem<T> for &'a Cell<T> {
     }
 }
 
+/// Assignable element, simply `self.set(input)`.
+impl<'a, T> AssignElem<T> for &'a MathCell<T> {
+    fn assign_elem(self, input: T) {
+        self.set(input);
+    }
+}
+
 /// Assignable element, the item in the MaybeUninit is overwritten (prior value, if any, is not
 /// read or dropped).
 impl<'a, T> AssignElem<T> for &'a mut MaybeUninit<T> {
diff --git a/src/impl_methods.rs b/src/impl_methods.rs
index cccc195a..fd89df21 100644
--- a/src/impl_methods.rs
+++ b/src/impl_methods.rs
@@ -20,6 +20,7 @@ use crate::dimension::{
     abs_index, axes_of, do_slice, merge_axes, size_of_shape_checked, stride_offset, Axes,
 };
 use crate::error::{self, ErrorKind, ShapeError};
+use crate::math_cell::MathCell;
 use crate::itertools::zip;
 use crate::zip::Zip;
 
@@ -69,7 +70,7 @@ where
         self.dim.clone().into_pattern()
     }
 
-    /// Return the shape of the array as it stored in the array.
+    /// Return the shape of the array as it's stored in the array.
     ///
     /// This is primarily useful for passing to other `ArrayBase`
     /// functions, such as when creating another array of the same
@@ -151,6 +152,20 @@ where
         unsafe { ArrayViewMut::new(self.ptr, self.dim.clone(), self.strides.clone()) }
     }
 
+    /// Return a shared view of the array with elements as if they were embedded in cells.
+    ///
+    /// The cell view requires a mutable borrow of the array. Once borrowed the
+    /// cell view itself can be copied and accessed without exclusivity.
+    ///
+    /// The view acts "as if" the elements are temporarily in cells, and elements
+    /// can be changed through shared references using the regular cell methods.
+    pub fn cell_view(&mut self) -> ArrayView<'_, MathCell<A>, D>
+    where
+        S: DataMut,
+    {
+        self.view_mut().into_cell_view()
+    }
+
     /// Return an uniquely owned copy of the array.
     ///
     /// If the input array is contiguous and its strides are positive, then the
@@ -630,7 +645,7 @@ where
     ///
     /// The caller must ensure that:
     ///
-    /// 1. both `index1 and `index2` are in-bounds and
+    /// 1. both `index1` and `index2` are in-bounds and
     ///
     /// 2. the data is uniquely held by the array. (This property is guaranteed
     ///    for `Array` and `ArrayViewMut`, but not for `ArcArray` or `CowArray`.)
@@ -929,7 +944,7 @@ where
     /// Return a producer and iterable that traverses over all 1D lanes
     /// pointing in the direction of `axis`.
     ///
-    /// When the pointing in the direction of the first axis, they are *columns*,
+    /// When pointing in the direction of the first axis, they are *columns*,
     /// in the direction of the last axis *rows*; in general they are all
     /// *lanes* and are one dimensional.
     ///
@@ -1198,7 +1213,7 @@ where
         (len, stride)
     }
 
-    /// Return an view of the diagonal elements of the array.
+    /// Return a view of the diagonal elements of the array.
     ///
     /// The diagonal is simply the sequence indexed by *(0, 0, .., 0)*,
     /// *(1, 1, ..., 1)* etc as long as all axes have elements.
@@ -1257,7 +1272,7 @@ where
     /// Return `true` if the array data is laid out in contiguous “C order” in
     /// memory (where the last index is the most rapidly varying).
     ///
-    /// Return `false` otherwise, i.e the array is possibly not
+    /// Return `false` otherwise, i.e. the array is possibly not
     /// contiguous in memory, it has custom strides, etc.
     pub fn is_standard_layout(&self) -> bool {
         fn is_standard_layout<D: Dimension>(dim: &D, strides: &D) -> bool {
diff --git a/src/impl_views/conversions.rs b/src/impl_views/conversions.rs
index 863d26dd..8c423010 100644
--- a/src/impl_views/conversions.rs
+++ b/src/impl_views/conversions.rs
@@ -13,6 +13,7 @@ use crate::imp_prelude::*;
 use crate::{Baseiter, ElementsBase, ElementsBaseMut, Iter, IterMut};
 
 use crate::iter::{self, AxisIter, AxisIterMut};
+use crate::math_cell::MathCell;
 use crate::IndexLonger;
 
 /// Methods for read-only array views.
@@ -117,6 +118,21 @@ where
     pub fn into_slice(self) -> Option<&'a mut [A]> {
         self.try_into_slice().ok()
     }
+
+    /// Return a shared view of the array with elements as if they were embedded in cells.
+    ///
+    /// The cell view itself can be copied and accessed without exclusivity.
+    ///
+    /// The view acts "as if" the elements are temporarily in cells, and elements
+    /// can be changed through shared references using the regular cell methods.
+    pub fn into_cell_view(self) -> ArrayView<'a, MathCell<A>, D> {
+        // safety: valid because
+        // A and MathCell<A> have the same representation
+        // &'a mut T is interchangeable with &'a Cell<T> -- see method Cell::from_mut in std
+        unsafe {
+            self.into_raw_view_mut().cast::<MathCell<A>>().deref_into_view()
+        }
+    }
 }
 
 /// Private array view methods
diff --git a/src/lib.rs b/src/lib.rs
index c0a3b2e8..e49445a8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -139,6 +139,7 @@ pub use crate::linalg_traits::LinalgScalar;
 
 pub use crate::stacking::{concatenate, stack, stack_new_axis};
 
+pub use crate::math_cell::MathCell;
 pub use crate::impl_views::IndexLonger;
 pub use crate::shape_builder::{Shape, ShapeBuilder, StrideShape};
 
@@ -180,6 +181,7 @@ mod layout;
 mod linalg_traits;
 mod linspace;
 mod logspace;
+mod math_cell;
 mod numeric_util;
 mod partial;
 mod shape_builder;
@@ -300,7 +302,7 @@ pub type Ixs = isize;
 ///
 /// [`ArrayView`] and [`ArrayViewMut`] are read-only and read-write array views
 /// respectively. They use dimensionality, indexing, and almost all other
-/// methods the same was as the other array types.
+/// methods the same way as the other array types.
 ///
 /// Methods for `ArrayBase` apply to array views too, when the trait bounds
 /// allow.
diff --git a/src/math_cell.rs b/src/math_cell.rs
new file mode 100644
index 00000000..f0f8da40
--- /dev/null
+++ b/src/math_cell.rs
@@ -0,0 +1,102 @@
+
+use std::cell::Cell;
+use std::cmp::Ordering;
+use std::fmt;
+
+use std::ops::{Deref, DerefMut};
+
+/// A transparent wrapper of [`Cell<T>`](std::cell::Cell) which is identical in every way, except
+/// it will implement arithmetic operators as well.
+///
+/// The purpose of `MathCell` is to be used from [.cell_view()](crate::ArrayBase::cell_view).
+/// The `MathCell` derefs to `Cell`, so all the cell's methods are available.
+#[repr(transparent)]
+#[derive(Default)]
+pub struct MathCell<T>(Cell<T>);
+
+impl<T> MathCell<T> {
+    /// Create a new cell with the given value
+    #[inline(always)]
+    pub const fn new(value: T) -> Self { MathCell(Cell::new(value)) }
+
+    /// Return the inner value
+    pub fn into_inner(self) -> T { Cell::into_inner(self.0) }
+
+    /// Swap value with another cell
+    pub fn swap(&self, other: &Self) {
+        Cell::swap(&self.0, &other.0)
+    }
+}
+
+impl<T> Deref for MathCell<T> {
+    type Target = Cell<T>;
+    #[inline(always)]
+    fn deref(&self) -> &Self::Target { &self.0 }
+}
+
+impl<T> DerefMut for MathCell<T> {
+    #[inline(always)]
+    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
+}
+
+impl<T> Clone for MathCell<T>
+    where T: Copy
+{
+    fn clone(&self) -> Self {
+        MathCell::new(self.get())
+    }
+}
+
+impl<T> PartialEq for MathCell<T>
+    where T: Copy + PartialEq
+{
+    fn eq(&self, rhs: &Self) -> bool {
+        self.get() == rhs.get()
+    }
+}
+
+impl<T> Eq for MathCell<T>
+    where T: Copy + Eq
+{ }
+
+impl<T> PartialOrd for MathCell<T>
+    where T: Copy + PartialOrd
+{
+    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
+        self.get().partial_cmp(&rhs.get())
+    }
+
+    fn lt(&self, rhs: &Self) -> bool { self.get().lt(&rhs.get()) }
+    fn le(&self, rhs: &Self) -> bool { self.get().le(&rhs.get()) }
+    fn gt(&self, rhs: &Self) -> bool { self.get().gt(&rhs.get()) }
+    fn ge(&self, rhs: &Self) -> bool { self.get().ge(&rhs.get()) }
+}
+
+impl<T> Ord for MathCell<T>
+    where T: Copy + Ord
+{
+    fn cmp(&self, rhs: &Self) -> Ordering {
+        self.get().cmp(&rhs.get())
+    }
+}
+
+impl<T> fmt::Debug for MathCell<T>
+    where T: Copy + fmt::Debug
+{
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.get().fmt(f)
+    }
+}
+
+
+#[cfg(test)]
+mod tests {
+    use super::MathCell;
+
+    #[test]
+    fn test_basic() {
+        let c = &MathCell::new(0);
+        c.set(1);
+        assert_eq!(c.get(), 1);
+    }
+}
diff --git a/src/numeric/impl_numeric.rs b/src/numeric/impl_numeric.rs
index 6fc4f6cf..a3858f82 100644
--- a/src/numeric/impl_numeric.rs
+++ b/src/numeric/impl_numeric.rs
@@ -13,6 +13,8 @@ use crate::imp_prelude::*;
 use crate::itertools::enumerate;
 use crate::numeric_util;
 
+use crate::{FoldWhile, Zip};
+
 /// # Numerical Methods for Arrays
 impl<A, S, D> ArrayBase<S, D>
 where
@@ -111,6 +113,114 @@ where
         sum
     }
 
+    /// Return variance of elements in the array.
+    ///
+    /// The variance is computed using the [Welford one-pass
+    /// algorithm](https://www.jstor.org/stable/1266577).
+    ///
+    /// The parameter `ddof` specifies the "delta degrees of freedom". For
+    /// example, to calculate the population variance, use `ddof = 0`, or to
+    /// calculate the sample variance, use `ddof = 1`.
+    ///
+    /// The variance is defined as:
+    ///
+    /// ```text
+    ///               1       n
+    /// variance = ――――――――   ∑ (xᵢ - x̅)²
+    ///            n - ddof  i=1
+    /// ```
+    ///
+    /// where
+    ///
+    /// ```text
+    ///     1   n
+    /// x̅ = ―   ∑ xᵢ
+    ///     n  i=1
+    /// ```
+    ///
+    /// and `n` is the length of the array.
+    ///
+    /// **Panics** if `ddof` is less than zero or greater than `n`
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use ndarray::array;
+    /// use approx::assert_abs_diff_eq;
+    ///
+    /// let a = array![1., -4.32, 1.14, 0.32];
+    /// let var = a.var(1.);
+    /// assert_abs_diff_eq!(var, 6.7331, epsilon = 1e-4);
+    /// ```
+    pub fn var(&self, ddof: A) -> A
+    where
+        A: Float + FromPrimitive,
+    {
+        let zero = A::from_usize(0).expect("Converting 0 to `A` must not fail.");
+        let n = A::from_usize(self.len()).expect("Converting length to `A` must not fail.");
+        assert!(
+            !(ddof < zero || ddof > n),
+            "`ddof` must not be less than zero or greater than the length of \
+             the axis",
+        );
+        let dof = n - ddof;
+        let mut mean = A::zero();
+        let mut sum_sq = A::zero();
+        for (i, &x) in self.into_iter().enumerate() {
+            let count = A::from_usize(i + 1).expect("Converting index to `A` must not fail.");
+            let delta = x - mean;
+            mean = mean + delta / count;
+            sum_sq = (x - mean).mul_add(delta, sum_sq);
+        }
+        sum_sq / dof
+    }
+
+    /// Return standard deviation of elements in the array.
+    ///
+    /// The standard deviation is computed from the variance using
+    /// the [Welford one-pass algorithm](https://www.jstor.org/stable/1266577).
+    ///
+    /// The parameter `ddof` specifies the "delta degrees of freedom". For
+    /// example, to calculate the population standard deviation, use `ddof = 0`,
+    /// or to calculate the sample standard deviation, use `ddof = 1`.
+    ///
+    /// The standard deviation is defined as:
+    ///
+    /// ```text
+    ///               ⎛    1       n          ⎞
+    /// stddev = sqrt ⎜ ――――――――   ∑ (xᵢ - x̅)²⎟
+    ///               ⎝ n - ddof  i=1         ⎠
+    /// ```
+    ///
+    /// where
+    ///
+    /// ```text
+    ///     1   n
+    /// x̅ = ―   ∑ xᵢ
+    ///     n  i=1
+    /// ```
+    ///
+    /// and `n` is the length of the array.
+    ///
+    /// **Panics** if `ddof` is less than zero or greater than `n`
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use ndarray::array;
+    /// use approx::assert_abs_diff_eq;
+    ///
+    /// let a = array![1., -4.32, 1.14, 0.32];
+    /// let stddev = a.std(1.);
+    /// assert_abs_diff_eq!(stddev, 2.59483, epsilon = 1e-4);
+    /// ```
+    pub fn std(&self, ddof: A) -> A
+    where
+        A: Float + FromPrimitive,
+    {
+        self.var(ddof).sqrt()
+    }
+
     /// Return sum along `axis`.
     ///
     /// ```
diff --git a/src/private.rs b/src/private.rs
index ea13164e..552b3149 100644
--- a/src/private.rs
+++ b/src/private.rs
@@ -21,5 +21,5 @@ macro_rules! private_impl {
         fn __private__(&self) -> crate::private::PrivateMarker {
             crate::private::PrivateMarker
         }
-    }
+    };
 }


Test patch:
diff --git a/tests/numeric.rs b/tests/numeric.rs
index 7c6f1441..13f8433c 100644
--- a/tests/numeric.rs
+++ b/tests/numeric.rs
@@ -64,6 +64,72 @@ fn sum_mean_empty() {
     assert_eq!(a, None);
 }
 
+#[test]
+fn var() {
+    let a = array![1., -4.32, 1.14, 0.32];
+    assert_abs_diff_eq!(a.var(0.), 5.049875, epsilon = 1e-8);
+}
+
+#[test]
+#[should_panic]
+fn var_negative_ddof() {
+    let a = array![1., 2., 3.];
+    a.var(-1.);
+}
+
+#[test]
+#[should_panic]
+fn var_too_large_ddof() {
+    let a = array![1., 2., 3.];
+    a.var(4.);
+}
+
+#[test]
+fn var_nan_ddof() {
+    let a = Array2::<f64>::zeros((2, 3));
+    let v = a.var(::std::f64::NAN);
+    assert!(v.is_nan());
+}
+
+#[test]
+fn var_empty_arr() {
+    let a: Array1<f64> = array![];
+    assert!(a.var(0.0).is_nan());
+}
+
+#[test]
+fn std() {
+    let a = array![1., -4.32, 1.14, 0.32];
+    assert_abs_diff_eq!(a.std(0.), 2.24719, epsilon = 1e-5);
+}
+
+#[test]
+#[should_panic]
+fn std_negative_ddof() {
+    let a = array![1., 2., 3.];
+    a.std(-1.);
+}
+
+#[test]
+#[should_panic]
+fn std_too_large_ddof() {
+    let a = array![1., 2., 3.];
+    a.std(4.);
+}
+
+#[test]
+fn std_nan_ddof() {
+    let a = Array2::<f64>::zeros((2, 3));
+    let v = a.std(::std::f64::NAN);
+    assert!(v.is_nan());
+}
+
+#[test]
+fn std_empty_arr() {
+    let a: Array1<f64> = array![];
+    assert!(a.std(0.0).is_nan());
+}
+
 #[test]
 #[cfg(feature = "approx")]
 fn var_axis() {
diff --git a/tests/views.rs b/tests/views.rs
new file mode 100644
index 00000000..216e5540
--- /dev/null
+++ b/tests/views.rs
@@ -0,0 +1,16 @@
+use ndarray::prelude::*;
+use ndarray::Zip;
+
+#[test]
+fn cell_view() {
+    let mut a = Array::from_shape_fn((10, 5), |(i, j)| (i * j) as f32);
+    let answer = &a + 1.;
+
+    {
+        let cv1 = a.cell_view();
+        let cv2 = cv1;
+
+        Zip::from(cv1).and(cv2).apply(|a, b| a.set(b.get() + 1.));
+    }
+    assert_eq!(a, answer);
+}