checked チュートリアル

このチュートリアルでは、失敗する可能性のあるコードを微分します。双対数上でチェック付きの除算と平方根を呼び出し、そのエラーを計算全体に伝播させ、通常の数と双対数の両方で動作するジェネリックなチェック付きコードを書きます。考え方は checked の設計 にあります。

クイックスタート

moon add Luna-Flow/autodiff@0.2.0
import {
  "Luna-Flow/autodiff/checked",
}
fn main {
  let ctx = @checked.ArithmeticContext::new(53)
  let x : @checked.Dual[Double] = @checked.Dual::variable(2.0)
  let y : @checked.Dual[Double] = @checked.Dual::constant(8.0)
  match y.div_checked(x, ctx) {
    Ok(q) => println("8/x at 2: value \{q.value()}, derivative \{q.tangent()}")
    Err(e) => println("failed: \{e.message}")
  }
}
8/x at 2: value 4, derivative -2

日常的なタスク

失敗を調べる

fn main {
  let ctx = @checked.ArithmeticContext::new(53)
  let one : @checked.Dual[Double] = @checked.Dual::constant(1.0)
  for c in [1.0, 0.0] {
    match one.div_checked(@checked.Dual::variable(c), ctx) {
      Ok(q) => println("1/x at \{c}: derivative \{q.tangent()}")
      Err(e) => println("1/x at \{c}: \{e.message}")
    }
  }
}
1/x at 1: derivative -1
1/x at 0: division by zero

チェック付きのステップを連鎖させる

各ステップは Result を返します。最初のエラーで停止します:

fn norm_ratio(
  x : @checked.Dual[Double],
  y : @checked.Dual[Double],
  ctx : @checked.ArithmeticContext,
) -> Result[@checked.Dual[Double], @checked.ArithmeticError] {
  // sqrt(x^2 + y^2) / y
  let r = (x * x + y * y).sqrt_checked(ctx)
  match r {
    Err(e) => Err(e)
    Ok(r) => r.div_checked(y, ctx)
  }
}

fn main {
  let ctx = @checked.ArithmeticContext::new(53)
  let y : @checked.Dual[Double] = @checked.Dual::constant(4.0)
  match norm_ratio(@checked.Dual::variable(3.0), y, ctx) {
    Ok(v) => println("value \{v.value()}, d/dx \{v.tangent()}")
    Err(e) => println(e.message)
  }
  let zero : @checked.Dual[Double] = @checked.Dual::constant(0.0)
  match norm_ratio(@checked.Dual::variable(0.0), zero, ctx) {
    Ok(_) => println("unexpected")
    Err(e) => println("at the origin: \{e.message}")
  }
}
value 1.25, d/dx 0.15
at the origin: zero divided by zero is undefined

原点では平方根が 00 で評価され、そこでは導関数が存在しないので、連鎖はそこで停止します。

ジェネリックなチェック付きコード

関数をチェック付きトレイトで境界付けると、Double 上でも Dual[Double] 上でも動作します:

fn[T : @checked.DivChecked + @checked.SqrtChecked] sqrt_ratio(
  a : T,
  b : T,
  ctx : @checked.ArithmeticContext,
) -> Result[T, @checked.ArithmeticError] {
  match @checked.DivChecked::div_checked(a, b, ctx) {
    Err(e) => Err(e)
    Ok(q) => @checked.SqrtChecked::sqrt_checked(q, ctx)
  }
}

fn main {
  let ctx = @checked.ArithmeticContext::new(53)
  match sqrt_ratio(8.0, 2.0, ctx) {
    Ok(v) => println("plain: \{v}")
    Err(e) => println(e.message)
  }
  let a : @checked.Dual[Double] = @checked.Dual::variable(8.0)
  let b : @checked.Dual[Double] = @checked.Dual::constant(2.0)
  match sqrt_ratio(a, b, ctx) {
    Ok(v) => println("dual: \{v.value()}, d/da \{v.tangent()}")
    Err(e) => println(e.message)
  }
}
plain: 2
dual: 2, d/da 0.125

さらに進む

  • ArithmeticContext::new(precision, rounding=…) は、コンテキストを使うスカラー型のためのコンテキストを構築します。Double と Float はこれを無視します。
  • @autodiff.diff に渡す関数はチェック付き演算を使えます。エラーをクロージャの外に持ち出す方法は forward チュートリアル で示しています。
  • 独自のスカラー型は DivChecked と SqrtChecked を実装することで参加できます。そのエラーは双対数の結果にそのまま現れます。

よくある落とし穴

  • sqrt_checked は 0 で失敗します。 入力が定数であっても、そこでは導関数が存在しません。
  • 非常に小さい除数。 ∣c∣<1.5×10−162|c| < 1.5 \times 10^{-162} では c2c^2 がアンダーフローし、接成分の除算がゼロ除算を報告します。
  • チェックなしの演算子はチェックなしのままです。 双対数上の x / y と x.sqrt() がエラーを返すことはありません。_checked の形式を使ってください。

次のステップ

  • checked API には再エクスポートされる名前が記載されています。
  • dual API には正確なエラー表があります。
  • arithmetic ではエラーモデルを文書化しています。