container チュートリアル

このチュートリアルでは、container のアルゴリズムで行列やベクトルを表現の間で移す方法、その途中で要素型を変える方法、そして 2 つの小さな操作辞書を書いて自作のコンテナ型を参加させる方法を示します。背後にあるモデルは container の設計 にあります。

クイックスタート

moon add Luna-Flow/linear-algebra@0.5.0
///|
import {
  "Luna-Flow/linear-algebra/container",
  "Luna-Flow/linear-algebra/container/adapters" @container_adapters,
  "Luna-Flow/linear-algebra/error" @la_error,
  "Luna-Flow/linear-algebra/immut",
  "Luna-Flow/linear-algebra/mutable",
}

可変な作業用行列を不変な値として凍結します。

///|
test "freeze a mutable matrix" {
  let work = @mutable.Matrix::from_2d_array([[1, 2], [3, 4]])
  let frozen : @immut.Matrix[Int] = @container.matrix_convert(
    work,
    @container_adapters.mutable_matrix_read_ops(),
    @container_adapters.immutable_matrix_build_ops(),
  ).unwrap()
  work.set(0, 0, 99)
  inspect(frozen, content="|1, 2|\n|3, 4|")
}

後から work に書き込んでも frozen には届きません。変換はコピーするからです。

日常的なタスク

変換して要素型を変える

matrix_map は変換と写像を 1 パスで行います。要素型は変わってもかまいません。

///|
test "integer pixels to normalized doubles" {
  let pixels = @immut.Matrix::from_2d_array([[0, 128], [255, 64]])
  let normalized : @mutable.Matrix[Double] = @container.matrix_map(
    pixels,
    @container_adapters.immutable_matrix_read_ops(),
    @container_adapters.mutable_matrix_build_ops(),
    p => p.to_double() / 255.0,
  ).unwrap()
  inspect(normalized.get(1, 0), content="1")
  inspect(normalized.get(0, 0), content="0")
}

別の表現へ転置する

///|
test "transpose a dense wrapper into an immutable matrix" {
  let source = @default.DenseMatrix::from_2d_array([[1, 2, 3]])
  let column : @immut.Matrix[Int] = @container.matrix_transpose(
    source,
    @container_adapters.dense_matrix_read_ops(),
    @container_adapters.immutable_matrix_build_ops(),
  ).unwrap()
  inspect(column, content="|1|\n|2|\n|3|")
}

辞書を通じて編集する

編集辞書は検査付きです。不正なインデックスはエラー値になり、何も変更されません。

///|
test "checked edits" {
  let m = @immut.Matrix::from_2d_array([[1, 2], [3, 4]])
  let edit = @container_adapters.immutable_matrix_persistent_edit_ops()
  let m2 = (edit.set)(m, 1, 1, 40).unwrap()
  inspect(m2, content="|1, 2|\n|3, 40|")
  inspect(m, content="|1, 2|\n|3, 4|")
  match (edit.set)(m, 2, 0, 0) {
    Err(e) => inspect(e.is_index_out_of_bounds(), content="true")
    Ok(_) => fail("row 2 does not exist")
  }
  let row = @mutable.Matrix::from_2d_array([[5, 6, 7]]).row_view(0)
  let view_edit = @container_adapters.mutable_row_view_mutable_edit_ops()
  (view_edit.set)(row, 2, 70).unwrap()
  inspect(row, content="|5, 6, 70|")
}

自作の型の辞書を公開する

インデックスと値の組として格納された疎ベクトルは、読み取りも構築もできます。2 つの辞書を一度書けば、すべての汎用アルゴリズムがその型を受け付けます。

///|
struct ContTutSparse {
  len : Int
  entries : Array[(Int, Double)]
}

///|
fn cont_tut_sparse_read() -> @container.VectorReadOps[ContTutSparse, Double] {
  @container.VectorReadOps::new(v => v.len, (v, i) => {
    guard i >= 0 && i < v.len else {
      return Err(
        @la_error.LinearAlgebraError::index_out_of_bounds("index \{i}"),
      )
    }
    for entry in v.entries {
      if entry.0 == i {
        return Ok(entry.1)
      }
    }
    Ok(0.0)
  })
}

///|
fn cont_tut_sparse_build() -> @container.VectorBuildOps[ContTutSparse, Double] {
  @container.VectorBuildOps::new((n, f) => {
    guard n >= 0 else {
      return Err(
        @la_error.LinearAlgebraError::negative_dimension("length \{n}"),
      )
    }
    let entries = []
    for i in 0..<n {
      let x = f(i)
      if x != 0.0 {
        entries.push((i, x))
      }
    }
    Ok({ len: n, entries, })
  })
}

///|
test "a sparse vector joins the generic algorithms" {
  let sparse : ContTutSparse = { len: 5, entries: [(1, 2.0), (4, -1.0)], }
  let dense : @immut.Vector[Double] = @container.vector_convert(
    sparse,
    cont_tut_sparse_read(),
    @container_adapters.immutable_vector_build_ops(),
  ).unwrap()
  inspect(dense, content="|0, 2, 0, 0, -1|")
  let back : ContTutSparse = @container.vector_convert(
    dense,
    @container_adapters.immutable_vector_read_ops(),
    cont_tut_sparse_build(),
  ).unwrap()
  inspect(back.entries.length(), content="2")
}

さらに進んで

どの辞書を公開するか。 観測できる型には読み取りを、関数から構築できる型には構築を、そして所有モデルに応じて 2 つの編集形式のうちちょうど 1 つを提供してください。値型には永続的な編集を、インプレースの型とビューには可変な編集をです。統合ガイド には、採用のレベルと、アダプタのコードを誰が所有すべきかが記載されています。

テストすべき法則。 自分の辞書について、tabulate してから get すると初期化関数の値が返ること、不正なインデックスで IndexOutOfBounds になること、負の形状で NegativeDimension になること、永続的な set が引数を変更しないことをテストしてください。設計ページではこれらを等式として述べています。

性能。 アルゴリズムはクロージャを介してコピーし、ソース全体をバッファリングします。境界での変換のためのものであり、数値コードの内側のループ向けではありません。そこでは具体的な型自身のメソッドを使ってください。

よくある落とし穴

  • フィールド呼び出しの括弧を忘れる。 ops.get(v, i) ではなく (ops.get)(v, i) と書いてください。フィールドはメソッドではなくクロージャです。
  • ビューをターゲットにできると思い込む。 行・列・転置のビューには読み取りと可変な編集の辞書はありますが、構築辞書はありません。
  • 0×30 \times 3 を 0×00 \times 0 として扱う。 退化した形状は保存されます。両方の次元を検査してください。
  • 共有を期待する。 どのアルゴリズムも新しいターゲットを構築します。その後のソースへの編集は反映されません。

次のステップ