container API

Luna-Flow/linear-algebra/container は、汎用コードが格納方式を知らずに線形コンテナを観測・構築・編集する方法を記述します。能力は 操作辞書 であり、一つのコンテナ型 V または M と一つの要素型 T に対する関数のレコードです。五つの汎用アルゴリズム(map、convert、transpose)はこの辞書に対して書かれています。リポジトリ自身の型に対する既製の辞書は container/adapters にあります。

ソース: src/container/vector_ops.mbt、src/container/matrix_ops.mbt、src/container/generic_algorithms.mbt。モデルは container の設計 で説明し、外部ライブラリが辞書を公開する方法は 統合ガイド で示しています。

インポート

///|
import {
  "Luna-Flow/linear-algebra/container",
  "Luna-Flow/linear-algebra/container/adapters" @container_adapters,
  "Luna-Flow/linear-algebra/error" @la_error,
}

すべての辞書に共通する約束

辞書の関数はすべて検査付きです。Result を返し、どの引数に対しても中断してはなりません。

  • 形状の外のインデックスでの読み取りや編集は、種類 IndexOutOfBounds のエラーを返します。
  • 負の長さや次元での構築は種類 NegativeDimension のエラーを返し、初期化関数を呼びません。
  • 退化した形状 0×n0 \times n と n×0n \times 0 は有効であり、正確に保たれなければなりません。これらは 0×00 \times 0 とは異なります。
  • 行列の座標は 0 始まりの (row, col) です。ビルダーは初期化関数を (row, col) で呼び出します。

レコードのフィールドは公開されていて読み取れます。フィールドを関数として呼ぶには、フィールドアクセスを括弧で囲みます: (ops.get)(m, 0, 1)。

ベクトルの辞書

VectorReadOps

VectorReadOps[V, T] は要素 T を持つベクトル的なコンテナ V を観測します。

pub struct VectorReadOps[V, T] {
  length : (V) -> Int
  get : (V, Int) -> Result[T, @error.LinearAlgebraError]
}

length(v) は要素数 n≥0n \ge 0 を返し、get(v, i) は 0≤i<n0 \le i < n なら要素 ii を、そうでなければ IndexOutOfBounds を返します。

VectorReadOps::new

VectorReadOps::new(length, get) は二つの関数から読み取り辞書を作ります。

pub fn[V, T] VectorReadOps::new((V) -> Int, (V, Int) -> Result[T, @error.LinearAlgebraError]) -> Self[V, T]

VectorBuildOps

VectorBuildOps[V, T] はインデックスの関数から型 V のベクトルを構築します。

pub struct VectorBuildOps[V, T] {
  tabulate : (Int, (Int) -> T) -> Result[V, @error.LinearAlgebraError]
}

tabulate(n, f) はベクトル (f(0),…,f(n−1))(f(0), \dots, f(n-1)) を返し、n<0n < 0 なら NegativeDimension を返します。

VectorBuildOps::new

VectorBuildOps::new(tabulate) は構築辞書を作ります。

pub fn[V, T] VectorBuildOps::new((Int, (Int) -> T) -> Result[V, @error.LinearAlgebraError]) -> Self[V, T]

VectorPersistentEditOps

VectorPersistentEditOps[V, T] は要素を一つ置き換えた新しい値を返します。

pub struct VectorPersistentEditOps[V, T] {
  set : (V, Int, T) -> Result[V, @error.LinearAlgebraError]
}

set(v, i, x) は、インデックス ii が x である以外は v と等しいベクトルを返します。実装がバージョン間で格納領域を共有していても、引数 v は変化してはなりません。

VectorPersistentEditOps::new

VectorPersistentEditOps::new(set) は永続編集辞書を作ります。

pub fn[V, T] VectorPersistentEditOps::new((V, Int, T) -> Result[V, @error.LinearAlgebraError]) -> Self[V, T]

VectorMutableEditOps

VectorMutableEditOps[V, T] は要素を一つインプレースで置き換えます。

pub struct VectorMutableEditOps[V, T] {
  set : (V, Int, T) -> Result[Unit, @error.LinearAlgebraError]
}

set(v, i, x) は v のインデックス ii に x を書き込みます。エラーのときは何も書き込みません。

