diff --git a/Cargo.toml b/Cargo.toml index e77ee98fa..d5f02a648 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,9 @@ matrixmultiply = { version = "0.1.13" } version = "0.9" optional = true +[dev-dependencies] +defmac = "0.1" + [features] blas = ["blas-sys"] diff --git a/src/aliases.rs b/src/aliases.rs index 759b2039f..4f5ab945a 100644 --- a/src/aliases.rs +++ b/src/aliases.rs @@ -1,7 +1,14 @@ //! Type aliases for common array sizes //! -use ::{Ix, Array, ArrayView, ArrayViewMut, RcArray}; +use ::{ + Ix, + Array, + ArrayView, + ArrayViewMut, + RcArray, + IxDynImpl, +}; use ::dimension::Dim; use dimension::DimPrivate; @@ -38,6 +45,13 @@ pub fn Ix6(i0: Ix, i1: Ix, i2: Ix, i3: Ix, i4: Ix, i5: Ix) -> Ix6 { Dim::new([i0, i1, i2, i3, i4, i5]) } +/// Create a dynamic-dimensional index +#[allow(non_snake_case)] +#[inline(always)] +pub fn IxDyn(ix: &[Ix]) -> IxDyn { + Dim(ix) +} + /// zero-dimensionial pub type Ix0 = Dim<[Ix; 0]>; /// one-dimensional @@ -54,16 +68,18 @@ pub type Ix5 = Dim<[Ix; 5]>; pub type Ix6 = Dim<[Ix; 6]>; /// dynamic-dimensional /// -/// `Vec` and `&[usize]` implement `IntoDimension` to produce `IxDyn`; -/// use them to create arrays with a dynamic number of axes. +/// You can use the `IxDyn` function to create a dimension for an array with +/// dynamic number of dimensions. (`Vec` and `&[usize]` also implement +/// `IntoDimension` to produce `IxDyn`). /// /// ``` /// use ndarray::ArrayD; +/// use ndarray::IxDyn; /// /// // Create a 5 × 6 × 3 × 4 array using the dynamic dimension type -/// let mut a = ArrayD::::zeros(vec![5, 6, 3, 4]); +/// let mut a = ArrayD::::zeros(IxDyn(&[5, 6, 3, 4])); /// // Create a 1 × 3 × 4 array using the dynamic dimension type -/// let mut b = ArrayD::::zeros(vec![1, 3, 4]); +/// let mut b = ArrayD::::zeros(IxDyn(&[1, 3, 4])); /// /// // We can use broadcasting to add arrays of compatible shapes together: /// a += &b; @@ -78,7 +94,7 @@ pub type Ix6 = Dim<[Ix; 6]>; /// // the same type `Array` a.k.a `ArrayD`: /// let arrays = vec![a, b]; /// ``` -pub type IxDyn = Dim>; +pub type IxDyn = Dim; /// zero-dimensional array pub type Array0 = Array; diff --git a/src/arraytraits.rs b/src/arraytraits.rs index ec069dbb2..4968c63be 100644 --- a/src/arraytraits.rs +++ b/src/arraytraits.rs @@ -304,9 +304,11 @@ impl<'a, A: 'a, D, T> AsArray<'a, A, D> for T /// /// The array is created with dimension `D::default()`, which results /// in for example dimensions `0` and `(0, 0)` with zero elements for the -/// one-dimensional and two-dimensional cases respectively, while for example -/// the zero dimensional case uses `()` (or `Vec::new()`) which -/// results in an array with one element. +/// one-dimensional and two-dimensional cases respectively. +/// +/// The default dimension for `IxDyn` is `IxDyn(&[0])` (array has zero +/// elements). And the default for the dimension `()` is `()` (array has +/// one element). /// /// Since arrays cannot grow, the intention is to use the default value as /// placeholder. diff --git a/src/dimension/conversion.rs b/src/dimension/conversion.rs index edddf1e42..98f8e3bc8 100644 --- a/src/dimension/conversion.rs +++ b/src/dimension/conversion.rs @@ -11,7 +11,7 @@ use std::ops::{Index, IndexMut}; use libnum::Zero; -use {Ix, Ix1, IxDyn, Dimension, Dim}; +use {Ix, Ix1, IxDyn, Dimension, Dim, IxDynImpl}; use super::DimPrivate; /// $m: macro callback @@ -56,12 +56,18 @@ impl IntoDimension for D where D: Dimension { fn into_dimension(self) -> Self { self } } -impl IntoDimension for Vec { +impl IntoDimension for IxDynImpl { type Dim = IxDyn; #[inline(always)] fn into_dimension(self) -> Self::Dim { Dim::new(self) } } +impl IntoDimension for Vec { + type Dim = IxDyn; + #[inline(always)] + fn into_dimension(self) -> Self::Dim { Dim::new(IxDynImpl::from(self)) } +} + pub trait Convert { type To; fn convert(self) -> Self::To; diff --git a/src/dimension/dimension_trait.rs b/src/dimension/dimension_trait.rs index e3cdefe1b..0251f13e0 100644 --- a/src/dimension/dimension_trait.rs +++ b/src/dimension/dimension_trait.rs @@ -13,7 +13,7 @@ use std::ops::{Add, Sub, Mul, AddAssign, SubAssign, MulAssign}; use itertools::{enumerate, zip}; -use {Ix, Ixs, Ix0, Ix1, Ix2, Ix3, IxDyn, Dim, Si}; +use {Ix, Ixs, Ix0, Ix1, Ix2, Ix3, IxDyn, Dim, Si, IxDynImpl}; use IntoDimension; use RemoveAxis; use {ArrayView1, ArrayViewMut1}; @@ -53,7 +53,7 @@ pub unsafe trait Dimension : Clone + Eq + Debug + Send + Sync + Default + /// - For `Ix1`: `[Si; 1]` /// - For `Ix2`: `[Si; 2]` /// - and so on.. - /// - For `Vec`: `[Si]` + /// - For `IxDyn: `[Si]` /// /// The easiest way to create a `&SliceArg` is using the macro /// [`s![]`](macro.s!.html). @@ -63,7 +63,7 @@ pub unsafe trait Dimension : Clone + Eq + Debug + Send + Sync + Default + /// - For `Ix1`: `usize`, /// - For `Ix2`: `(usize, usize)` /// - and so on.. - /// - For `Vec`: `Vec`, + /// - For `IxDyn: `IxDyn` type Pattern: IntoDimension; // Next smaller dimension (if it exists) #[doc(hidden)] @@ -734,7 +734,7 @@ large_dim!(4, Ix4, Ix, Ix, Ix, Ix); large_dim!(5, Ix5, Ix, Ix, Ix, Ix, Ix); large_dim!(6, Ix6, Ix, Ix, Ix, Ix, Ix, Ix); -/// Vec is a "dynamic" index, pretty hard to use when indexing, +/// IxDyn is a "dynamic" index, pretty hard to use when indexing, /// and memory wasteful, but it allows an arbitrary and dynamic number of axes. unsafe impl Dimension for IxDyn { @@ -761,17 +761,17 @@ unsafe impl Dimension for IxDyn } } -impl Index for Dim> - where Vec: Index, +impl Index for Dim + where IxDynImpl: Index, { - type Output = as Index>::Output; + type Output = >::Output; fn index(&self, index: J) -> &Self::Output { &self.ix()[index] } } -impl IndexMut for Dim> - where Vec: IndexMut, +impl IndexMut for Dim + where IxDynImpl: IndexMut, { fn index_mut(&mut self, index: J) -> &mut Self::Output { &mut self.ixm()[index] diff --git a/src/dimension/dynindeximpl.rs b/src/dimension/dynindeximpl.rs new file mode 100644 index 000000000..6f29d5782 --- /dev/null +++ b/src/dimension/dynindeximpl.rs @@ -0,0 +1,211 @@ + +use std::ops::{ + Index, + IndexMut, + Deref, + DerefMut, +}; +use imp_prelude::*; +use dimension::DimPrivate; + +const CAP: usize = 4; + +/// T is usize or isize +#[derive(Debug)] +enum IxDynRepr { + Inline(u32, [T; CAP]), + Alloc(Box<[T]>), +} + +impl Deref for IxDynRepr { + type Target = [T]; + fn deref(&self) -> &[T] { + match *self { + IxDynRepr::Inline(len, ref ar) => { + unsafe { + ar.get_unchecked(..len as usize) + } + } + IxDynRepr::Alloc(ref ar) => &*ar, + } + } +} + +impl DerefMut for IxDynRepr { + fn deref_mut(&mut self) -> &mut [T] { + match *self { + IxDynRepr::Inline(len, ref mut ar) => { + unsafe { + ar.get_unchecked_mut(..len as usize) + } + } + IxDynRepr::Alloc(ref mut ar) => &mut *ar, + } + } +} + +/// The default is equivalent to `Self::from(&[0])`. +impl Default for IxDynRepr { + fn default() -> Self { + Self::copy_from(&[0]) + } +} + + +use ::libnum::Zero; + +impl IxDynRepr { + pub fn copy_from(x: &[T]) -> Self { + if x.len() <= CAP { + let mut arr = [T::zero(); CAP]; + for i in 0..x.len() { + arr[i] = x[i]; + } + IxDynRepr::Inline(x.len() as _, arr) + } else { + Self::from(x) + } + } +} + +impl IxDynRepr { + // make an Inline or Alloc version as appropriate + fn from_vec_auto(v: Vec) -> Self { + if v.len() <= CAP { + Self::copy_from(&v) + } else { + Self::from_vec(v) + } + } +} + +impl IxDynRepr { + fn from_vec(v: Vec) -> Self { + IxDynRepr::Alloc(v.into_boxed_slice()) + } + + fn from(x: &[T]) -> Self { + Self::from_vec(x.to_vec()) + } +} + +impl Clone for IxDynRepr { + fn clone(&self) -> Self { + match *self { + IxDynRepr::Inline(len, arr) => { + IxDynRepr::Inline(len, arr) + } + _ => Self::from(&self[..]) + } + } +} + +impl Eq for IxDynRepr { } + +impl PartialEq for IxDynRepr { + fn eq(&self, rhs: &Self) -> bool { + match (self, rhs) { + (&IxDynRepr::Inline(slen, ref sarr), &IxDynRepr::Inline(rlen, ref rarr)) => { + slen == rlen && + (0..CAP as usize).filter(|&i| i < slen as usize) + .all(|i| sarr[i] == rarr[i]) + } + _ => self[..] == rhs[..] + } + } +} + +/// Dynamic dimension or index type. +/// +/// Use `IxDyn` directly. This type implements a dynamic number of +/// dimensions or indices. Short dimensions are stored inline and don't need +/// any dynamic memory allocation. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct IxDynImpl(IxDynRepr); +unsafe impl Send for IxDynImpl {} +unsafe impl Sync for IxDynImpl {} + +impl IxDynImpl { + fn remove(&self, i: usize) -> Self { + IxDynImpl(match self.0 { + IxDynRepr::Inline(0, _) => IxDynRepr::Inline(0, [0; CAP]), + IxDynRepr::Inline(1, _) => IxDynRepr::Inline(0, [0; CAP]), + IxDynRepr::Inline(2, ref arr) => { + let mut out = [0; CAP]; + out[0] = arr[1 - i]; + IxDynRepr::Inline(1, out) + } + ref ixdyn => { + let len = ixdyn.len(); + let mut result = IxDynRepr::copy_from(&ixdyn[..len - 1]); + for j in i..len - 1 { + result[j] = ixdyn[j + 1] + } + result + } + }) + } +} + +impl<'a> From<&'a [Ix]> for IxDynImpl { + #[inline] + fn from(ix: &'a [Ix]) -> Self { + IxDynImpl(IxDynRepr::copy_from(ix)) + } +} + +impl From> for IxDynImpl { + #[inline] + fn from(ix: Vec) -> Self { + IxDynImpl(IxDynRepr::from_vec_auto(ix)) + } +} + +impl Index for IxDynImpl + where [Ix]: Index, +{ + type Output = <[Ix] as Index>::Output; + fn index(&self, index: J) -> &Self::Output { + &self.0[index] + } +} + +impl IndexMut for IxDynImpl + where [Ix]: IndexMut, +{ + fn index_mut(&mut self, index: J) -> &mut Self::Output { + &mut self.0[index] + } +} + +impl Deref for IxDynImpl { + type Target = [Ix]; + #[inline] + fn deref(&self) -> &[Ix] { + &self.0 + } +} + +impl DerefMut for IxDynImpl { + #[inline] + fn deref_mut(&mut self) -> &mut [Ix] { + &mut self.0 + } +} + +impl<'a> IntoIterator for &'a IxDynImpl { + type Item = &'a Ix; + type IntoIter = <&'a [Ix] as IntoIterator>::IntoIter; + #[inline] + fn into_iter(self) -> Self::IntoIter { + self[..].into_iter() + } +} + +impl RemoveAxis for Dim { + type Smaller = Self; + fn remove_axis(&self, axis: Axis) -> Self { + debug_assert!(axis.index() < self.ndim()); + Dim::new(self.ix().remove(axis.index())) + } +} diff --git a/src/dimension/mod.rs b/src/dimension/mod.rs index a7e16f87c..ed914e57a 100644 --- a/src/dimension/mod.rs +++ b/src/dimension/mod.rs @@ -17,12 +17,14 @@ pub use self::dimension_trait::Dimension; pub use self::ndindex::NdIndex; pub use self::remove_axis::RemoveAxis; pub use self::axes::{axes_of, Axes, AxisDescription}; +pub use self::dynindeximpl::IxDynImpl; #[macro_use] mod macros; mod axis; mod conversion; pub mod dim; mod dimension_trait; +mod dynindeximpl; mod ndindex; mod remove_axis; mod axes; diff --git a/src/dimension/ndindex.rs b/src/dimension/ndindex.rs index eeea5b29a..a33ac37e9 100644 --- a/src/dimension/ndindex.rs +++ b/src/dimension/ndindex.rs @@ -4,6 +4,9 @@ use std::fmt::Debug; use itertools::zip; use {Ix, Ix0, Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn, Dim, Dimension, IntoDimension}; +use { + IxDynImpl, +}; use super::{stride_offset, stride_offset_checked}; use super::DimPrivate; @@ -129,10 +132,10 @@ macro_rules! ndindex_with_array { } #[inline] - fn index_unchecked(&self, strides: &$ix_n) -> isize { + fn index_unchecked(&self, _strides: &$ix_n) -> isize { $( - stride_offset(self[$index], get!(strides, $index)) + - )+ + stride_offset(self[$index], get!(_strides, $index)) + + )* 0 } } @@ -154,7 +157,7 @@ macro_rules! ndindex_with_array { self, strides.ndim()); $( stride_offset(get!(self, $index), get!(strides, $index)) + - )+ + )* 0 } } @@ -176,7 +179,7 @@ macro_rules! ndindex_with_array { self, strides.ndim()); $( stride_offset(self[$index], get!(strides, $index)) + - )+ + )* 0 } } @@ -185,6 +188,7 @@ macro_rules! ndindex_with_array { } ndindex_with_array!{ + [0, Ix0] [1, Ix1 0] [2, Ix2 0 1] [3, Ix3 0 1 2] @@ -194,9 +198,9 @@ ndindex_with_array!{ } impl<'a> IntoDimension for &'a [Ix] { - type Dim = Dim>; + type Dim = IxDyn; fn into_dimension(self) -> Self::Dim { - Dim(self.to_vec()) + Dim(IxDynImpl::from(self)) } } @@ -209,7 +213,7 @@ unsafe impl<'a> NdIndex for &'a [Ix] { } } -unsafe impl NdIndex for Vec { +unsafe impl NdIndex for IxDynImpl { fn index_checked(&self, dim: &IxDyn, strides: &IxDyn) -> Option { stride_offset_checked(dim.ix(), strides.ix(), self) } diff --git a/src/dimension/remove_axis.rs b/src/dimension/remove_axis.rs index 824c5b21a..83fa3b8b1 100644 --- a/src/dimension/remove_axis.rs +++ b/src/dimension/remove_axis.rs @@ -66,12 +66,4 @@ macro_rules! impl_remove_axis_array( impl_remove_axis_array!(3, 4, 5, 6); -impl RemoveAxis for Dim> { - type Smaller = Self; - fn remove_axis(&self, axis: Axis) -> Self { - let mut res = self.clone(); - res.ixm().remove(axis.index()); - res - } -} diff --git a/src/lib.rs b/src/lib.rs index 37c314663..7b1611792 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,6 +94,7 @@ pub use dimension::{ pub use dimension::dim::*; pub use dimension::NdIndex; +pub use dimension::IxDynImpl; pub use indexes::Indices; pub use indexes::{indices, indices_of}; pub use error::{ShapeError, ErrorKind}; diff --git a/tests/array.rs b/tests/array.rs index acb1fe005..f0770aa5b 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -1072,6 +1072,14 @@ fn test_default() { assert_eq!(b, arr0(Foo::default())); } +#[test] +fn test_default_ixdyn() { + let a = as Default>::default(); + let b = >::zeros(IxDyn(&[0])); + assert_eq!(a, b); +} + + #[test] fn test_map_axis() { let a = arr2(&[[1, 2, 3], diff --git a/tests/dimension.rs b/tests/dimension.rs index 6f75ebbf0..ea2c527ea 100644 --- a/tests/dimension.rs +++ b/tests/dimension.rs @@ -1,4 +1,6 @@ extern crate ndarray; +#[macro_use] +extern crate defmac; use ndarray::{ RcArray, @@ -18,7 +20,7 @@ fn remove_axis() assert_eq!(Dim([1, 2]).remove_axis(Axis(0)), Dim([2])); assert_eq!(Dim([4, 5, 6]).remove_axis(Axis(1)), Dim([4, 6])); - assert_eq!(Dim(vec![1,2]).remove_axis(Axis(0)), Dim(vec![2])); + assert_eq!(Dim(vec![1, 2]).remove_axis(Axis(0)), Dim(vec![2])); assert_eq!(Dim(vec![4, 5, 6]).remove_axis(Axis(1)), Dim(vec![4, 6])); let a = RcArray::::zeros((4,5)); @@ -34,7 +36,7 @@ fn dyn_dimension() let a = arr2(&[[1., 2.], [3., 4.0]]).into_shape(vec![2, 2]).unwrap(); assert_eq!(&a - &a, Array::zeros(vec![2, 2])); assert_eq!(a[&[0, 0][..]], 1.); - assert_eq!(a[vec![0, 0]], 1.); + assert_eq!(a[[0, 0]], 1.); let mut dim = vec![1; 1024]; dim[16] = 4; @@ -43,6 +45,25 @@ fn dyn_dimension() assert_eq!(z.shape(), &dim[..]); } +#[test] +fn dyn_remove() { + let mut v = vec![1, 2, 3, 4, 5, 6, 7]; + let mut dim = Dim(v.clone()); + defmac!(test_remove index => { + dim = dim.remove_axis(Axis(index)); + v.remove(index); + assert_eq!(dim.slice(), &v[..]); + }); + + test_remove!(1); + test_remove!(2); + test_remove!(3); + test_remove!(0); + test_remove!(2); + test_remove!(0); + test_remove!(0); +} + #[test] fn fastest_varying_order() { let strides = Dim([2, 8, 4, 1]); diff --git a/tests/iterator_chunks.rs b/tests/iterator_chunks.rs index 965837fa4..286d643d5 100644 --- a/tests/iterator_chunks.rs +++ b/tests/iterator_chunks.rs @@ -50,7 +50,7 @@ fn chunks_ok_size() { let mut c = 0; for elt in a.whole_chunks(vec![2, 1]) { assert!(elt.iter().all(|&x| x == 1.)); - assert_eq!(elt.dim(), vec![2, 1]); + assert_eq!(elt.shape(), &[2, 1]); c += 1; } assert_eq!(c, 3); diff --git a/tests/ixdyn.rs b/tests/ixdyn.rs index e293c7ab8..b6843d4fa 100644 --- a/tests/ixdyn.rs +++ b/tests/ixdyn.rs @@ -3,6 +3,7 @@ extern crate ndarray; use ndarray::Array; use ndarray::Ix3; +use ndarray::IntoDimension; use ndarray::ShapeBuilder; #[test] @@ -98,3 +99,50 @@ fn test_ixdyn_uget() { } assert_eq!(sum, 10.); } + +#[test] +fn test_0() { + let mut a = Array::zeros(vec![]); + let z = vec![].into_dimension(); + assert_eq!(a[z.clone()], 0.); + a[[]] = 1.; + assert_eq!(a[[]], 1.); + assert_eq!(a.len(), 1); + assert_eq!(a.as_slice().unwrap(), &[1.]); + + let mut a = Array::zeros(vec![].f()); + assert_eq!(a[[]], 0.); + a[[]] = 1.; + assert_eq!(a[[]], 1.); + assert_eq!(a.len(), 1); + assert_eq!(a.as_slice().unwrap(), &[1.]); +} + +#[test] +fn test_0_add() { + let mut a = Array::zeros(vec![]); + a += 1.; + assert_eq!(a[[]], 1.); + a += 2.; + assert_eq!(a[[]], 3.); +} + +#[test] +fn test_0_add_add() { + let mut a = Array::zeros(vec![]); + a += 1.; + let mut b = Array::zeros(vec![]); + b += 1.; + a += &b; + assert_eq!(a[[]], 2.); +} + +#[test] +fn test_0_add_broad() { + let mut b = Array::from_vec(vec![5., 6.]); + let mut a = Array::zeros(vec![]); + a += 1.; + b += &a; + assert_eq!(b[0], 6.); + assert_eq!(b[1], 7.); +}