Implement row by row operation using ndarray

Viewed 318

I wanted to perform a loop operation that would do the following operation (operations must be element by element).

let mut spec = array![] // matrix mxn
let exponencial = array![] // 1xn

for k in 0..N {
    spectrum [k, ..] = spectrum [k, ..] * exponential;
}

How could I implement this operation correctly and efficiently? My intention is to implement it in a loop to compare with the implementation with other languages. Do I have to use some intermediate temporary array?

Attempt 1

use ndarray::prelude::*; // 0.13.1

fn main() {
    // All rows in same time
    let mut spec = array![[1, 2, 3, 4], [5, 6, 7, 8]]; // mxn
    let exponencial = array![[1, 1, 1, 1]]; // matrix 1xn

    let _result = spec.assign(&(&spec * &exponencial));
    // or spec = &spec*&exponencial;

    //  In the form of a loop. one row each iteration.
    // This is the implementation that I want to make to be able
    // to implement it to be able to compare with other languages
    // 1)
    let mut spec = array![[1, 2, 3, 4], [5, 6, 7, 8]]; // mxn
    let exponencial = array![[1, 1, 1, 1]]; // matrix 1xn
    spec.slice_mut(s![0, ..])
        .assign(&(&(spec.row(0)) * &exponencial.row(0)));
}

playground

error[E0502]: cannot borrow `spec` as immutable because it is also borrowed as mutable
  --> src/main.rs:18:21
   |
17 |     spec.slice_mut(s![0, ..])
   |     ---- mutable borrow occurs here
18 |         .assign(&(&(spec.row(0)) * &exponencial.row(0)));
   |          ------     ^^^^ immutable borrow occurs here
   |          |
   |          mutable borrow later used by call

Attempt 2

use ndarray::prelude::*; // 0.13.1

fn main() {
    // All rows in same time
    let mut spec = array![[1, 2, 3, 4], [5, 6, 7, 8]]; // mxn
    let exponencial = array![[1, 1, 1, 1]]; // matrix 1xn

    let result = spec.assign(&(&spec * &exponencial));
    // or spec = &spec*&exponencial;

    //  In the form of a loop. one row each iteration.
    // This is the implementation that I want to make to be able
    // to implement it to be able to compare with other languages
    // 2)
    let mut spec = array![[1, 2, 3, 4], [5, 6, 7, 8]]; // mxn
    let exponencial = array![[1, 1, 1, 1]]; // matrix 1xn
    spec.row_mut(0) = &spec.row(0) * &exponencial.row(0);
}

playground

error[E0308]: mismatched types
  --> src/main.rs:17:23
   |
17 |     spec.row_mut(0) = &spec.row(0) * &exponencial.row(0);
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `ndarray::ViewRepr`, found struct `ndarray::OwnedRepr`
   |
   = note: expected struct `ndarray::ArrayBase<ndarray::ViewRepr<&mut {integer}>, _>`
              found struct `ndarray::ArrayBase<ndarray::OwnedRepr<{integer}>, _>`

error[E0070]: invalid left-hand side of assignment
  --> src/main.rs:17:21
   |
17 |     spec.row_mut(0) = &spec.row(0) * &exponencial.row(0);
   |     --------------- ^
   |     |
   |     cannot assign to this expression
2 Answers

In addition to bnaecker's answer, if you want to implement it in a loop you could iterate along the axis using axis_iter_mut():

use ndarray::prelude::*;

fn main() {
    let mut spec = array![
        [1, 2, 3, 4],
        [5, 6, 7, 8]
    ];
    
    let expo = array![2, 2, 2, 2];
    
    for mut row in spec.axis_iter_mut(Axis(0)) {
        row *= &expo;
    }
    
    println!("{}", spec);
}
[[2, 4, 6, 8],
 [10, 12, 14, 16]]

I'm assuming that the first (pseudocode) loop you wrote is for k in 0..m, given that the size of your matrix is m-by-n and you want to apply this operation to each row.

If that's the case, then reading through the docs on binary operations and broadcasting, it looks like multiplying the arrays directly should be sufficient:

use ndarray::prelude::*; // 0.13.1

fn main() {
    // All rows in same time
    let spec = array![[1, 2, 3, 4], [5, 6, 7, 8]]; // mxn
    let exponencial = array![[2, 2, 2, 2]]; // matrix 1xn
    println!("{:?}", spec * exponencial);
}

playground

You can see the output has each row doubled:

[[2, 4, 6, 8],
 [10, 12, 14, 16]], shape=[2, 4], strides=[4, 1], layout=C (0x1), const ndim=2

This works by broadcasting, which means operating as if the 1-by-n array exponencial actually has shape m-by-n, by effectively replicating the array along the singleton dimension. This awesome trick was inspired by NumPy, which uses broadcasting heavily to compactly and efficiently express this kind of element-wise product.

Related