mutable/dense チュートリアル

このチュートリアルでは、一変数多項式をその場で構築・更新する方法を示します。係数を 1 つずつ設定し、和と積を 1 つのコンテナに累積し、スナップショットを保持し、結果をイミュータブルなコードに引き渡します。

クイックスタート

moon add Luna-Flow/luna-poly@0.2.0
import {
  "Luna-Flow/luna-poly/mutable",
}
test "mutable dense quick start" {
  let p = @mutable.DensePolynomial::from_coefficients([1, 2, 3])
  p.set_coefficient(1, 5)
  inspect(p, content="1 + 5x^1 + 3x^2")
}

set_coefficient(1, 5) は p 自身の xx の係数を変更します。

日常的なタスク

係数を 1 つずつ埋める

ゼロから始めて、分かっている係数を設定します。配列は必要に応じて伸びます:

test "filling" {
  let p : @mutable.DensePolynomial[Int] = @mutable.DensePolynomial::zero()
  for k in 0..<5 {
    p.set_coefficient(k, k * k)
  }
  inspect(p, content="1x^1 + 4x^2 + 9x^3 + 16x^4")
  p.set_coefficient(4, 0)
  debug_inspect(p.degree(), content="Some(3)")
}

多項式は正規形に保たれるため、先頭係数をゼロにすると次数が下がります。

1 つのコンテナに累積する

add_inplace と mul_inplace はレシーバを更新するので、ループで中間値が生じません:

test "accumulate" {
  let x_plus_1 = @mutable.DensePolynomial::from_coefficients([1, 1])
  let product : @mutable.DensePolynomial[Int] = @mutable.DensePolynomial::one()
  let sum : @mutable.DensePolynomial[Int] = @mutable.DensePolynomial::zero()
  for _ in 0..<3 {
    product.mul_inplace(x_plus_1)
    sum.add_inplace(product)
  }
  inspect(product, content="1 + 3x^1 + 3x^2 + 1x^3")
  inspect(sum, content="3 + 6x^1 + 4x^2 + 1x^3")
}

sum は (x+1)+(x+1)2+(x+1)3(x+1) + (x+1)^2 + (x+1)^3 です。

スナップショットを保持する

copy と to_immut は、さらに変更を加える前に独立したスナップショットを取ります:

test "snapshots" {
  let p = @mutable.DensePolynomial::from_coefficients([1, 1])
  let saved = p.copy()
  let frozen = p.to_immut()
  p.scale_inplace(2, 10)
  inspect(p, content="10x^2 + 10x^3")
  inspect(saved, content="1 + 1x^1")
  inspect(frozen, content="1 + 1x^1")
}

演算子は変更を行わない

新しい値が欲しい場合は演算子を使います。オペランドはそのまま残ります:

test "operators" {
  let p = @mutable.DensePolynomial::from_coefficients([1, 2])
  let q = p * p + p
  inspect(q, content="2 + 6x^1 + 4x^2")
  inspect(p, content="1 + 2x^1")
}

さらに進む

ミュータブルに構築し、イミュータブルに公開する

逐次的な処理はミュータブルなバッファで行い、イミュータブルな値を返すと、呼び出し側は値セマンティクスを得られます:

fn truncated_exp_numerators(n : Int) -> @immut.DensePolynomial[Int] {
  // n! * (1 + x + x^2/2! + ... + x^n/n!)
  let buffer : @mutable.DensePolynomial[Int] = @mutable.DensePolynomial::zero()
  let mut falling = 1
  for k = n; k >= 0; k = k - 1 {
    buffer.set_coefficient(k, falling)
    falling = falling * (if k == 0 { 1 } else { k })
  }
  buffer.to_immut()
}

test "publish" {
  inspect(truncated_exp_numerators(3), content="6 + 6x^1 + 3x^2 + 1x^3")
}

両方の層にまたがるジェネリックなコード

演算レコードと能力トレイトは、ミュータブル型とイミュータブル型で同じ形をしています:

fn[P : @mutable.UnivariatePolynomial] is_linear(p : P) -> Bool {
  @mutable.HasDegree::degree(p) == Some(1)
}

test "generic" {
  inspect(is_linear(@mutable.DensePolynomial::from_coefficients([0, 3])), content="true")
  inspect(is_linear(@immut.DensePolynomial::from_coefficients([1, 0, 2])), content="false")
}

リセットして再利用する

clear(@mutable.Clearable::clear でもあります)はバッファをゼロにリセットし、再利用できるようにします:

test "reuse" {
  let buffer = @mutable.DensePolynomial::from_coefficients([4, 5])
  @mutable.Clearable::clear(buffer)
  assert_true(buffer.is_zero())
  buffer.set_coefficient(2, 1)
  inspect(buffer, content="1x^2")
}

よくある落とし穴

  • 共有されるコンテナ. let q = p は q を同じコンテナにします。q を変更すると p も変わります。p.copy() を使ってください。
  • チェック付きのセッターはない. set_coefficient は負の次数で中断 (abort) します。
  • 導関数 には、イミュータブル型と同様に Float、Double、BigInt のいずれかの係数が必要です。
  • 委譲される演算は変換を伴う. pow、substitute、karatsuba は係数をイミュータブル型にコピーし、また戻します。演算そのものに比べれば安価ですが、無料ではありません。

次のステップ