From 5cad326c20f02f89a04a4980038a03f35c29f378 Mon Sep 17 00:00:00 2001 From: Jim Turner Date: Tue, 15 Oct 2019 23:14:58 -0400 Subject: [PATCH 1/2] Impl arithmetic ops on more combinations of types In particular, this adds implementations for `ArrayView` where `&ArrayBase` was allowed previously, and it adds support for in-place operations where the RHS is any array (instead of requiring the RHS to be a reference). --- src/impl_ops.rs | 143 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 31 ++++++----- 2 files changed, 159 insertions(+), 15 deletions(-) diff --git a/src/impl_ops.rs b/src/impl_ops.rs index 4804356e8..02eee8675 100644 --- a/src/impl_ops.rs +++ b/src/impl_ops.rs @@ -127,6 +127,74 @@ where } } +/// Perform elementwise +#[doc=$doc] +/// between references `self` and `rhs`, +/// and return the result as a new `Array`. +/// +/// If their shapes disagree, `rhs` is broadcast to the shape of `self`. +/// +/// **Panics** if broadcasting isn’t possible. +impl<'a, 'b, A, B, S2, D, E> $trt<&'b ArrayBase> for ArrayView<'a, A, D> +where + A: Clone + $trt, + B: Clone, + S2: Data, + D: Dimension, + E: Dimension, +{ + type Output = Array; + fn $mth(self, rhs: &'b ArrayBase) -> Array { + // FIXME: Can we co-broadcast arrays here? And how? + self.to_owned().$mth(rhs) + } +} + +/// Perform elementwise +#[doc=$doc] +/// between references `self` and `rhs`, +/// and return the result as a new `Array`. +/// +/// If their shapes disagree, `rhs` is broadcast to the shape of `self`. +/// +/// **Panics** if broadcasting isn’t possible. +impl<'a, 'b, A, B, S, D, E> $trt> for &'a ArrayBase +where + A: Clone + $trt, + B: Clone, + S: Data, + D: Dimension, + E: Dimension, +{ + type Output = Array; + fn $mth(self, rhs: ArrayView<'b, B, E>) -> Array { + // FIXME: Can we co-broadcast arrays here? And how? + self.to_owned().$mth(rhs) + } +} + +/// Perform elementwise +#[doc=$doc] +/// between references `self` and `rhs`, +/// and return the result as a new `Array`. +/// +/// If their shapes disagree, `rhs` is broadcast to the shape of `self`. +/// +/// **Panics** if broadcasting isn’t possible. +impl<'a, 'b, A, B, D, E> $trt> for ArrayView<'a, A, D> +where + A: Clone + $trt, + B: Clone, + D: Dimension, + E: Dimension, +{ + type Output = Array; + fn $mth(self, rhs: ArrayView<'b, B, E>) -> Array { + // FIXME: Can we co-broadcast arrays here? And how? + self.to_owned().$mth(rhs) + } +} + /// Perform elementwise #[doc=$doc] /// between `self` and the scalar `x`, @@ -163,6 +231,21 @@ impl<'a, A, S, D, B> $trt for &'a ArrayBase self.to_owned().$mth(x) } } + +/// Perform elementwise +#[doc=$doc] +/// between the reference `self` and the scalar `x`, +/// and return the result as a new `Array`. +impl<'a, A, D, B> $trt for ArrayView<'a, A, D> + where A: Clone + $trt, + D: Dimension, + B: ScalarOperand, +{ + type Output = Array; + fn $mth(self, x: B) -> Array { + self.to_owned().$mth(x) + } +} ); ); @@ -218,6 +301,23 @@ impl<'a, S, D> $trt<&'a ArrayBase> for $scalar }) } } + +/// Perform elementwise +/// between the scalar `self` and array `rhs`, +/// and return the result as a new `Array`. +impl<'a, D> $trt> for $scalar +where + D: Dimension, +{ + type Output = Array<$scalar, D>; + fn $mth(self, rhs: ArrayView<'a, $scalar, D>) -> Array<$scalar, D> { + if_commutative!($commutative { + rhs.$mth(self) + } or { + self.$mth(rhs.to_owned()) + }) + } +} ); } @@ -320,6 +420,19 @@ mod arithmetic_ops { } } + impl<'a, A, D> Neg for ArrayView<'a, A, D> + where + for<'b> &'b A: Neg, + D: Dimension, + { + type Output = Array; + /// Perform an elementwise negation of reference `self` and return the + /// result as a new `Array`. + fn neg(self) -> Array { + self.map(Neg::neg) + } + } + impl Not for ArrayBase where A: Clone + Not, @@ -349,6 +462,19 @@ mod arithmetic_ops { self.map(Not::not) } } + + impl<'a, A, D> Not for ArrayView<'a, A, D> + where + for<'b> &'b A: Not, + D: Dimension, + { + type Output = Array; + /// Perform an elementwise unary not of reference `self` and return the + /// result as a new `Array`. + fn not(self) -> Array { + self.map(Not::not) + } + } } mod assign_ops { @@ -359,6 +485,23 @@ mod assign_ops { ($trt:ident, $method:ident, $doc:expr) => { use std::ops::$trt; + #[doc=$doc] + /// If their shapes disagree, `rhs` is broadcast to the shape of `self`. + /// + /// **Panics** if broadcasting isn’t possible. + impl $trt> for ArrayBase + where + A: Clone + $trt, + S: DataMut, + S2: Data, + D: Dimension, + E: Dimension, + { + fn $method(&mut self, rhs: ArrayBase) { + self.$method(&rhs) + } + } + #[doc=$doc] /// If their shapes disagree, `rhs` is broadcast to the shape of `self`. /// diff --git a/src/lib.rs b/src/lib.rs index 35d1e1aab..38283f6ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -607,18 +607,18 @@ pub type Ixs = isize; /// /// ### Binary Operators with Two Arrays /// -/// Let `A` be an array or view of any kind. Let `B` be an array +/// Let `A` be an array or view of any kind. Let `O` be an array /// with owned storage (either `Array` or `ArcArray`). -/// Let `C` be an array with mutable data (either `Array`, `ArcArray` -/// or `ArrayViewMut`). +/// Let `M` be an array with mutable data (either `Array`, `ArcArray` +/// or `ArrayViewMut`). Let `V` be an `ArrayView`. /// The following combinations of operands /// are supported for an arbitrary binary operator denoted by `@` (it can be /// `+`, `-`, `*`, `/` and so on). /// -/// - `&A @ &A` which produces a new `Array` -/// - `B @ A` which consumes `B`, updates it with the result, and returns it -/// - `B @ &A` which consumes `B`, updates it with the result, and returns it -/// - `C @= &A` which performs an arithmetic operation in place +/// - `&A @ &A`, `&A @ V`, `V @ &A`, or `V @ V` which produce a new `Array` +/// - `O @ A` which consumes `O`, updates it with the result, and returns it +/// - `O @ &A` which consumes `O`, updates it with the result, and returns it +/// - `M @= &A` or `M @= A` which performs an arithmetic operation in place on `M` /// /// Note that the element type needs to implement the operator trait and the /// `Clone` trait. @@ -646,18 +646,19 @@ pub type Ixs = isize; /// are supported (scalar can be on either the left or right side, but /// `ScalarOperand` docs has the detailed condtions). /// -/// - `&A @ K` or `K @ &A` which produces a new `Array` -/// - `B @ K` or `K @ B` which consumes `B`, updates it with the result and returns it -/// - `C @= K` which performs an arithmetic operation in place +/// - `&A @ K`, `V @ K`, `K @ &A`, or `K @ V` which produces a new `Array` +/// - `O @ K` or `K @ O` which consumes `O`, updates it with the result and returns it +/// - `M @= K` which performs an arithmetic operation in place /// /// ### Unary Operators /// -/// Let `A` be an array or view of any kind. Let `B` be an array with owned -/// storage (either `Array` or `ArcArray`). The following operands are supported -/// for an arbitrary unary operator denoted by `@` (it can be `-` or `!`). +/// Let `A` be an array or view of any kind. Let `O` be an array with owned +/// storage (either `Array` or `ArcArray`). Let `V` be an `ArrayView`. The +/// following operands are supported for an arbitrary unary operator denoted by +/// `@` (it can be `-` or `!`). /// -/// - `@&A` which produces a new `Array` -/// - `@B` which consumes `B`, updates it with the result, and returns it +/// - `@&A` or `@V` which produces a new `Array` +/// - `@O` which consumes `O`, updates it with the result, and returns it /// /// ## Broadcasting /// From e1e825b58d5e8bb0ecbae13bd165ff426af6957f Mon Sep 17 00:00:00 2001 From: Jim Turner Date: Fri, 18 Oct 2019 16:16:11 -0400 Subject: [PATCH 2/2] Generalize arithmetic op impls --- src/impl_ops.rs | 180 +++++++++++------------------------------------- src/lib.rs | 32 ++++----- tests/array.rs | 10 +-- 3 files changed, 57 insertions(+), 165 deletions(-) diff --git a/src/impl_ops.rs b/src/impl_ops.rs index 02eee8675..23ccd6ddb 100644 --- a/src/impl_ops.rs +++ b/src/impl_ops.rs @@ -53,9 +53,7 @@ macro_rules! impl_binary_op( /// Perform elementwise #[doc=$doc] /// between `self` and `rhs`, -/// and return the result (based on `self`). -/// -/// `self` must be an `Array` or `ArcArray`. +/// and return the result. /// /// If their shapes disagree, `rhs` is broadcast to the shape of `self`. /// @@ -64,13 +62,13 @@ impl $trt> for ArrayBase where A: Clone + $trt, B: Clone, - S: DataOwned + DataMut, + S: Data, S2: Data, D: Dimension, E: Dimension, { - type Output = ArrayBase; - fn $mth(self, rhs: ArrayBase) -> ArrayBase + type Output = Array; + fn $mth(self, rhs: ArrayBase) -> Array { self.$mth(&rhs) } @@ -79,7 +77,7 @@ where /// Perform elementwise #[doc=$doc] /// between `self` and reference `rhs`, -/// and return the result (based on `self`). +/// and return the result. /// /// If their shapes disagree, `rhs` is broadcast to the shape of `self`. /// @@ -88,18 +86,19 @@ impl<'a, A, B, S, S2, D, E> $trt<&'a ArrayBase> for ArrayBase where A: Clone + $trt, B: Clone, - S: DataOwned + DataMut, + S: Data, S2: Data, D: Dimension, E: Dimension, { - type Output = ArrayBase; - fn $mth(mut self, rhs: &ArrayBase) -> ArrayBase + type Output = Array; + fn $mth(self, rhs: &ArrayBase) -> Array { - self.zip_mut_with(rhs, |x, y| { + let mut lhs = self.into_owned(); + lhs.zip_mut_with(rhs, |x, y| { *x = x.clone() $operator y.clone(); }); - self + lhs } } @@ -129,67 +128,23 @@ where /// Perform elementwise #[doc=$doc] -/// between references `self` and `rhs`, -/// and return the result as a new `Array`. -/// -/// If their shapes disagree, `rhs` is broadcast to the shape of `self`. -/// -/// **Panics** if broadcasting isn’t possible. -impl<'a, 'b, A, B, S2, D, E> $trt<&'b ArrayBase> for ArrayView<'a, A, D> -where - A: Clone + $trt, - B: Clone, - S2: Data, - D: Dimension, - E: Dimension, -{ - type Output = Array; - fn $mth(self, rhs: &'b ArrayBase) -> Array { - // FIXME: Can we co-broadcast arrays here? And how? - self.to_owned().$mth(rhs) - } -} - -/// Perform elementwise -#[doc=$doc] -/// between references `self` and `rhs`, +/// between `self` and `rhs`, /// and return the result as a new `Array`. /// /// If their shapes disagree, `rhs` is broadcast to the shape of `self`. /// /// **Panics** if broadcasting isn’t possible. -impl<'a, 'b, A, B, S, D, E> $trt> for &'a ArrayBase +impl<'a, A, B, S, S2, D, E> $trt> for &'a ArrayBase where A: Clone + $trt, B: Clone, S: Data, + S2: Data, D: Dimension, E: Dimension, { type Output = Array; - fn $mth(self, rhs: ArrayView<'b, B, E>) -> Array { - // FIXME: Can we co-broadcast arrays here? And how? - self.to_owned().$mth(rhs) - } -} - -/// Perform elementwise -#[doc=$doc] -/// between references `self` and `rhs`, -/// and return the result as a new `Array`. -/// -/// If their shapes disagree, `rhs` is broadcast to the shape of `self`. -/// -/// **Panics** if broadcasting isn’t possible. -impl<'a, 'b, A, B, D, E> $trt> for ArrayView<'a, A, D> -where - A: Clone + $trt, - B: Clone, - D: Dimension, - E: Dimension, -{ - type Output = Array; - fn $mth(self, rhs: ArrayView<'b, B, E>) -> Array { + fn $mth(self, rhs: ArrayBase) -> Array { // FIXME: Can we co-broadcast arrays here? And how? self.to_owned().$mth(rhs) } @@ -198,21 +153,20 @@ where /// Perform elementwise #[doc=$doc] /// between `self` and the scalar `x`, -/// and return the result (based on `self`). -/// -/// `self` must be an `Array` or `ArcArray`. +/// and return the result. impl $trt for ArrayBase where A: Clone + $trt, - S: DataOwned + DataMut, + S: Data, D: Dimension, B: ScalarOperand, { - type Output = ArrayBase; - fn $mth(mut self, x: B) -> ArrayBase { - self.unordered_foreach_mut(move |elt| { + type Output = Array; + fn $mth(self, x: B) -> Array { + let mut lhs = self.into_owned(); + lhs.unordered_foreach_mut(move |elt| { *elt = elt.clone() $operator x.clone(); }); - self + lhs } } @@ -231,21 +185,6 @@ impl<'a, A, S, D, B> $trt for &'a ArrayBase self.to_owned().$mth(x) } } - -/// Perform elementwise -#[doc=$doc] -/// between the reference `self` and the scalar `x`, -/// and return the result as a new `Array`. -impl<'a, A, D, B> $trt for ArrayView<'a, A, D> - where A: Clone + $trt, - D: Dimension, - B: ScalarOperand, -{ - type Output = Array; - fn $mth(self, x: B) -> Array { - self.to_owned().$mth(x) - } -} ); ); @@ -266,17 +205,17 @@ macro_rules! impl_scalar_lhs_op { // these have no doc -- they are not visible in rustdoc // Perform elementwise // between the scalar `self` and array `rhs`, -// and return the result (based on `self`). +// and return the result. impl $trt> for $scalar - where S: DataOwned + DataMut, + where S: Data, D: Dimension, { - type Output = ArrayBase; - fn $mth(self, rhs: ArrayBase) -> ArrayBase { + type Output = Array<$scalar, D>; + fn $mth(self, rhs: ArrayBase) -> Array<$scalar, D> { if_commutative!($commutative { rhs.$mth(self) } or {{ - let mut rhs = rhs; + let mut rhs = rhs.into_owned(); rhs.unordered_foreach_mut(move |elt| { *elt = self $operator *elt; }); @@ -301,23 +240,6 @@ impl<'a, S, D> $trt<&'a ArrayBase> for $scalar }) } } - -/// Perform elementwise -/// between the scalar `self` and array `rhs`, -/// and return the result as a new `Array`. -impl<'a, D> $trt> for $scalar -where - D: Dimension, -{ - type Output = Array<$scalar, D>; - fn $mth(self, rhs: ArrayView<'a, $scalar, D>) -> Array<$scalar, D> { - if_commutative!($commutative { - rhs.$mth(self) - } or { - self.$mth(rhs.to_owned()) - }) - } -} ); } @@ -393,16 +315,17 @@ mod arithmetic_ops { impl Neg for ArrayBase where A: Clone + Neg, - S: DataOwned + DataMut, + S: Data, D: Dimension, { - type Output = Self; + type Output = Array; /// Perform an elementwise negation of `self` and return the result. - fn neg(mut self) -> Self { - self.unordered_foreach_mut(|elt| { + fn neg(self) -> Array { + let mut array = self.into_owned(); + array.unordered_foreach_mut(|elt| { *elt = -elt.clone(); }); - self + array } } @@ -420,32 +343,20 @@ mod arithmetic_ops { } } - impl<'a, A, D> Neg for ArrayView<'a, A, D> - where - for<'b> &'b A: Neg, - D: Dimension, - { - type Output = Array; - /// Perform an elementwise negation of reference `self` and return the - /// result as a new `Array`. - fn neg(self) -> Array { - self.map(Neg::neg) - } - } - impl Not for ArrayBase where A: Clone + Not, - S: DataOwned + DataMut, + S: Data, D: Dimension, { - type Output = Self; + type Output = Array; /// Perform an elementwise unary not of `self` and return the result. - fn not(mut self) -> Self { - self.unordered_foreach_mut(|elt| { + fn not(self) -> Array { + let mut array = self.into_owned(); + array.unordered_foreach_mut(|elt| { *elt = !elt.clone(); }); - self + array } } @@ -462,19 +373,6 @@ mod arithmetic_ops { self.map(Not::not) } } - - impl<'a, A, D> Not for ArrayView<'a, A, D> - where - for<'b> &'b A: Not, - D: Dimension, - { - type Output = Array; - /// Perform an elementwise unary not of reference `self` and return the - /// result as a new `Array`. - fn not(self) -> Array { - self.map(Not::not) - } - } } mod assign_ops { diff --git a/src/lib.rs b/src/lib.rs index 38283f6ed..ca63b8dd8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -607,17 +607,13 @@ pub type Ixs = isize; /// /// ### Binary Operators with Two Arrays /// -/// Let `A` be an array or view of any kind. Let `O` be an array -/// with owned storage (either `Array` or `ArcArray`). -/// Let `M` be an array with mutable data (either `Array`, `ArcArray` -/// or `ArrayViewMut`). Let `V` be an `ArrayView`. -/// The following combinations of operands -/// are supported for an arbitrary binary operator denoted by `@` (it can be -/// `+`, `-`, `*`, `/` and so on). -/// -/// - `&A @ &A`, `&A @ V`, `V @ &A`, or `V @ V` which produce a new `Array` -/// - `O @ A` which consumes `O`, updates it with the result, and returns it -/// - `O @ &A` which consumes `O`, updates it with the result, and returns it +/// Let `A` be an array or view of any kind. Let `M` be an array with mutable +/// data (either `Array`, `ArcArray` or `ArrayViewMut`). The following +/// combinations of operands are supported for an arbitrary binary operator +/// denoted by `@` (it can be `+`, `-`, `*`, `/` and so on). +/// +/// - `&A @ &A` or `&A @ A` which produce a new `Array` +/// - `A @ &A` or `A @ A` which may reuse the allocation of the LHS if it's an owned array /// - `M @= &A` or `M @= A` which performs an arithmetic operation in place on `M` /// /// Note that the element type needs to implement the operator trait and the @@ -646,19 +642,17 @@ pub type Ixs = isize; /// are supported (scalar can be on either the left or right side, but /// `ScalarOperand` docs has the detailed condtions). /// -/// - `&A @ K`, `V @ K`, `K @ &A`, or `K @ V` which produces a new `Array` -/// - `O @ K` or `K @ O` which consumes `O`, updates it with the result and returns it +/// - `&A @ K` or `K @ &A` which produces a new `Array` +/// - `A @ K` or `K @ A` which may reuse the allocation of the array if it's an owned array /// - `M @= K` which performs an arithmetic operation in place /// /// ### Unary Operators /// -/// Let `A` be an array or view of any kind. Let `O` be an array with owned -/// storage (either `Array` or `ArcArray`). Let `V` be an `ArrayView`. The -/// following operands are supported for an arbitrary unary operator denoted by -/// `@` (it can be `-` or `!`). +/// The following operands are supported for an arbitrary unary operator +/// denoted by `@` (it can be `-` or `!`). /// -/// - `@&A` or `@V` which produces a new `Array` -/// - `@O` which consumes `O`, updates it with the result, and returns it +/// - `@&A` which produces a new `Array` +/// - `@A` which may reuse the allocation of the array if it's an owned array /// /// ## Broadcasting /// diff --git a/tests/array.rs b/tests/array.rs index ea374bc7c..4c997798d 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -394,11 +394,11 @@ fn test_add() { } let B = A.clone(); - A = A + &B; - assert_eq!(A[[0, 0]], 0); - assert_eq!(A[[0, 1]], 2); - assert_eq!(A[[1, 0]], 4); - assert_eq!(A[[1, 1]], 6); + let C = A + &B; + assert_eq!(C[[0, 0]], 0); + assert_eq!(C[[0, 1]], 2); + assert_eq!(C[[1, 0]], 4); + assert_eq!(C[[1, 1]], 6); } #[test]