Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

コードを書けば複素数がわかる

2,833 views

Published on

コードで2次元ベクトルから複素数を構成し、iOSシミュレータ上で複素数を動かしてみる話。「情報科学若手の会冬の陣2015」にて発表。

Published in: Science
  • Be the first to comment

コードを書けば複素数がわかる

  1. 1. コードを書けば複素数がわかる @taketo1024 情報科学若手の会冬の陣 2015
  2. 2. i2 = 1
  3. 3. 学校で習うとき 1. 虚数単位 i = -1 がいきなり出てくる 2. 複素数 z = a + bi の四則演算が定義される 3. 「数」と思っていた z がベクトルになってる 4. 「i をかけるのは90度回転です」などと教わる 5. 以後当たり前のように電流や波の方程式に出てくる i = p 1 z = a + bi z i
  4. 4. ちょっと待ってくれ… i は「想像上の数」なんだろ…?i
  5. 5. 簡単なコードで i は 作れる! i
  6. 6. 方針 1. 複素数を最初から2次元ベクトルとして定義する。 2. i^2 = -1 となるように 掛け算を入れる。 3. これが実数を拡大した「数」になることを確認。 i2 = 1
  7. 7. 1. 複素数を作ろう
  8. 8. まず2次元ベクトルから出発 struct Complex { let x: Double let y: Double init(_ x: Double, _ y: Double) { self.x = x self.y = y } } 言語:Swift
  9. 9. let z = Complex(2, 3) x y 2 z = ✓ 2 3 ◆ 3
  10. 10. 足し算・引き算・実数倍を定義 func + (z: Complex, w: Complex) -> Complex { return Complex(z.x + w.x, z.y + w.y) } prefix func -(z: Complex) -> Complex { return Complex(-z.x, -z.y); } func - (z: Complex, z: Complex) -> Complex { return Complex(z.x - w.x, z.y - w.y) } func * (a: Double, z: Complex) -> Complex { return Complex(a * z.x, a * z.y) } 演算子のオーバーライド
  11. 11. イコールを定義 struct Complex: Equatable { … } func == (a: Complex, b: Complex) -> Bool { return (a.x == b.x) && (a.y == b.y) } プロトコル(Java のインターフェース)
  12. 12. Complex(2, 3) + Complex(1, -1) == Complex(3, 2) x y
  13. 13. 次に1 = (1, 0), i = (0, 1) として、 z = x + yi の形で書けるようにする。 1 = ✓ 1 0 ◆ i = ✓ 0 1 ◆ z = a + bi
  14. 14. Int, Double から Complex を生成 struct Complex: …, IntegerLiteralConvertible, FloatLiteralConvertible { … init(integerLiteral x: IntegerLiteralType) { self.x = Double(x) self.y = 0 } init(floatLiteral x: FloatLiteralType) { self.x = x self.y = 0 } } リテラル限定の静的キャスト(Swift特有?)
  15. 15. これで実数を複素数に「埋め込んだ」ことになる。 x y 1 = ✓ 1 0 ◆ 1 == Complex(1, 0) // true
  16. 16. あとは 定数 i を定義すれば… let i = Complex(0, 1) i x y i = ✓ 0 1 ◆
  17. 17. let z = 2 + 3 * i x y 求めていた表現を得る! 2 = ✓ 2 0 ◆ 3i = ✓ 0 3 ◆ z = ✓ 2 3 ◆ = 2 + 3i
  18. 18. さて、いよいよ掛け算の定義。
  19. 19. 複素数の掛け算は、 実数と同じ計算規則を満たし、かつ、 となるように定義したい。 i2 = 1
  20. 20. z = a + bi, w = c + di として、積 zw は、z = a + bi, w = c + di となる。 特に a = c = 0, b = d = 1 の場合が i^2 = -1 の式。 zw zw = (a + bi)(c + di) = a(c + di) + bi(c + di) 分配法則 = ac + adi + bci + bdi2 = ac + adi + bci bd 分配法則 = ac bd + adi + bci 交換法則 = (ac bd) + (ad + bc)i 分配法則 i2 = 1 i2 = 1
  21. 21. 先の式で掛け算を定義してみると… struct Complex { … } func * (z: Complex, w: Complex) -> Complex { return Complex(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x) }
  22. 22. ちゃんと「掛け算の要件」を満たしている! let α = 3 + 5 * i let β = -1 + 4 * i let γ = 4 - 7 * i α * 1 == α // 1は単位元 α * β == β * α // 交換法則 (α * β) * γ == α * (β * γ) // 結合法則 α * (β + γ) == α * β + α * γ // 分配法則 当たり前に見えるが、テキトーに定義したのではこうならない。 これで安心して「数」として扱えるようになる。
  23. 23. i * i == -1 // true できました!
  24. 24. 割り算は?
  25. 25. zw = (ac bd) + (ad + bc)i = 1 + 0i 割り算は「逆数との積」なので、z の逆数 w は、 これを解けば、 w = 1 a2 + b2 (a bi) , ( ac bd = 1 ad + bc = 0 z w
  26. 26. 「逆数との積」で割り算を定義 struct Complex { … } func / (z: Complex, w: Complex) -> Complex { let w_inv = (1 / (w.x * w.x + w.y * w.y) ) * Complex(w.x, -w.y) return z * w_inv }
  27. 27. 複素数、できました! struct Complex: Equatable, IntegerLiteralConvertible, FloatLiteralConvertible { let x: Double let y: Double init(_ x: Double, _ y: Double) { self.x = x self.y = y } init(_ x: Double) { self.x = x self.y = 0 } init(integerLiteral x: IntegerLiteralType) { self.x = Double(x) self.y = 0 } init(floatLiteral x: FloatLiteralType) { self.x = x self.y = 0 } } func == (z: Complex, w: Complex) -> Bool { return z.x == w.x && z.y == w.y } func + (z: Complex, w: Complex) -> Complex { return Complex(z.x + w.x, z.y + w.y) } func - (z: Complex, w: Complex) -> Complex { return Complex(z.x - w.x, z.y - w.y) } prefix func -(z: Complex) -> Complex { return Complex(-z.x, -z.y); } func * (a: Double, z: Complex) -> Complex { return Complex(a * z.x, a * z.y) } func * (z: Complex, w: Complex) -> Complex { return Complex(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x) } func / (z: Complex, w: Complex) -> Complex { let w_inv = Complex(w.x / (w.x * w.x + w.y * w.y), -w.y / (w.x * w.x + w.y * w.y)) return z * w_inv }
  28. 28. この掛け算こそが複素数の本質 struct Complex: Equatable, IntegerLiteralConvertible, FloatLiteralConvertible { let x: Double let y: Double init(_ x: Double, _ y: Double) { self.x = x self.y = y } init(_ x: Double) { self.x = x self.y = 0 } init(integerLiteral x: IntegerLiteralType) { self.x = Double(x) self.y = 0 } init(floatLiteral x: FloatLiteralType) { self.x = x self.y = 0 } } func == (z: Complex, w: Complex) -> Bool { return z.x == w.x && z.y == w.y } func + (z: Complex, w: Complex) -> Complex { return Complex(z.x + w.x, z.y + w.y) } func - (z: Complex, w: Complex) -> Complex { return Complex(z.x - w.x, z.y - w.y) } prefix func -(z: Complex) -> Complex { return Complex(-z.x, -z.y); } func * (a: Double, z: Complex) -> Complex { return Complex(a * z.x, a * z.y) } func * (z: Complex, w: Complex) -> Complex { return Complex(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x) } func / (z: Complex, w: Complex) -> Complex { let w_inv = Complex(w.x / (w.x * w.x + w.y * w.y), -w.y / (w.x * w.x + w.y * w.y)) return z * w_inv } func * (z: Complex, w: Complex) -> Complex { return Complex(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x) }
  29. 29. 2. 複素数を動かしてみよう
  30. 30. class ComplexPlane : UIView { var unit: CGFloat = 50.0 var scale: CGFloat = 1.0 var points: [String: Complex] = [:] var colors: [String: UIColor] = [:] override func drawRect(rect: CGRect) { let ctx = UIGraphicsGetCurrentContext() let centerX = self.bounds.width / 2 let centerY = self.bounds.height / 2 // fill background CGContextSetFillColorWithColor(ctx, UIColor.whiteColor().CGColor) CGContextFillRect(ctx, self.bounds) // draw axises … } } 複素平面のViewクラス
  31. 31. let cplane = ComplexPlane(frame: …) cplane["1"] = 1 cplane["i"] = i let z = Complex(r: 2, θ: M_PI / 3) cplane["z"] = z let w = z * z cplane["w"] = w
  32. 32. DEMO : 動かしてみよう!
  33. 33. z と w の関係は?z w
  34. 34. Re Im 複素数 z は絶対値 r = ¦z¦, 偏角 θ= arg(z) を用いて、 z = r(cosθ + i sin θ) と書ける(極表示) r ✓ r = |z| ✓ = arg(z)z z = r(cos✓ + isin✓) z = r(cos✓ + isin✓) rcos✓ irsin✓
  35. 35. 複素数の掛け算を極表示で書き直してみると…
  36. 36. z = r(cos✓ + isin✓), w = s(cos + isin ) zw = rs{(cos✓cos sin✓sin ) + i(sin✓cos + cos✓sin )} = rs(cos(✓ + ) + isin(✓ + )) に対して、 つまり…、 加法定理
  37. 37. Re Im r ✓ s z w zw rs = rs(cos(✓ + ) + isin(✓ + ))zw 複素数の掛け算は「絶対値の積」 「偏角の和」だった!
  38. 38. Re Im 特に i^2 = -1 は、 「90 回転を2回すれば180 回転」 i2 = 1 i 90 i2 = 1
  39. 39. 3. なぜ複素数?
  40. 40. そもそもこれはどこから出て来た? i2 = 1
  41. 41. (例) 方程式: x^2 + x + 1 = 0 -10 -7.5 -5 -2.5 0 2.5 5 7.5 10 -5 -2.5 2.5 5 x2 + x + 1 = 0 この方程式は実数の範囲では解を持たない。 y = x2 + x + 1
  42. 42. 形式的に2方程式の解の公式を使うと、 ここで -3 を 3 i と置き換えて: は、x^2 + x + 1 = 0 の解になっている。 p 3 p 3i x2 + x + 1 = 0 x = 1 ± p 12 4 · 1 · 1 2 = 1 ± p 3 2 ↵ = 1 + p 3i 2 , = 1 p 3i 2
  43. 43. -10 -7.5 -5 -2.5 0 2.5 5 7.5 10 -5 -2.5 2.5 5 y = x2 + x + 1 y = x^2 + x + 1 は x = α, β で x 軸と交わっている…?y = x2 + x + 1 x = ↵, ↵? ? x, y を複素数と見てグラフを描くには、 残念ながら我々の世界では次元が1つ足りない。
  44. 44. Re Im Re Im z w w = f(z) = z^2 + z + 1 を平面から平面への写像と見て、 z の動きにあわせて w がどう動くか見てみる。z w w = f(z) = z2 + z + 1 代わりに、 f
  45. 45. DEMO w = f(z) = z2 + z + 1
  46. 46. Re Im Re Im z w z を半径を大きくしながら円上で動かす。z
  47. 47. Re Im Re Im z w 半径 1 のときに w = 0 となる点が2つある。w = 0
  48. 48. Re Im Re Im ↵ z w この2点が α, β で、f によって 0 に写されていた!↵, f ↵ = 1 + p 3i 2 , = 1 p 3i 2
  49. 49. Re Im Re Im z w したがって… 一般の n 次式の場合も同様に、 z を 0 から大きくしていけば、w は必ず 0 を通る ↵
  50. 50. 代数学の基本定理 複素数の範囲では必ず解を持つ! anzn + ... + a1z + a0 = 0n 次方程式 は、
  51. 51. コードを書いて自分で動かしてみれば、 数学はグッと身近になる!
  52. 52. 「SwiftComplex」github で公開してます:
  53. 53. 1/30(金) 「第1回 プログラマのための数学勉強会」開催! もう満席ですが、次も近いうちやります!
 発表者募集中!
  54. 54. Thanks! @taketo1024

×