VectorMutableEditOps::new

VectorMutableEditOps::new(set) は可変編集辞書を作ります。

pub fn[V, T] VectorMutableEditOps::new((V, Int, T) -> Result[Unit, @error.LinearAlgebraError]) -> Self[V, T]

行列の辞書

MatrixReadOps

MatrixReadOps[M, T] は行列的なコンテナを観測します。

pub struct MatrixReadOps[M, T] {
  shape : (M) -> (Int, Int)
  get : (M, Int, Int) -> Result[T, @error.LinearAlgebraError]
}

shape(m) は (rows, cols) を返し、get(m, r, c) は成分 (r,c)(r, c) または IndexOutOfBounds を返します。

MatrixReadOps::new

MatrixReadOps::new(shape, get) は行列の読み取り辞書を作ります。

pub fn[M, T] MatrixReadOps::new((M) -> (Int, Int), (M, Int, Int) -> Result[T, @error.LinearAlgebraError]) -> Self[M, T]

MatrixBuildOps

MatrixBuildOps[M, T] は座標の関数から行列を構築します。

pub struct MatrixBuildOps[M, T] {
  tabulate : (Int, Int, (Int, Int) -> T) -> Result[M, @error.LinearAlgebraError]
}

tabulate(r, c, f) は成分が f(i,j)f(i, j) の行列を返し、r<0r < 0 または c<0c < 0 なら NegativeDimension を返します。

MatrixBuildOps::new

MatrixBuildOps::new(tabulate) は行列の構築辞書を作ります。

pub fn[M, T] MatrixBuildOps::new((Int, Int, (Int, Int) -> T) -> Result[M, @error.LinearAlgebraError]) -> Self[M, T]

MatrixPersistentEditOps

MatrixPersistentEditOps[M, T] は成分を一つ置き換えた新しい行列を返し、引数は変更しません。

pub struct MatrixPersistentEditOps[M, T] {
  set : (M, Int, Int, T) -> Result[M, @error.LinearAlgebraError]
}

MatrixPersistentEditOps::new

MatrixPersistentEditOps::new(set) は永続編集辞書を作ります。

pub fn[M, T] MatrixPersistentEditOps::new((M, Int, Int, T) -> Result[M, @error.LinearAlgebraError]) -> Self[M, T]

MatrixMutableEditOps

MatrixMutableEditOps[M, T] は成分を一つインプレースで置き換えます。

pub struct MatrixMutableEditOps[M, T] {
  set : (M, Int, Int, T) -> Result[Unit, @error.LinearAlgebraError]
}

MatrixMutableEditOps::new

MatrixMutableEditOps::new(set) は可変編集辞書を作ります。

pub fn[M, T] MatrixMutableEditOps::new((M, Int, Int, T) -> Result[Unit, @error.LinearAlgebraError]) -> Self[M, T]

素の Array[Int] 向けの辞書を、そのフィールドを通じて使う例:

///|
fn cont_api_array_read() -> @container.VectorReadOps[Array[Int], Int] {
  @container.VectorReadOps::new(xs => xs.length(), (xs, i) => {
    guard i >= 0 && i < xs.length() else {
      return Err(
        @la_error.LinearAlgebraError::index_out_of_bounds("index \{i}"),
      )
    }
    Ok(xs[i])
  })
}

///|
test "a hand-written read dictionary" {
  let ops = cont_api_array_read()
  inspect((ops.length)([4, 5, 6]), content="3")
  inspect((ops.get)([4, 5, 6], 1).unwrap(), content="5")
  match (ops.get)([4, 5, 6], 3) {
    Err(e) => inspect(e.is_index_out_of_bounds(), content="true")
    Ok(_) => fail("index 3 is out of bounds")
  }
}

ジェネリックアルゴリズム

すべてのアルゴリズムは、まずソース全体を行優先で一時バッファに読み込み、それから対象の tabulate を呼びます。読み取りエラーは何かを構築する前に返されるので、部分的に構築された対象が観測されることはありません。いずれも nn 要素に対して O(n)O(n) 回の辞書呼び出しを行い、O(n)O(n) の追加メモリを使います。

vector_map

