コンテンツにスキップ

フラクタル

フラクタルとは、異なるスケールで自己相似性を示す複雑な幾何学的形状やパターンの集合で、幾何学の分野の1つです。 その性質は、拡大・縮小しても全体が同じように見えたり繰り返されたり、拡大・縮小に伴って複雑な細部や相似なパターンが浮かび上がってくるものです。

自己相似性にはいくつかのタイプがあります。

  • 厳密自己相似性(Exact Self-Similarity):形状の一部分が全体と完全に同じ形をしているもの。例えば、コッホ曲線やシェルピンスキーの三角形は厳密自己相似性を持つ。
  • 統計的自己相似性(Statistical Self-Similarity):拡大縮小したときに形状が完全に一致するわけではないが、統計的な性質やパターンが似ているもの。自然界の多くのフラクタルはこのタイプに該当する。例えば、山脈の輪郭や雲の形状などが挙げられる。
  • 自己近似性(Self-Affinity):拡大縮小の比率が方向によって異なるもの。地形の表面や株価の変動などに見られる。

最も有名なフラクタルの1つに、マンデルブロ集合があります。 マンデルブロ集合とは、複素数平面上の各点にある数式を繰り返し適用し、その結果得られる数列が有界に留まるか否かを判定することで生成される複素数の集合のことです。 二次元のフラクタルをフラクタル図形、三次元のフラクタルをフラクタル構造などと呼びます。 フラクタルは、1970年代に数学者のBenoît B. Mandelbrotよって発見されました。 MandelbrotがIBM研究所で株価の予測アルゴリズムを研究していたときに発見した、株価の日足や週足などの時系列変動の自己相似が基礎となっているそうです。

マンデルブロ集合の他のフラクタルとして、コッホ曲線や樹木曲線などが知られています。 また、これらを応用して、コンピュータグラフィクスの山や海の描画に利用されたりもします。5

 
         コッホ曲線1          樹木曲線2

フラクタルは、数学や人工的な分野だけでなく自然界でも見られます。 例えば、カリフラワーの一種であるロマネスコは、螺旋が右向きに8本、左向きに13本などとなる、フィボナッチ数列を基本としたフラクタル構造を有しています。


              ロマネスコ3

コッホ曲線

コッホ曲線 (Koch curve) は、1904年にスウェーデンの数学者 Helge von Koch によって研究されたフラクタル曲線の1つです。 元の線分に対して一定の角度を持った小さな線分を繰り返し追加し、その小さな線分のそれぞれに同じ処理を再帰的に適用することで作成されます。

また、これを二次元的に拡張したコッホ雪片 (Koch snowfrake) というものもあります。

作成方法

点P0とP1をつなぐ線分 A に、以下の処理を再帰的に繰り返すことでコッホ曲線が得られます。


          コッホ曲線の作成手順4

  1. 線分A上に以下の3点を定めて、線分Aの左側に正三角形 Q0, Q1, Q2 を作ります。

    • Q0: 線分Aを1:2に内分する点
    • Q2: 線分Aを2:1に内分する点
    • Q1: Q0 を中心に Q2 を反時計回りに60度回転させて得られる点
  2. 元の点 P0, P1 と新しい点 Q0, Q1, Q2 を結ぶ

  3. 1で定めた点からなる、4つの線分 [P0, Q0], [Q0, Q1], [Q1, Q2], [Q2, P1] に対して、処理1,2を改めて適用する

1回の処理で線分の長さが 4/3 倍になるので、コッホ曲線は無限の長さを持ちます。 つまり、2つの頂点 P0, Q0 や Q0, Q1 の間に含まれるコッホ曲線の一部分は、P0, P1 の間に含まれるコッホ曲線全体と相似になっています。 これが自己相似です。

上記の処理1で導く3つの点 Q0, Q1, Q2 を数式で表すと以下の様になり、これを用いてプログラミングを行います。

(xq0,yq0)=(xa+13(xbxa), ya+13(ybya))(xq2,yq2)=(xa+23(xbxa), ya+23(ybya))xq1=xq0+(xq2xq2)cos(60)(yq2yq2)sin(60)yq1=yq0+(xq2xq2)sin(60)(yq2yq2)cos(60) \begin{align} (x_{q0}, y_{q0}) &= \left( x_a + \frac{1}{3}(x_b - x_a), ~ y_a + \frac{1}{3}(y_b - y_a) \right) \\ (x_{q2}, y_{q2}) &= \left( x_a + \frac{2}{3}(x_b - x_a), ~ y_a + \frac{2}{3}(y_b - y_a) \right) \\ x_{q1} &= x_{q0}+(x_{q2}−x_{q2})\cos(60)−(y_{q2}−y_{q2})\sin(60) \\ y_{q1} &= y_{q0}+(x_{q2}−x_{q2})\sin(60)−(y_{q2}−y_{q2})\cos(60) \end{align}

コッホ曲線は、その曲線の長さに特徴があります。 上記の処理を一回行うと各線分の長さはもとの43\frac{4}{3}倍になります。 また、nステップ後の曲線の長さは (43)n\left( \frac{4}{3} \right)^nになります。 折り曲げ処理を無限大回行うと、nn \rightarrow \inftyとなり、線の長さが無限大となります。

サンプルコード

