[Ndarray] Inplace operation

Hi everybody,

I was wondering how to do inplace operation in rust using the ndarray library.

by doing so

use ndarray::array;
fn main(
)
{

  let x = array!([1.0]);
  let y = array!([2.0]);

  x += y;
   
}



I have got the following errors:

error[E0271]: type mismatch resolving `<OwnedRepr<{float}> as RawData>::Elem == ArrayBase<OwnedRepr<{float}>, Dim<[usize; 2]>>`
  --> src/main.rs:40:5
   |
40 |   x +=y;
   |     ^^ expected floating-point number, found struct `ArrayBase`
   |
   = note: expected type `{float}`
            found struct `ArrayBase<OwnedRepr<{float}>, Dim<[usize; 2]>>`
   = note: required because of the requirements on the impl of `AddAssign<ArrayBase<OwnedRepr<{float}>, Dim<[usize; 2]>>>` for `ArrayBase<OwnedRepr<{float}>, Dim<[usize; 2]>>`

error[E0277]: the trait bound `ArrayBase<OwnedRepr<{float}>, Dim<[usize; 2]>>: ScalarOperand` is not satisfied
  --> src/main.rs:40:5
   |
40 |   x +=y;
   |     ^^ the trait `ScalarOperand` is not implemented for `ArrayBase<OwnedRepr<{float}>, Dim<[usize; 2]>>

If I understand the errors correctly it is only possible with the trait scalar operand like f32.

I am pretty sure it is possible to this addition in place but can't figure out how :sweat_smile:

Thanks in advance if anyone has the solution.

fn main() {
    let mut x = array!([1.0]);
    let y = array!([2.0]);
    x += &y;
}

Admitted, the error message was pretty bad, since it didn’t mention the other AddAssign implementation which was the one you actually wanted.

1 Like

Well indeed it works that way ! Thanks a lot.
It is true that the error message was missleading, I tried to search in the source code as well and in the docs but did not succed :pensive:

The source code might be a bit hard to search because of the use of lots of macros. But in the docs, you can find it (as you can see from the link provided).

By the way, ordinary addition should also be implemented in-place if (one of) the argument(s) is provided as an owned argument. E.g. let z = x + y; or let z = x + &y;.

3 Likes

thanks, it is good to now. Any place in the docs where it is detailed which operation will be in-place or not ?

I wouldn’t know about a place where it’s documented explicitly, but generally if you have a function receiving and returning an owned argument, you can assume it operates by modifying the argument if it’s decently implemented; if the modification doesn’t do something like a resize, etc. and the types involved aren’t representing immutable data, the modification should generally happen in-place.


In the case of ndarray, the situation is also highlighted in the types because they contain generic arguments specifying the kind of ownership/representation you have:

impl<'a, A, B, S, S2, D, E> Add<&'a ArrayBase<S2, E>> for ArrayBase<S, D>
where
    A: Clone + Add<B, Output = A>,
    B: Clone,
    S: DataOwned<Elem = A> + DataMut,
    S2: Data<Elem = B>,
    D: Dimension + DimMax<E>,
    E: Dimension,
{
    type Output = ArrayBase<S, <D as DimMax<E>>::Output>;

    fn add(self, rhs: &ArrayBase<S2, E>) -> Self::Output { … }
}

impl<'a, A, B, S, S2, D, E> Add<&'a ArrayBase<S2, E>> for &'a ArrayBase<S, D>
where
    A: Clone + Add<B, Output = A>,
    B: Clone,
    S: Data<Elem = A>,
    S2: Data<Elem = B>,
    D: Dimension + DimMax<E>,
    E: Dimension,
{
    type Output = Array<A, <D as DimMax<E>>::Output>;
    fn add(self, rhs: &'a ArrayBase<S2, E>) -> Self::Output { … }
}

impl<A, B, S, S2, D, E> Add<ArrayBase<S2, E>> for ArrayBase<S, D>
where
    A: Clone + Add<B, Output = A>,
    B: Clone,
    S: DataOwned<Elem = A> + DataMut,
    S2: Data<Elem = B>,
    D: Dimension + DimMax<E>,
    E: Dimension,
{
    type Output = ArrayBase<S, <D as DimMax<E>>::Output>;

    fn add(self, rhs: ArrayBase<S2, E>) -> Self::Output { … }
}
impl<'a, A, B, S, S2, D, E> Add<ArrayBase<S2, E>> for &'a ArrayBase<S, D>
where
    A: Clone + Add<B, Output = B>,
    B: Clone,
    S: Data<Elem = A>,
    S2: DataOwned<Elem = B> + DataMut,
    D: Dimension,
    E: Dimension + DimMax<D>,
{
    type Output = ArrayBase<S2, <E as DimMax<D>>::Output>;

    fn add(self, rhs: ArrayBase<S2, E>) -> Self::Output { … }
}

Looking at Output types, you see that Add<&'a ArrayBase<S2, E>> for ArrayBase<S, D> and Add<ArrayBase<S2, E>> for ArrayBase<S, D> output ArrayBase<S, <D as DimMax<E>>::Output>, so this will be modifying the first argument (if possible), and Add<ArrayBase<S2, E>> for &'a ArrayBase<S, D> outputs ArrayBase<S2, <E as DimMax<D>>::Output> (note the S2!), so it’s modifying the second argument in-place (if possible); Add<&'a ArrayBase<S2, E>> for &'a ArrayBase<S, D> outputs Array<A, <D as DimMax<E>>::Output>;, which is always a newly created owned array, it doesn’t use the S or S2 argument. I don’t quite understand the nuances of broadcasting in ndarray, so I can’t tell you what happens if the shapes don’t agree.

1 Like

Thanks, that is a great explanation !

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.