rust-ndarray / ndarray

ndarray: an N-dimensional array with array views, multidimensional slicing, and efficient operations

Home Page:https://docs.rs/ndarray/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Inconsistency in adding ArrayView and Array

rkshthrmsh opened this issue · comments

While trying to add an ArrayBase<ViewRepr<&i32>, Dim<[usize; 1]>> and an ArrayBase<OwnedRepr<i32>, Dim<[usize; 1]>>, in the following code, I am getting the error shown below:

    fn update_weight_vec(&mut self, U: ArrayBase<ViewRepr<&i32>, Dim<[usize; 1]>>) {
        let mut W_z_tAdd1 = self.W.slice(s![z, ..]) + U.to_owned(); // shapes are equivalent as seen in the error message below.
    }

Error:

   Compiling rust v0.1.
error[E0369]: cannot add `ArrayBase<OwnedRepr<i32>, Dim<[usize; 1]>>` to `ArrayBase<ViewRepr<&i32>, Dim<[usize; 1]>>`
   --> src/main.rs:418:53
    |
418 |         let mut W_z_tAdd1 = self.W.slice(s![z, ..]) + U.to_owned();
    |                             ----------------------- ^ ------------ ArrayBase<OwnedRepr<i32>, Dim<[usize; 1]>>
    |                             |
    |                             ArrayBase<ViewRepr<&i32>, Dim<[usize; 1]>>

However, attempting the same addition between the two Repr types elsewhere seems to work fine.

fn main() {
    let mut n = Array2::<i32>::ones((2, 4));
    let m = Array1::<i32>::ones(6);
    let m_sliced = m.slice(s![..4]);
    let n_sliced = n.slice(s![0, ..]);
    let mut s = m_sliced.to_owned() + n_sliced; // works fine
}

Could you please help me understand what is happening here?

Exploring this some more, it appears that addition between ArrayBase<ViewRepr<&i32>, Dim<[usize; 1]>> and an ArrayBase<OwnedRepr<i32>, Dim<[usize; 1]>> is not commutative. The following worked:

fn update_weight_vec(&mut self, U: ArrayBase<ViewRepr<&i32>, Dim<[usize; 1]>>) {
        // let mut W_z_tAdd1 = self.W.slice(s![z, ..]) + U.to_owned(); causes error
        let mut W_z_tAdd1 = U.to_owned() + self.W.slice(s![z, ..]); // this works
    }

I don't have the exact technical answer you want, but here's some details

  1. You can use ArrayView1<i32> instead of ArrayBase<ViewRepr<&i32>, Dim<[usize; 1]>>). In fact, ArrayView1 is an alias to the looong type you use.
  2. You don't have to call to_owned each time you want to use math operations. I know that some operations are forbidden, like view op view, but they can be valid if you use &view op &view, like &U + &self.W.slice(s![z, ..])
  3. My understanding is that without the &, the array is consumed (and thus the memory is reused for the result). With the &, ndarray understands that it needs to allocate a new array. Please read this section of the documentation for more information.

Thank you, @nilgoyette.

  1. Yes, I have now aliased with ArrayView1 and the code looks much cleaner.
  2. In this usecase, to_owned is necessary for Rust's borrow-checker. The code snippet here is a smaller part of another program.
  3. Right, so it is related to memory handling. However, this behaviour still seems counter-intiutive since most users would expect addition to be commutative. Maybe this could be mentioned in the docs somewhere?

I haven't see your complete code, but you should never have to call to_owned to do math. You can apply all math operations on a view. Simply use the & when needed.

As for the "commutative" point. It's mentioned in the doc, right in the link I sent you

// let sum2 = view1 + &view2; // This doesn't work because `view1` is not an owned array.

IIUC, view1, being a view, can be consumed (the struct can be), but it's memory can't be reused to store the result of the + operation because it doesn't own the memory, so view1 + &view2 is forbidden. The first array must either be

  • owned (and consumed)
  • referenced (&) to explicitly ask ndarray to allocate (instead of reusing the memory)

I guess we could explain the reason more clearly in the doc.

Thanks for clearing it up, @nilgoyette. :)
Closing this issue now.