コッホ曲線を始めとしたフラクタルをプログラミングする際は、再帰処理が欠かせません。 再帰処理は、定義している関数自身をその定義の中で改めて呼び出すことです。 これを再帰呼び出しと呼び、再帰呼び出しを行う関数を再帰関数といいます。 再帰関数は、ある条件になると別の処理を実行して大元の関数(呼び出し元)に戻ります。 再帰処理の回数は、呼び出し回数や数値との比較などを判断基準としています。

コッホ曲線を描画するjuliaコード
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using Plots

const radian = π * 60 / 180 # 正三角形の角度をラジアン化
const p0 = (0.0, 0.0)
const p1 = (100.0, 0.0)
const num_recursion = 5 # 繰り返し回数
const pos_x = 1
const pos_y = 2
points = [p0] # 各点の座標(x,y)を入れる配列(最終的にこれをプロット)

# Function definition
function koch(a, b, num_recursion)
    if num_recursion == 0
        return
    end

    q0 = (a[pos_x] + (b[pos_x] - a[pos_x]) / 3, a[pos_y] + (b[pos_y] - a[pos_y]) / 3)
    q2 = (a[pos_x] + (b[pos_x] - a[pos_x]) * 2 / 3, a[pos_y] + (b[pos_y] - a[pos_y]) *  2 / 3)
    q1 = (
        q0[pos_x] + (q2[pos_x] - q0[pos_x]) * cos(radian) - (q2[pos_y] - q0[pos_y])   * sin(radian),
        q0[pos_y] + (q2[pos_x] - q0[pos_x]) * sin(radian) + (q2[pos_y] - q0[pos_y]) * cos(radian)
        )

    koch(a, q0, num_recursion - 1)
    push!(points, q0)
    koch(q0, q1, num_recursion - 1)
    push!(points, q1)
    koch(q1, q2, num_recursion - 1)
    push!(points, q2)
    koch(q2, b, num_recursion - 1)
end

# Main part
koch(p0, p1, num_recursion)
push!(points, p1)

x = [points[i][pos_x] for i in 1:length(points)]
y = [points[i][pos_y] for i in 1:length(points)]
plot(x, y, linewidth=0.5)

savefig("koch_curve.png")
実行結果

マンデルブロ集合

マンデルブロ集合はコッホ曲線と異なり、いろいろな数学的なルールを設定することで、多種多様な形状を取ります。 コッホ曲線とは異なり、完全に相似な形状ではなくある程度類似した形が出現します。

マンデルブロ集合の定義は、 複素数 ZnZ_nに対し、Zn+1=Zn2+CZ_{n+1} = {Z_n}^2 + Cなどの式において Zn|Z_n|が発散しないような複素数CCの集合です。 Zn|Z_n|は、xとyを実数としてZ=x+yiZ = x + y iとしたときに、Z=x2+y2|Z| = \sqrt{x^2+y^2}で表される数とします。 発散しているかの具体的な判定は、Zn<2|Z_n| < 2などとします。 また、数式を変更することができ、Zn+1=Zn3+CZ_{n+1} = {Z_n}^3 + Cやその他の式を用いる場合もあり、全く異なる形状を示します。

マンデルブロ集合の周辺部には、繰り返し回数を数万回に大きくしてようやく発散する個所が多数あり、それらを探索するには強力なコンピュータが必要でした。しかし、近年のCPUの進歩により、より多くの人がマンデルブロ集合の周辺部の探索を開始しています。

サンプルコード

マンデルブロ集合の描画(というか塗り方)にはいくつかのパターンが考えられます。 今回は、実部をx軸に虚部をy軸にプロットした二次元複素平面に対し、集合が存在する部分を処理の繰り返し回数で塗る形で描画しています。 ハイライトされた部分に、上記の数式のマンデルブロの数式を記述しています。

マンデルブロ集合を描画するjuliaコード
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using Plots
# mandelbortを計算する関数。返り値は計算回数
function mandelbrot(x, y; max_iterations)
    z = zero(Complex(x, y))
    for k in 1:max_iterations
        z = z^2 + Complex(x, y)
        abs2(z) > 4 && return k
    end
    return max_iterations
end

function plot_mandelbrot(pos, s;
        # additional info
        max_iterations=1000,
        color=reverse(cgrad(:jet1)),
        figsize=(300, 300),
        kwargs...
    )

    # 点(x, y)で、mandelbrotをmax_iterations回だけ計算する
    x = range(pos[1] - s, pos[1] + s, length = 301)
    tate_x = transpose(x)
    y = range(pos[2] - s, pos[2] + s, length = 301)
    k = mandelbrot.(tate_x, y; max_iterations = max_iterations)

    # 計算結果を描画
    plot(; kwargs...)
    # extrema(itr): Compute both the minimum and maximum element
    plot!(xlim = extrema(x), ylim = extrema(y), xrotation = 90)
    heatmap!(x, y, log.(k); colorbar = false, size = figsize, color = color, tickfontsize = 6)
end

t = range(0, 1, length = 200)
ss =  @.exp( -log( 1 / 0.001 ) * t)

starting_point = [-1.2562, 0.0685]
# starting_point = (0.75, 0.0)
anim = @animate for s in [fill(ss[1], 20); ss; fill(ss[end], 20)]
    plot_mandelbrot(starting_point, s; xtick=false, ytick=false)
end
gif(anim, "mandelbrot.gif", fps=20)
実行例

参考文献