線形代数
行列の加算
ndarray::arr2 を使用して 2 つの 2 次元行列を作成し、要素ごとに加算します。
和は let sum = &a + &b として計算されることに注意してください。& 演算子は a と b を消費しないようにするために使用され、後で表示できるようになります。それらの和を含む新しい配列が作成されます。
use ndarray::arr2;
fn main() {
let a = arr2(&[[1, 2, 3],
[4, 5, 6]]);
let b = arr2(&[[6, 5, 4],
[3, 2, 1]]);
let sum = &a + &b;
println!("{}", a);
println!("+");
println!("{}", b);
println!("=");
println!("{}", sum);
}
行列の乗算
ndarray::arr2 で 2 つの行列を作成し、ndarray::ArrayBase::dot でそれらの行列乗算を実行します。
use ndarray::arr2;
fn main() {
let a = arr2(&[[1, 2, 3],
[4, 5, 6]]);
let b = arr2(&[[6, 3],
[5, 2],
[4, 1]]);
println!("{}", a.dot(&b));
}
スカラーをベクトルに掛け、さらに行列を掛ける
ndarray::arr1 で 1 次元配列(ベクトル)を作成し、ndarray::arr2 で
2 次元配列(行列)を作成します。
まず、スカラーをベクトルに掛けて別のベクトルを得ます。次に、その新しいベクトルに
対して行列を ndarray::Array2::dot で掛けます。(行列の乗算は dot を使用して行われます。一方、
* 演算子は要素ごとの乗算を行います。)
ndarray では、1 次元配列は文脈に応じて行ベクトルまたは列ベクトルの
いずれとしても解釈できます。ベクトルの向きを表すことが重要な場合は、
代わりに 1 行または 1 列の 2 次元配列を使用する必要があります。この例では、
ベクトルは右辺の 1 次元配列であるため、dot はそれを列ベクトルとして
扱います。
use ndarray::{arr1, arr2, Array1};
fn main() {
let scalar = 4;
let vector = arr1(&[1, 2, 3]);
let matrix = arr2(&[[4, 5, 6],
[7, 8, 9]]);
let new_vector: Array1<_> = scalar * vector;
println!("{}", new_vector);
let new_matrix = matrix.dot(&new_vector);
println!("{}", new_matrix);
}
ベクトルの比較
ndarray クレートは配列を作成するためのさまざまな方法をサポートしています。このレシピでは、from を使って std::Vec から
ndarray::Array を作成します。次に、それらの配列を要素ごとに加算します。
このレシピには、2 つの浮動小数点ベクトルを要素ごとに比較する例も含まれています。
単純なケースでは、厳密な等価比較に assert_eq! を使用できます。より
複雑な浮動小数点比較で精度の問題を扱う必要がある場合は、Cargo.toml の ndarray 依存関係で
approx 機能を有効にしたうえで approx クレートを使用できます。たとえば、
ndarray = { version = "0.13", features = ["approx"] } のようにします。
このレシピには、所有権に関する追加の例も含まれています。ここでは、let z = a + b によって
a と b が消費され、a が結果で更新された後、その所有権が z にムーブされます。別の方法として、
let w = &c + &d は c と d を消費せずに新しいベクトルを作成するため、
後からそれらを変更できます。詳細については Binary Operators With Two Arrays を参照してください。
use ndarray::Array;
fn main() {
let a = Array::from(vec![1., 2., 3., 4., 5.]);
let b = Array::from(vec![5., 4., 3., 2., 1.]);
let mut c = Array::from(vec![1., 2., 3., 4., 5.]);
let mut d = Array::from(vec![5., 4., 3., 2., 1.]);
let z = a + b;
let w = &c + &d;
assert_eq!(z, Array::from(vec![6., 6., 6., 6., 6.]));
println!("c = {}", c);
c[0] = 10.;
d[1] = 10.;
assert_eq!(w, Array::from(vec![6., 6., 6., 6., 6.]));
}
ベクトルノルム
このレシピでは、与えられたベクトルの l1 ノルムと l2 ノルムを計算する際の
Array1 型、ArrayView1 型、
fold メソッド、および dot メソッドの使い方を示します。
l2_norm関数は 2 つのうち単純な方であり、ベクトルとそれ自身との ドット積の平方根を計算します。l1_norm関数は、要素の絶対値を合計するfold演算によって計算されます。(これはx.mapv(f64::abs).scalar_sum()でも実行できますが、その場合mapvの結果を保持するための新しい 配列が確保されます。)
l1_norm と l2_norm はどちらも ArrayView1 型を受け取ることに注意してください。このレシピでは
ベクトルノルムを扱うため、ノルム関数は一次元の
ビューだけを受け取れれば十分です(したがって ArrayView1)。
これらの関数は代わりに &Array1<f64> 型の
パラメータを取ることもできますが、その場合、呼び出し側は所有された配列への
参照を持っている必要があり、単にビューへアクセスできる場合よりも制約が強くなります
(ビューは所有された配列だけでなく、任意の配列またはビューから作成できるためです)。
Array と ArrayView はどちらも ArrayBase の型エイリアスです。したがって、呼び出し側にとって
最も汎用的な引数型は &ArrayBase<S, Ix1> where S: Data
になります。これは、呼び出し側が x.view() の代わりに &array または &view を
使えるようになるためです。
関数が公開 API の一部である場合、ユーザーの利便性のためにそれがより良い選択である
可能性があります。内部関数では、より簡潔な ArrayView1<f64>
の方が望ましいかもしれません。
use ndarray::{array, Array1, ArrayView1};
fn l1_norm(x: ArrayView1<f64>) -> f64 {
x.fold(0., |acc, elem| acc + elem.abs())
}
fn l2_norm(x: ArrayView1<f64>) -> f64 {
x.dot(&x).sqrt()
}
fn normalize(mut x: Array1<f64>) -> Array1<f64> {
let norm = l2_norm(x.view());
x.mapv_inplace(|e| e/norm);
x
}
fn main() {
let x = array![1., 2., 3., 4., 5.];
println!("||x||_2 = {}", l2_norm(x.view()));
println!("||x||_1 = {}", l1_norm(x.view()));
println!("Normalizing x yields {:?}", normalize(x));
}
行列を反転
nalgebra::Matrix3 で 3x3 行列を作成し、可能であればそれを反転します。
use nalgebra::Matrix3;
fn main() {
let m1 = Matrix3::new(2.0, 1.0, 1.0, 3.0, 2.0, 1.0, 2.0, 1.0, 2.0);
println!("m1 = {}", m1);
match m1.try_inverse() {
Some(inv) => {
println!("The inverse of m1 is: {}", inv);
}
None => {
println!("m1 is not invertible!");
}
}
}
行列をシリアライズ/デシリアライズする
行列を JSON との間でシリアライズおよびデシリアライズします。シリアライズは
serde_json::to_string が行い、serde_json::from_str がデシリアライズを行います。
シリアライズの後にデシリアライズすると、元の行列がそのまま返されることに注意してください。
use nalgebra::DMatrix;
fn main() -> Result<(), std::io::Error> {
let row_slice: Vec<i32> = (1..5001).collect();
let matrix = DMatrix::from_row_slice(50, 100, &row_slice);
// 行列をシリアライズする
let serialized_matrix = serde_json::to_string(&matrix)?;
// 行列をデシリアライズする
let deserialized_matrix: DMatrix<i32> = serde_json::from_str(&serialized_matrix)?;
// `deserialized_matrix` が `matrix` と等しいことを検証する
assert!(deserialized_matrix == matrix);
Ok(())
}