core チュートリアル

このチュートリアルでは、ジェネリックな Complex[T] 型を使って複素数の算術を行います。複素数を構築して出力し、Double 上と整数上で計算し、luna-generic トレイトを通じて複素数を受け付けるジェネリックなコードを書き、値をインプレースで更新します。背後にある代数は core の設計 にあります。

クイックスタート

moon add Luna-Flow/luna-complex@0.2.0
import {
  "Luna-Flow/luna-complex" @complex,
}
fn main {
  let z = @complex.Complex::new(1.0, 2.0)
  let w = @complex.Complex::new(3.0, -1.0)
  println("z + w = \{z + w}")
  println("z * w = \{z * w}")
  println("z / w = \{z / w}")
}
z + w = 4 + 1i
z * w = 5 + 5i
z / w = 0.1 + 0.7000000000000001i

日常的なタスク

共役と絶対値の 2 乗

zzˉ=∣z∣2z\bar z = |z|^2 は実数です:

fn main {
  let z = @complex.Complex::new(3.0, 4.0)
  let n = z * z.conjugate()
  println("z * conj(z) = \{n}")
  println("1 / z = \{z.inv()}")
}
z * conj(z) = 25 + 0i
1 / z = 0.12 + -0.16i

ガウス整数

Int 上では、Complex[Int] はガウス整数の環 Z[i]\mathbb Z[i] です。除算以外はすべて動作し、結果は厳密です:

fn main {
  let a = @complex.Complex::new(2, 1)
  let b = @complex.Complex::new(2, -1)
  println("(2 + i)(2 - i) = \{a * b}")
  let mut p = @complex.Complex::one()
  for _ in 0..<4 {
    p = p * @complex.Complex::new(1, 1)
  }
  println("(1 + i)^4 = \{p}")
}
(2 + i)(2 - i) = 5 + 0i
(1 + i)^4 = -4 + 0i

5=(2+i)(2−i)5 = (2 + i)(2 - i) から、55 は Z[i]\mathbb Z[i] において素数でないことがわかります。

環上のジェネリックなコード

luna-generic トレイトに対して書かれたコードは複素数を受け付けます。ここでは p(x)=x2+1p(x) = x^2 + 1 を Horner 法で評価します:

fn[T : @lg.Ring] horner(coefficients : Array[T], x : T) -> T {
  let mut acc : T = @lg.Zero::zero()
  for i = coefficients.length() - 1; i >= 0; i = i - 1 {
    acc = acc * x + coefficients[i]
  }
  acc
}

fn main {
  let one : @complex.Complex[Double] = @complex.Complex::one()
  let zero : @complex.Complex[Double] = @complex.Complex::zero()
  let i = @complex.Complex::new(0.0, 1.0)
  println("p(i) = \{horner([one, zero, one], i)}")
  println("p(2) = \{horner([1.0, 0.0, 1.0], 2.0)}")
}
p(i) = 0 + 0i
p(2) = 5

インプレースでの更新

set、set_re、set_im は値を上書きします。その値へのすべての束縛が変更を観測します:

fn main {
  let acc = @complex.Complex::new(0.0, 0.0)
  let shared = acc
  for k in 1..=3 {
    let kd = k.to_double()
    acc.set(acc.re + kd, acc.im - kd)
  }
  println("acc    = \{acc}")
  println("shared = \{shared}")
}
acc    = 6 + -6i
shared = 6 + -6i

さらに進んで

入れ子の複素数

Complex[T] は、必要なトレイトを持つ任意の T に対して動作し、Complex[Double] 自体も例外ではありません:

fn main {
  let a = @complex.Complex::new(1.0, 2.0)
  let b = @complex.Complex::new(-0.5, 0.25)
  let z = @complex.Complex::new(a, b)
  let w = z * z.inv()
  println("z * z^-1 = (\{w.re}) + (\{w.im})j")
}
z * z^-1 = (1.0000000000000002 + 5.204170427930421e-17i) + (0 + 0i)j

この zz の絶対値の 2 乗 a2+b2a^2 + b^2 は可逆なので、積は丸め誤差の範囲で 11 になります。一般には Complex[Complex[Double]] は体ではありません。下のよくある落とし穴を参照してください。

解析関数

コアには sqrt、exp、sin はありません。Complex[Double] 向けには Luna-Flow/luna-complex/float_backend をインポートしてください。方法は float_backend チュートリアル で説明しています。

よくある落とし穴

  • ゼロ除算は中断する。 z / w と w.inv() は c2+d2c^2 + d^2 に対して Inverse::inv を呼び出します。Double では、値がゼロのとき、また ∣w∣≲10−162|w| \lesssim 10^{-162} でアンダーフローしたときにも中断します。堅牢な除算には float_backend の @fb.div を使ってください。
  • 大きな値は除算でオーバーフローする。 ∣w∣≳10154|w| \gtrsim 10^{154} で c2+d2c^2 + d^2 がオーバーフローします。
  • 共有された変更。 let shared = z はコピーしません。セッターはすべての別名に影響します。
  • テキスト形式。 負の虚部は + -2i と出力されます。別の形式が必要な場合は debug_inspect か独自の整形を使ってください。
  • 入れ子の型は体ではない。 Complex[Complex[Double]] では、1+i j1 + i\,j のような値の絶対値の 2 乗はゼロとなり、inv が中断します。

次のステップ