背景
rustとは、速度、安全性、並列性を重視して作られたプログラミング言語です。ndarrayとは多次元配列を扱うためのライブラリです。
ndarrayで行列を扱うにあたり、データの作成や扱い方に戸惑ったので、備忘録としてメモを残します。
全体像
- 使ったもの
- 行列を作成
- ベクトルから行列を作成
- 行列からベクトルを作成
- 行列の一部を抽出
- 行列の転置
- 行列+行列
- 行列-行列
- 行列×行列
- 行列○行列
- 列の置換
- 行列を関数の引数として渡す
- まとめ
使ったもの
rustのをインストールしたPC
rustのインストールページに従ってインストールしました。動作環境はこちらです。
ubuntu: 17.10
rustc: rustc 1.22.1 (05e2e1c41 2017-11-22)
ndarrayを呼び出せるプロジェクト
実行可能なプロジェクト(下記のコマンドだとrust_ndarray_practice)をつくり、それの依存としてndarrayを設定しました。プロジェクトの作成
cargo new rust_ndarray_practice --bin
依存関係の記述
Cargo.tmol
[dependencies] ndarray = "0.11"
プロジェクトのディレクトリ内で下記のコマンドを実行すると、プログラムを動かせます。
cargo run
行列を作成
2次元の行列はarr2で作れます。extern crate ndarray; use ndarray::{arr2}; fn main() { let matrix = arr2(&[[1, 2, 3], [4, 5, 6], [7, 8, 9]]); println!("{:?}", matrix); }
実行するとこうなります。
[[1, 2, 3], [4, 5, 6], [7, 8, 9]] shape=[3, 3], strides=[3, 1], layout=C (0x1)
実行するとこうなります。
Are they same?: true
ベクトルから行列を作成
from_vecとinto_shapeを利用します。extern crate ndarray; use ndarray::{Array, arr2}; fn main() { let matrix1 = arr2(&[[1, 2, 3], [4, 5, 6], [7, 8, 9]]); let vector = vec!(1, 2, 3, 4, 5, 6, 7, 8, 9); let matrix2 = Array::from_vec(vector).into_shape((3,3)).unwrap(); let result = matrix1 == matrix2; println!("Are they same?: {:?}", result); }
行列からベクトルを作成
shape, into_shape, to_vecを利用します。to_vecは1次元の行列からしか呼べ無さそうなので、shapeで形を取得し、要素の長さの1次元行列に変換した後、to_vecを読んでいます。
shapeを使わずにvecに変換する方法をご存じでしたら、教えてもらえると嬉しいです。
extern crate ndarray; use ndarray::{arr2}; fn main() { let matrix = arr2(&[[1, 2, 3], [4, 5, 6], [7, 8, 9]]); let vector1 = vec!(1, 2, 3, 4, 5, 6, 7, 8, 9); let (w, h) = { let shape = matrix.shape(); (shape[1], shape[0]) }; let vector2 = matrix.into_shape(h*w).unwrap().to_vec(); let result = vector1 == vector2; println!("Are they same?: {:?}", result); }
実行するとこうなります。
Are they same?: true
行列の一部を抽出
sliceとs!を使うと、抽出できます。s!はsliceの範囲を表現するマクロです。
開始と終わり+1を..でつなげたレンジを、次元と同じ数与えます。
#[macro_use(s)] extern crate ndarray; use ndarray::{arr2}; fn main() { let matrix1 = arr2(&[[1, 2, 3], [4, 5, 6], [7, 8, 9]]); let matrix2 = arr2(&[[2, 3], [5, 6]]); let matrix3 = matrix1.slice(s![0..2, 1..3]); let result = matrix2 == matrix3; println!("Are they same?: {:?}", result); }
実行するとこうなります。
Are they same?: true
行列の転置
転置(transpose)とは、行と列を入れ替える操作です。reversed_axesという関数を利用すると、行列を転置できます。
extern crate ndarray; use ndarray::{arr2}; fn main() { let matrix = arr2(&[[1, 2, 3], [4, 5, 6], [7, 8, 9]]); let result = matrix.reversed_axes(); println!("{:?}", result); }
実行するとこうなります。
[[1, 4, 7], [2, 5, 8], [3, 6, 9]] shape=[3, 3], strides=[1, 3], layout=F (0x2)
行列+行列
行列の足し算はaddでできます。std::ops::Addをuseとして記述する必要がありました。
extern crate ndarray; use ndarray::{arr2}; use std::ops::Add; fn main() { let matrix1 = arr2(&[[1, 2, 3], [4, 5, 6], [7, 8, 9]]); let matrix2 = arr2(&[[1, 0, 0], [0, 1, 0], [0, 0, 2]]); let result = matrix1.add(&matrix2); println!("{:?}", result); }
実行するとこうなります。
[[2, 2, 3], [4, 6, 6], [7, 8, 11]] shape=[3, 3], strides=[3, 1], layout=C (0x1)
行列-行列
行列の引き算はsubでできます。std::ops::Subをuseとして記述する必要がありました。
extern crate ndarray; use ndarray::{arr2}; use std::ops::Sub; fn main() { let matrix1 = arr2(&[[1, 2, 3], [4, 5, 6], [7, 8, 9]]); let matrix2 = arr2(&[[1, 0, 0], [0, 1, 0], [0, 0, 2]]); let result = matrix1.sub(&matrix2); println!("{:?}", result); }
実行するとこうなります。
[[0, 2, 3], [4, 4, 6], [7, 8, 7]] shape=[3, 3], strides=[3, 1], layout=C (0x1)
行列×行列
行列の掛け算はdotでできます。extern crate ndarray; use ndarray::{arr2}; fn main() { let matrix1 = arr2(&[[1, 2, 3], [4, 5, 6], [7, 8, 9]]); let matrix2 = arr2(&[[1, 0, 0], [0, 1, 0], [0, 0, 2]]); let result = matrix1.dot(&matrix2); println!("{:?}", result); }
実行するとこうなります。
[[1, 2, 6], [4, 5, 12], [7, 8, 18]] shape=[3, 3], strides=[3, 1], layout=C (0x1)
行列○行列
行列の要素同士の掛け算はmulでできます。std::ops::Mulをuseとして記述する必要がありました。
extern crate ndarray; use ndarray::{arr2}; use std::ops::Mul; fn main() { let matrix1 = arr2(&[[1, 2, 3], [4, 5, 6], [7, 8, 9]]); let matrix2 = arr2(&[[1, 0, 0], [0, 1, 0], [0, 0, 2]]); let result = matrix1.mul(&matrix2); println!("{:?}", result); }
実行するとこうなります。
[[1, 0, 0], [0, 5, 0], [0, 0, 18]] shape=[3, 3], strides=[3, 1], layout=C (0x1)
列の置換
ndarrayには行や列の置換(permute、replace)を行ってくれる関数は無さそうでした。しかし、下記のどちらかの処理で行を置き換えられることが分かりました。
入れ替えたい列の要素を1つずつswapで入れ替える。
extern crate ndarray; use ndarray::{arr2}; fn main() { let mut matrix = arr2(&[[1, 2, 3], [4, 5, 6], [7, 8, 9]]); matrix.swap(s![0, 0..3], s![2, 0..3]); for i in 0..2 { matrix.swap((i, 0), (i, 2)); } println!("{:?}", matrix); }
置換のための行列を掛け合わせる。
extern crate ndarray; use ndarray::{arr2}; fn main() { let matrix1 = arr2(&[[1, 2, 3], [4, 5, 6], [7, 8, 9]]); let matrix2 = arr2(&[[0, 0, 1], [0, 1, 0], [1, 0, 0]]); let result = matrix1.dot(&matrix2); println!("{:?}", result); }
実行するとこうなります。
[[3, 2, 1], [6, 5, 4], [9, 8, 7]] shape=[3, 3], strides=[3, 1], layout=C (0x1)
行列を関数の引数として渡す
Array2に要素の型を記述したものが、2次元行列の型になります。0~255の値を使っているので、今回はu8型にしました。
extern crate ndarray; use ndarray::{arr2, Array2}; fn truncate_and_replace(matrix1: &Array2) -> Array2 { let matrix1 = matrix1.clone(); let matrix2 = arr2(&[[0, 0, 1], [0, 1, 0], [1, 0, 0]]); matrix1.reversed_axes().dot(&matrix2) } fn main() { let matrix = arr2(&[[1, 2, 3], [4, 5, 6], [7, 8, 9]]); let result = truncate_and_replace(&matrix); println!("{:?}", result); }
実行するとこうなります。
[[7, 4, 1], [8, 5, 2], [9, 6, 3]] shape=[3, 3], strides=[3, 1], layout=C (0x1)
まとめ
ndarrayを利用して、rustで行列の操作ができました。行列の基本変形としてwikipediaでも紹介されている行や列の交換に関する操作が、関数の機能として見つけられなかったのが疑問です。
該当する機能を知っている方が居れば、共有してもらえると嬉しいです。
ndarrayの他にもnalgebraという行列を操作に関するライブラリあるので試してみましたが、次元の他に行列のサイズも型に含まれるため、型による束縛が強いと個人的に感じました。
そのため、ndarrayの使い方だけ備忘録としてまとめてみました。
何かの参考になれば嬉しいです。
参考
概要理解の参考にしました。Rustによる数値計算: 線形代数編
ndarrayのドキュメントです。
Crate ndarray
ndarrayのサンプルプログラムです。
rust-ndarray/examples
今回の記事の内容は advent of code 2017 day21 を解くのに利用しました。
自分の回答です。
ndarray版
nalgebra版
更新情報
2018.02.11下記の項目を追加しました。
- ベクトルから行列を作成
- 行列からベクトルを作成
- 行列の一部を抽出
- 行列+行列
- 行列-行列
- 行列○行列
0 件のコメント :
コメントを投稿