Python では複素数の演算が簡単にできます。例えば複素数 $3 + 4i$ は Python では 3 + 4j と書きます。虚数単位を $i$ ではなく $j$ とするのは工学系の慣習です。
z = 3 + 4j abs(z)
5.0
さて、複素数 $c$ が与えられたとき、$z_0 = 0$ から初めて $z_{k+1} = z_k^2 - c$ という漸化式を適用してできる数列で $\{|z_k|\}$ が有界のときの $c$ の集合を Mandelbrot(マンデルブロー、マンデルブロート)集合といいます($c$ の前の符号は $+$ でも $-$ でもかまいません)。この集合はフラクタルの例として知られています。
これを Python で描いてみましょう。結果をうまく表すため、$M$ を十分大きい整数として、$z_k \geq 2$ になる最小の $k \leq M$ によって $c$ の領域を色分けすることにします。
import matplotlib.pyplot as plt
import numpy as np
M = 100
def mandel(c):
k = 0
z = 0
while k < M and abs(z) < 2:
z = z ** 2 - c
k += 1
return k
vmandel = np.vectorize(mandel)
x, y = np.meshgrid(np.linspace(-1, 2.2, 640), np.linspace(-1.2, 1.2, 480))
z = vmandel(x + y * 1j)
plt.pcolormesh(x, y, z, cmap='RdGy', vmin=0, vmax=M)
plt.axis('scaled')
plt.show()

この黒い部分が本来の Mandelbrot 集合です。
cmap='RdGy' の部分を Choosing Colormaps in Matplotlib を参照にして変えれば(あるいは Creating Colormaps in Matplotlib を参考にして新しいカラーマップを定義すれば)、色が変わります。
これで十分高速に実行できると思いますが、さらに高速にするために、Numba を使ってみましょう。変更部分は
from numba import jit
@jit(nopython=True)
def mandel(c):
(以下同じ)
だけです。数倍速くなると思います。
たまたま Numba の対応する NumPy のバージョンより新しい NumPy をインストールしてしまったので from numba import jit でエラーになりました。この場合は pip install --upgrade numba を再実行すれば、NumPy のバージョンを落としてくれました。
「有界」というところをもうちょっと説明しておきます。
例えば $c = 0.5 + 0.5i$ のとき、数列 $\{z_i\}$ を複素平面でプロットしてみると、次のようになります。
import matplotlib.pyplot as plt
def mandel(c):
z = 0
x = [0]
y = [0]
for i in range(100):
z = z ** 2 - c
if abs(z) > 100:
break
x.append(z.real)
y.append(z.imag)
plt.clf()
plt.plot(x, y, "o-")
plt.axis('equal')
mandel(0.5 + 0.5j)
ある点に収束していくようで、$\{|z_k|\}$ は有界です。ところが
mandel(0.52 + 0.52j)
では急速に発散します。この境目が Mandelbrot 集合の境界です。
Last modified: