Rustで線形代数 (ndarray-linalgの使い方)

ndarray-linalg 0.9.0のリリース記念に簡単な使い方をまとめます

GitHub: https://github.com/termoshtt/ndarray-linalg
docs.rs: https://docs.rs/ndarray-linalg/0.9.0/ndarray_linalg/

Cargo.toml
-----------

基本的には[rust-ndarray]と組み合わせて使います

```toml:Cargo.toml
[dependencies]
ndarray = "0.11"
ndarray-linalg = { version = "0.9", features = ["openblas"] }
```

featureにはLAPACKのバックエンドを入れます。一つだけ指定しないとエラーになります。現在対応してるのは以下の通り：

- OpenBLAS (`openblas`)
- Netlib (`netlib`)
- Accelerate (`accelerate`)
- Intel MKL (`intel-mkl`)

ndarray-linalgの使い方
======================

```rust:lib.rs
#[macro_use]
extern crate ndarray;
#[macro_use]
extern crate ndarray_linalg;

use ndarray::*;
use ndarray_linalg::*;
```

以下名前空間を省略してます

複素数 `c32`, `c64`
------

https://docs.rs/ndarray-linalg/0.9.0/ndarray_linalg/types/index.html

```rust
type c32 = Complex<f32>;
type c64 = Complex<f64>;
```

Complexは[num-complex]のものです

Generate
-------------------------
https://docs.rs/ndarray-linalg/0.9.0/ndarray_linalg/generate/index.html

```rust
let a: Array3<f64> = ndarray_linalg::random((3, 2, 3));
let a: Array1<c64> = ndarray_linalg::random(10);
let h: Array2<f64> = ndarray_linalg::random_hermite(5);
let h: Array2<f64> = ndarray_linalg::random_hpd(5);
```

乱数で多次元配列を初期化します。主に初期化・テスト用です。`random_hpd`はHermite Positive Definiteの略です。
戻り値の型で判定するので推定できない場合はアノテーション付ける必要があります。

Norm
-----
https://docs.rs/ndarray-linalg/0.9.0/ndarray_linalg/norm/trait.Norm.html

```rust
let a = ndarray_linalg::random((3, 3, 4));
a.norm_l1();  // L1-norm
a.norm_l2();  // L2-norm
a.norm_max(); // 最大値ノルム
```

全要素の絶対和(L1)二乗和(L2)最大値のいずれかでノルムを取ります。任意の次元で使えます。
行列の場合は作用素ノルムも使えます：

https://docs.rs/ndarray-linalg/0.9.0/ndarray_linalg/opnorm/trait.OperationNorm.html

```rust
let a = ndarray_linalg::random((3, 3));
a.opnorm_one();
a.opnorm_inf();
a.opnorm_fro();
```


QR分解
----

https://docs.rs/ndarray-linalg/0.9.0/ndarray_linalg/qr/index.html

```rust
let a: Array2<f64> = ndarray_linalg::random((5,4));
let (q, r) = a.qr().unwrap();
```

これは`a`を非破壊で結果を新しい配列(`Array2<f64>`)に入れます。正方行列でなくても使えます。


```rust
let a: Array2<f64> = ndarray_linalg::random((5,4));
let (q, r) = a.qr_into().unwrap();
```

`qr_into()`にすると`a`をmoveします。配列の確保が減ります。


```rust
let a: Array2<f64> = ndarray_linalg::random((10, 10));
let (q, r) = a.qr_square().unwrap();
```

正方行列を仮定してる場合です。ちょっとアルゴリズムが簡単になります。`a`は消費しません。`qr_square_into()`にすると消費します。消費する/しないは引数が`self`(消費する)か`&self`(消費しない)を見て判断します。

```rust
let mut a: Array2<f64> = ndarray_linalg::random((10, 10));
let (q, r) = a.qr_square_inplace().unwrap();
```

最後にinplaceで変換するタイプです。`r`は新しく生成します。`q`には`a`の`&mut`が入っています。

このように`a.method()`の形で基本的に呼び出せるようにしてあって、元の行列を消費する・しないをメソッド名で決定します。

SVD
----
https://docs.rs/ndarray-linalg/0.9.0/ndarray_linalg/svd/trait.SVD.html

```rust
let a: Array2<f64> = ndarray_linalg::random((10, 10));
let (u, s, vt) = a.svd(True, True).unwrap();
```

基本的にはQR分解と一緒ですが、特異値分解 $A = U\Sigma V^T$で$U$と$V^T$を計算しない選択肢があるのでそれを`bool`であげます。なので`u`, `vt`は`Option<Array2<f64>>`とかになります。これも`svd_into(...)`, `svd_inplace(...)`があります。

Solve(h)
------
https://docs.rs/ndarray-linalg/0.9.0/ndarray_linalg/solve/index.html

```rust
let a: Array2<f64> = ndarray_linalg::random((3,3));
let b: Array1<f64> = ndarray_linalg::random(3);
let x = a.solve_into(b).unwrap();
```

線形方程式 $Ax = b$を解きます。LU分解で解きますが複数$b$がある場合$A$の分解を記憶しておく方がいいの、その場合は以下のように明示的に分解します：

```rust
let a: Array2<f64> = random((3, 3));
let f = a.factorize_into().unwrap(); // LU factorize A (A is consumed)
for _ in 0..10 {
    let b: Array1<f64> = random(3);
    let x = f.solve_into(b).unwrap(); // Solve A * x = b using factorized L, U
}
```

Hermite（あるいは実対称）行列の場合は同じインターフェースが`solveh`モジュールに用意されています。
https://docs.rs/ndarray-linalg/0.9.0/ndarray_linalg/solveh/index.html


最後に
------
他にもいくつか機能があります！（飽きた(´・ω・｀)
続きはWEBで！ https://docs.rs/ndarray-linalg/0.9.0/ndarray_linalg/index.html

[num-complex]: https://docs.rs/num-complex/0.1.41/num_complex/struct.Complex.html
[rust-ndarray]: https://github.com/bluss/rust-ndarray