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")
}

すべてゼロの入力では最初の除算が 0/00/0 となり、種類 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 ページ に挙げた名前だけです。

よくある落とし穴

  • スケールの合わない絶対許容誤差。 102010^{20} 前後の値に対する approx_eq は実質的に厳密な等価比較になり、10−2010^{-20} 前後の値ではすべてを受け入れます。データをスケーリングするか、独自の相対的な規則を使ってください。
  • 近似比較を連鎖させる。 a≈ba \approx b と b≈cb \approx c から a≈ca \approx c は導かれません。
  • コンテキストで Double の結果が変わると期待する。 2 進浮動小数点では ArithmeticContext の精度は無視されます。
  • Int.MIN_VALUE に対する Abs。 結果はラップアラウンドし、負のままです。

次のステップ