arithmetic チュートリアル
このチュートリアルでは、arithmetic のスカラー演算 trait を自作の汎用ヘルパーで使う方法を示します。絶対値、近似比較、そして不正な入力を処理可能な値に変える検査付きの除算・平方根・比較です。背景は arithmetic の設計 にあります。
クイックスタート
moon add Luna-Flow/linear-algebra@0.5.0
moon add Luna-Flow/arithmetic@0.2.2
///|
import {
"Luna-Flow/linear-algebra/arithmetic" @la_arithmetic,
"Luna-Flow/arithmetic" @lf_arith,
}
汎用の「最大の絶対値」ヘルパーには Abs と Compare が必要です。
///|
fn[T : @la_arithmetic.Abs + Compare] arith_tut_max_abs(xs : Array[T]) -> T? {
let mut best : T? = None
for x in xs {
let a = @la_arithmetic.Abs::abs(x)
best = match best {
Some(b) if b >= a => Some(b)
_ => Some(a)
}
}
best
}
///|
test "largest magnitude" {
debug_inspect(arith_tut_max_abs([3, -7, 5]), content="Some(7)")
debug_inspect(arith_tut_max_abs([0.5, -0.25]), content="Some(0.5)")
}
日常的なタスク
ゼロ除算せずにベクトルを正規化する
CheckedDiv は、無限大を生成する代わりにゼロ除数をエラーとして報告します。
///|
fn arith_tut_normalize(
xs : Array[Double],
ctx : @lf_arith.ArithmeticContext,
) -> Result[Array[Double], @lf_arith.ArithmeticError] {
let mut sum = 0.0
for x in xs {
sum = sum + x.abs()
}
let out = []
for x in xs {
match @la_arithmetic.CheckedDiv::checked_div(x, sum, ctx) {
Ok(v) => out.push(v)
Err(e) => return Err(e)
}
}
Ok(out)
}
///|
test "normalize by the 1-norm" {
let ctx = @lf_arith.ArithmeticContext::new(53)
debug_inspect(
arith_tut_normalize([1.0, 3.0], ctx).unwrap(),
content="[0.25, 0.75]",
)
inspect(arith_tut_normalize([0.0, 0.0], ctx) is Err(_), content="true")
}
すべてゼロの入力では最初の除算が となり、種類 DomainError で報告されます。
定義域でのみ平方根を取る
///|
fn arith_tut_std_from_variance(
variance : Double,
) -> Result[Double, @lf_arith.ArithmeticError] {
@la_arithmetic.CheckedSqrt::checked_sqrt(
variance,
@lf_arith.ArithmeticContext::new(53),
)
}
///|
test "square root of a variance" {
inspect(arith_tut_std_from_variance(2.25).unwrap(), content="1.5")
match arith_tut_std_from_variance(-1.0e-18) {
Err(e) => inspect(e.is_domain_error(), content="true")
Ok(_) => fail("negative variance must be rejected")
}
}
わずかに負の分散は、1 パスの公式でよく見られる丸めの副産物です。検査付きの呼び出しでは、それをどう扱うかを自分で決めることになります。
NaN を含みうるデータをソートする
checked_compare は NaN で失敗するので、ソートの前に順序づけできないデータを検出できます。
///|
fn arith_tut_all_ordered(xs : Array[Double]) -> Bool {
for x in xs {
if @la_arithmetic.CheckedCompare::checked_compare(x, x) is Err(_) {
return false
}
}
true
}
///|
test "detect NaN before sorting" {
inspect(arith_tut_all_ordered([2.0, 1.0]), content="true")
inspect(arith_tut_all_ordered([2.0, 0.0 / 0.0]), content="false")
}
結果を近似的に比較する
ApproxEq は、正規化されたベクトルの要素のような 1 程度の大きさの値に便利です。
///|
fn[T : @la_arithmetic.ApproxEq] arith_tut_all_close(
xs : Array[T],
ys : Array[T],
) -> Bool {
if xs.length() != ys.length() {
return false
}
for i in 0..<xs.length() {
if !@la_arithmetic.ApproxEq::approx_eq(xs[i], ys[i]) {
return false
}
}
true
}
///|
test "approximate comparison of computed values" {
inspect(arith_tut_all_close([0.1 + 0.2, 1.0], [0.3, 1.0]), content="true")
inspect(0.1 + 0.2 == 0.3, content="false")
}
さらに進んで
自作のスカラー型。 すべての trait は pub(open) です。10 進数型や有理数型は Abs、ApproxEq(厳密な型なら単に ==)、検査付き trait を実装でき、そうすればそれらに対して書かれたすべてのヘルパーで動作します。
行列コードでのエラー。 行列パッケージはスカラーの失敗を、ArithmeticError をラップする LinearAlgebraError::arithmetic_failure で報告します。行列とスカラーのステップを混在させるヘルパーは、このコンストラクタで変換できます。error のチュートリアル を参照してください。
上流の trait。 超越関数やコンテキスト付きの算術には Luna-Flow/arithmetic を直接使ってください。このパッケージが再エクスポートするのは API ページ に挙げた名前だけです。
よくある落とし穴
- スケールの合わない絶対許容誤差。 前後の値に対する
approx_eqは実質的に厳密な等価比較になり、 前後の値ではすべてを受け入れます。データをスケーリングするか、独自の相対的な規則を使ってください。 - 近似比較を連鎖させる。 と から は導かれません。
- コンテキストで
Doubleの結果が変わると期待する。 2 進浮動小数点ではArithmeticContextの精度は無視されます。 Int.MIN_VALUEに対するAbs。 結果はラップアラウンドし、負のままです。
次のステップ
- 各 trait の正確な規則については arithmetic API。
approx_eqが同値関係でない理由については arithmetic の設計。SqrtとToleranceに依存する行列ルーチンについては mutable のチュートリアル。- スカラー演算ライブラリの全体については Luna-Flow/arithmetic。