[Rust] ndarray入門

# はじめに
ndarrayについてあまり記事がありませんでしたので，困った部分を残しておきます．

Nalgebraとの比較については，拙著
[[Rust] Nalgebra入門(ndarrayとの比較付き)](https://qiita.com/ttttkkkkk31525/items/613156f9007c84de1ede)
をご覧ください．
別の[cgmath](https://docs.rs/crate/cgmath/0.18.0)というクレートも次元を固定して扱えるので，3〜4次元程度しか扱わなければ便利そうですね．クォータニオンも扱えるらしいですし．

ndarrayは，[NumPyのnpyファイルを扱えるようになるクレート](https://docs.rs/ndarray-npy/0.7.1/ndarray_npy/)や[画像ファイルからの変換クレート](https://docs.rs/ndarray-image/0.3.0/ndarray_image/)など，いろいろなクレートがあって便利ですね！

ndarrayをNumPyと比較しながら入門するには[**ndarray::doc::ndarray_for_numpy_users**](https://docs.rs/ndarray/0.14.0/ndarray/doc/ndarray_for_numpy_users/index.html)が一番だと思いますが，それでは困ったことが多かったので...

ndarrayで線形代数を扱うためのクレート ndarray-linalgについては,[Rustで線形代数 (ndarray-linalgの使い方)](https://qiita.com/termoshtt/items/824684a37a2ec15dbfb9)が詳しいです.

余談ですが、ndarrayは[num](https://docs.rs/num/0.3.1/num/)系のクレートに依存しているので、numとの相性はよさそうです．

# Arrayの初期化
自分でも書こうかと思いましたが，
[[Rust] ndarray で行列を定義する方法一覧](https://qiita.com/osanshouo/items/7342d92a28d2df0b259a)で書かれているので十二分だと思います(ありがとうございます)．

結局`array!`マクロを使うのが一番な気がします．
それから，`Array::from_vec()`がduplicatedになっているので，`Array::from()`か`Vec`の`into()`メソッドを使いましょう．

# 要素へのアクセス
`a[[1, 2]]`とか，`a[(1, 2)]`と配列でもタプルでもできますが，[**ndarray::doc::ndarray_for_numpy_users**](https://docs.rs/ndarray/0.14.0/ndarray/doc/ndarray_for_numpy_users/index.html)では`[[1, 2]]`なので，配列を与えた方がいいのでしょうか．

(片方に統一してほしい．両方あるのは，一時Rustで`[(1, 2)]`を`[1, 2]`で表していいよ見たいな話があったからでしょうか．わからない)

# shape
`shape()`メソッドで`&[usize]`で得られます．

# 1次元配列の長さ
`len()` で取得できます．ドキュメントを探すのに苦労しました．2Dは書いてあるのに，1Dは特別なメソッドがないからという理由で埋もれていました...
# 2次元配列のshape
`nrows(), ncols()`です

# Vec to Array
`Array::from_shape_vec(shape, v)`が便利です
`shape`には`(usize, usize)`などのタプルを渡せばOKです．

```rust
let arr = Array::from_shape_vec((2, 3), vec![0, 1, 2, 3, 4, 5]).unwrap();
assert_eq!(arr, array![[0, 1, 2],
 [3, 4, 5]]);
```

# slice
`s!`マクロ(https://docs.rs/ndarray/0.14.0/ndarray/macro.s.html)
を，`slice`，`slice_mut`，`slice_move`メソッドに適用することでできます．NumPyのように`arr[1:3:-1]`のようなスライスも`arr.slice(s![1..3;-1])`とすることで扱えます．

```rust
let arr = array![0, 1, 2, 3];
assert_eq!(arr.slice(s![1..3;-1]), array![2, 1]);
assert_eq!(arr.slice(s![1..;-2]), array![3, 1]);
assert_eq!(arr.slice(s![0..4;-2]), array![3, 1]);
assert_eq!(arr.slice(s![0..;-2]), array![3, 1]);
assert_eq!(arr.slice(s![..;-2]), array![3, 1]);
```

# Array to ArrayView / ArrayViewMut
`ArrayView`というのは`Array`を複製せずに値を参照したいときに使う型で，`&Array`より便利です．また，`slice`の返り値でもあります．
それの`mut`版が`ArrayViewMut`です．`&mut Array`より便利です．これは`slice_mut`の返り値です．
これらは`Copy`トレイトを実装しています(参照がコピーされるだけで実体はコピーされません)．
`Array`から`ArrayView`, `ArrayViewMut`を呼ぶには`view()`，`view_mut()`メソッドを使います．例えば

```rust
let mut arr = array![[1, 2, 3], [4, 5, 6]];
some_func(arr.view()); // some_func(av: ArrayView<_>)
mut_func(arr.view_mut()); // mut_func(mut av: ArrayViewMut<_>)
```

とします．より具体的には

```rust
fn main() {
    let mut arr = array![[1, 2], [3, 4]];
    mut_arr(arr.view_mut());
    assert_eq!(arr, array![[0, 2], [3, 4]]);
}

fn mut_arr(mut av: ArrayViewMut2<i32>) {
    av[[0, 0]] = 0;
}
```

です．動作は[play ground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2d1ba12dfb2df46d61ef8fcabef563e5)で確かめられます．

# ArrayView / ArrayViewMut to Array

逆に`ArrayView`，`ArrayViewMut`を`Array`に直すときは
[`to_owned()`](https://docs.rs/ndarray/0.14.0/ndarray/struct.ArrayBase.html#method.to_owned)を使います．同じような`into_owned()`もありますが，Rust標準の`to_owned()`がいいでしょう．

# ArrayViewMutに行ベクトル/列ベクトルを代入する

ndarrayのスライスは`b.slice_mut(s![.., 0])`（型は`ArrayViewMut<_>`）の形となるため，NumPyのように
`a[:, 0] = foo`のように代入することができません．そこで`assign`メソッドを使います．

```rust
b.slice_mut(s![i, ..]).assign(&array![0, 1, 2]]); // b[i, :] = np.array([0, 1, 2])
```

具体的には，列ベクトル$b_1,\cdots,b_n$について
$$
\mathtt{swap}([b_1,\cdots, b_n],\ i,\ k)=[b_1,\cdots b_{i-1},b_k,b_{i},\cdots,b_{k-1}, b_{k+1},\cdots,b_n]
$$
のようなケースの場合，以下のようにやります．

```rust
fn swap(mut b: ArrayViewMut2<i32>, i: usize, k: usize) {
  let row_i = b.slice(s![.., i]).to_owned();
  let tmp = b.slice(s![.., k]).to_owned();
  b.slice_mut(s![.., i]).assign(&tmp);
  for l in ((i + 1)..k).rev() {
    let tmp = b.slice(s![.., l]).to_owned();
    b.slice_mut(s![.., l + 1]).assign(&tmp);
  }
  b.slice_mut(s![.., i + 1]).assign(&row_i);
}
```


# map, mapvについて

```rust

arr.map(|a| *a > 0);
arr.map(|a: &i32| *a > 0); // 型を明示
arr.mapv(|a| a > 0);
arr.mapv(|a: i32| a > 0); // 型を明示
```
という感じです．mapは要素の参照(`&i32`など)，mapvは要素そのもの(`i32`など)を引数に取る関数を適用します．

要素ごとに関数を適用するような場合は

```rust
a.mapv(|a| a.powi(3)); // a ** 3 (NumPy)
a.mapv(f64::sqrt); // np.sqrt(a) (NumPy)
```
とします．

# 演算子について

NumPy同様で，`a * b`，`a * &b`，`&a * &b`などは要素ごとの積を表します．行列積は`a.dot(&b)`を使います．

# NumPyのreshape

NumPyの`reshape`は`into_shape`です．

```rust
use ndarray::{aview1, aview2};

assert!(
    aview1(&[1., 2., 3., 4.]).into_shape((2, 2)).unwrap()
    == aview2(&[[1., 2.],
                [3., 4.]])
);
```

# 転置
転置は`arr.t()`です．

# 逆行列

[ndarray-linalg](https://docs.rs/crate/ndarray-linalg/0.13.0)クレートを使います．
このクレートを使うときは，[crates.io](https://crates.io/crates/ndarray-linalg)にあるとおり，backendを指定するようにしましょう．


```toml:Cargo.toml
[dependencies]
ndarray = "0.14"
ndarray-linalg = { version = "0.13", features = ["openblas-static"] }
```

逆行列を計算するには，

```rust
use ndarray_linalg::solve::Inverse;
let inv_arr = Inverse::inv(&arr);
```
とします。`Result`にwrapされているので，よしなにエラー処理してください．

`inv()`メソッドを`Array`に生やすこともできそうですが，やり方がわかりませんでしたので，どなたか教えてください...

なぜかndarray-linalgを混ぜて使うと、`dot`メソッドが複数あると言われてしまうので

```rust
bf_arr.dot(&Inverse::inv(&af_arr).unwrap())
```
が使えないことがあり、

```rust
Array2::<f64>::dot(&bf_arr, &Inverse::inv(&af_arr).unwrap())
```
のようにする必要がありました．うーん...
(このせいで[cgmath](https://docs.rs/crate/cgmath/0.18.0)に浮気しそうになった)


# おまけ
[RustでDeepLearning入門](https://qiita.com/ta_to_co/items/c5686bc702b9255b9c06)
ここでは紹介しきれなかった，ndarrayの具体的な場合の扱い方が書いてあるのでおすすめです．

それから
[多次元配列、線形代数 (ndarray, ndarray-linalg crate)]
(https://zenn.dev/termoshtt/books/b4bce1b9ea5e6853cb07/viewer/ndarray_linalg)
よさそうな記事がZennに出ていたので．
SEOが良くなるともっと上に出てきそうですね！