vector_map(source, source_ops, target_ops, f) は、要素 ii が f(source[i]) である対象型のベクトルを作ります。

pub fn[V1, V2, A, B] vector_map(V1, VectorReadOps[V1, A], VectorBuildOps[V2, B], (A) -> B) -> Result[V2, @error.LinearAlgebraError]

source_ops.length が負の長さを報告すれば NegativeDimension を、そうでなければ最初の読み取りエラーを返し、最後に tabulate が返したものをそのまま返します。f は各要素についてインデックス順にちょうど一回呼ばれます。

vector_convert

vector_convert(source, source_ops, target_ops) は、要素型を保ったままベクトルを別の表現にコピーします。

pub fn[V1, V2, T] vector_convert(V1, VectorReadOps[V1, T], VectorBuildOps[V2, T]) -> Result[V2, @error.LinearAlgebraError]

恒等関数を使った vector_map です。

matrix_map

matrix_map(source, source_ops, target_ops, f) は、成分 (i,j)(i, j) が f(source[i][j]) である同じ形状の行列を作ります。

pub fn[M1, M2, A, B] matrix_map(M1, MatrixReadOps[M1, A], MatrixBuildOps[M2, B], (A) -> B) -> Result[M2, @error.LinearAlgebraError]

matrix_convert

matrix_convert(source, source_ops, target_ops) は、要素型と形状を保ったまま行列を別の表現にコピーします。

pub fn[M1, M2, T] matrix_convert(M1, MatrixReadOps[M1, T], MatrixBuildOps[M2, T]) -> Result[M2, @error.LinearAlgebraError]

matrix_transpose

matrix_transpose(source, source_ops, target_ops) は source の転置を対象の表現で作ります。形状 (r,c)(r, c) のソースに対して形状は (c,r)(c, r) で、成分 (i,j)(i, j) はソースの成分 (j,i)(j, i) に等しくなります。

pub fn[M1, M2, T] matrix_transpose(M1, MatrixReadOps[M1, T], MatrixBuildOps[M2, T]) -> Result[M2, @error.LinearAlgebraError]

@algebra.TransposeMatrix::transpose と異なり、対象の型はソースの型と異なってもかまいません。

リポジトリのアダプターを使った三つの行列アルゴリズム:

///|
test "map, convert and transpose across representations" {
  let source = @mutable.Matrix::from_2d_array([[1, 2, 3], [4, 5, 6]])
  let read = @container_adapters.mutable_matrix_read_ops()
  let doubled : @immut.Matrix[Int] = @container.matrix_map(
    source,
    read,
    @container_adapters.immutable_matrix_build_ops(),
    x => x * 2,
  ).unwrap()
  inspect(doubled, content="|2, 4, 6|\n|8, 10, 12|")
  let as_text : @immut.Matrix[String] = @container.matrix_map(
    source,
    read,
    @container_adapters.immutable_matrix_build_ops(),
    x => "<\{x}>",
  ).unwrap()
  inspect(as_text[1][2], content="<6>")
  let t : @immut.Matrix[Int] = @container.matrix_transpose(
    source,
    read,
    @container_adapters.immutable_matrix_build_ops(),
  ).unwrap()
  inspect(t, content="|1, 4|\n|2, 5|\n|3, 6|")
  let copy : @mutable.Matrix[Int] = @container.matrix_convert(
    t,
    @container_adapters.immutable_matrix_read_ops(),
    @container_adapters.mutable_matrix_build_ops(),
  ).unwrap()
  debug_inspect(copy.shape(), content="(3, 2)")
}

ベクトルのアルゴリズムも同じように動きます。

///|
test "vector map and convert" {
  let v = @immut.Vector::from_array([1, 2, 3])
  let squares : @mutable.Vector[Int] = @container.vector_map(
    v,
    @container_adapters.immutable_vector_read_ops(),
    @container_adapters.mutable_vector_build_ops(),
    x => x * x,
  ).unwrap()
  inspect(squares, content="|1, 4, 9|")
  let back : @immut.Vector[Int] = @container.vector_convert(
    squares,
    @container_adapters.mutable_vector_read_ops(),
    @container_adapters.immutable_vector_build_ops(),
  ).unwrap()
  inspect(back, content="|1, 4, 9|")
}