マンデルブロ集合を描く

マンデルブロ集合が何か分かったところで、次は実際にどうやって描いたらいいのか、そのアルゴリズムを簡単に紹介します。このページはコンピュータやプログラミングの知識が多少あると読みやすいかと思います。

基本的なアルゴリズム

マンデルブロ集合を描くには、複素平面上のある範囲の点全てについて、マンデルブロ集合の定義にあてはめ、その点(複素数)が属するか否かを決めていかなければならず、人間の手で描くのは事実上不可能です。そこで通常はコンピュータを用います。

まず、描きたい範囲を等間隔の格子点に分割します。マンデルブロ集合は、実部・虚部共に-2から2の範囲にありますので、最初はこの範囲を指定するとよいでしょう。格子の間隔が小さいほど高解像度の画像が得られますが、多くの時間とメモリを要します。

次に、その全ての格子点について、定義である関数のくり返し計算を行います。前ページで説明しましたように|z_k| > 2となれば必ず発散しますから、そのときはくり返し計算を終了して“発散”と判定し、「マンデルブロ集合でない点」として決定します。また、このとき|z_k| > 2に至るまでのくり返し回数や、z_kの値などを保持しておくと、マンデルブロ集合の周囲に模様を描くことができます。

発散しない点(集合に属する点)や、発散に至るまでに非常に多くのくり返し計算を要する点(集合のごく近傍の点)は、いつまで経っても発散の判定ができないので、くり返し計算の回数の上限を決めておきます。この上限に達したら、“発散しない”と判定し、「マンデルブロ集合に属する点」として決定してしまいます。

このようにして、全ての点についてマンデルブロ集合であるか否かが決定したら、マンデルブロ集合に属する点と属さない点にそれぞれ別の色を付けてみます。すると、以下のような有名な形が描かれます。

マンデルブロ集合

図2-1:実部が-2.0~2.0、虚部が-1.6~1.6の範囲を描いたマンデルブロ集合。

以下は、原点の周囲4×4の領域を100×100の格子点に分割して描く、簡単な擬似コード(説明のための架空のプログラミング言語のコード)です。複素数の計算は、実部と虚部に分割して実数で行なっていますが、複素数の計算をサポートするプログラミング言語であれば、直接計算してもよいでしょう。

size = 4;                                // 描く領域の一辺の長さ
pixel = 100;                             // 描く領域の一辺のピクセル数
for (i = 0; pixel > i; i++) {            // x(実部)方向のループ
    x = i * size / pixel - size / 2;     // 定数Cの実部
    for (j = 0; pixel > j; j++) {        // y(虚部)方向のループ
        y = j * size / pixel - size / 2; // 定数Cの虚部
        a = 0;                           // くり返し計算に使う複素数zの実部
        b = 0;                           // くり返し計算に使う複素数zの虚部
        for (k = 0; 50 > k; k++) {       // 上限を50回とするくり返し計算
            _a = a * a - b * b + x;      // z^2+Cの計算(実部)
            _b = 2 * a * b + y;          // z^2+Cの計算(虚部)
            a = _a;                      // zの値を更新(実部)
            b = _b;                      // zの値を更新(虚部)
            if (a * a + b * b > 4) {     // もし絶対値が2を(絶対値の2乗が4を)超えていたら
                draw(i, j);              // (i,j)の位置のピクセルを「マンデルブロ集合でない色」で塗りつぶして
                break;                   // 次の点の計算へ
            }
        }
    }
}

ところで、マンデルブロ集合でない点は、マンデルブロ集合に近いものほど、なかなか発散せず、発散に至るまでに多くの計算回数を要します。したがって、集合のごく近傍を拡大したときなどは、計算回数の上限を大きくしないと、本当は「マンデルブロ集合ではない点」が誤って「マンデルブロ集合に属する点」として判定されてしまい、集合の形が本来より膨らんで境界が不正確になってしまいます。

逆に、計算回数を大きくしすぎると、境界が正確になっていくことで、画面の1ピクセル(計算上の格子点の幅)に満たない極めて細い繊細な部分などは、実際には存在するにもかかわらず、コンピュータの画面上ではつぶれて見えなくなってしまうこともあります。

次の画像は、計算回数の上限を変えて描き出したマンデルブロ集合の拡大図です。上限が小さいと、膨らんで不正確で不恰好な形(下図左)になってしまっていますが、一方、上限を大きくすると今度は繊細な模様が見えなくなってしまっている(下図右)ことが分かります。拡大率に応じて、計算回数の上限値は適切に選んでいく必要があると言えます。

計算回数の上限10回のマンデルブロ集合計算回数の上限20回のマンデルブロ集合計算回数の上限100回のマンデルブロ集合

図2-2:計算回数の上限を10回(左)、20回(中央)、100回(右)と変えて描き出した画像。
左は本来の形より膨らんでしまっており、右は逆に細い繊細な部分が見えなくなってしまっている。

着色の方法

よく見かけるマンデルブロ集合の画像では、マンデルブロ集合本体ではなく、その周囲(=数列が発散する範囲)に鮮やかな着色がされていることが多いですね。このように、マンデルブロ集合の周囲に色を付けたり模様を描く方法はいくつかあります。

最も簡単なのは、発散と判定されるまでのくり返し計算の回数に応じて色分けをすることです。例えば、発散と判定されるまでの計算回数が奇数なら黄色、偶数なら青色とすれば、このような縞模様が現れます。

計算回数の偶奇による着色

図2-3:発散と判定されるまでの計算回数が奇数なら黄色、偶数なら青色として描いたもの。

計算回数に対して好みのグラデーションを割り当てれば、自由に着色ができますね。着色の方法は最も好みが分かれるところでしょう。同じ範囲であっても、着色次第で芸術的になったり気持ち悪くなったり、全く印象の違う画像が得られます。例えば、下図左は、単純な色相のグラデーションを割り当てたもの(集合本体は黒)、右は闇の中で燃えさかるような印象に仕立てたもの(集合本体は白)です。「どのように色付けするか」は、数学やアルゴリズムの話になりますが、「何色にするか」という議論まで行くとアートの世界の話になりますね。当サイトのギャラリーもぜひご覧下さい。

計算回数に応じた着色計算回数に応じた着色

図2-4:計算回数に応じて好みの色を割り当てれば、さまざまな印象の画像がすぐに作れる。

また、発散は|z_k| > 2となったかどうかで判定すればよいと説明してきましたが、この数字は2以上であればいくつでも問題ありません。2より大きくすると、先ほどの縞模様の波が減って幾分なだらかになり、集合本体に沿ったラインに変化します(下図右)。

発散判定を2とした着色発散判定を10とした着色

図2-5:発散の判定を2で計算したもの(左)と10で計算したもの(右)。

今度は回数ではなく、発散と判定されたときのz_kの値に注目します。z_kの実部と虚部の符号が同じであればピンク色、異なっていれば緑色などとすると、以下のような放射状の格子模様が現れます。なお、この格子模様は、発散判定の値を2よりもだいぶ大きめの値(10~20程度)にすると、きれいに放射方向に繋がって見えるようになります(下図右)。

発散時の値に応じた着色発散時の値に応じた着色

図2-6:発散と判定されたときのz_kの値の実部と虚部の符号に応じて色付けしたもの。
左は、発散の判定を2、右は10で計算した場合。

ここまでに紹介した色付け方法は、最も単純で簡単な部類のものです。これ以外にも、さまざまな理論に基づいた色付け・模様付け・視覚化の方法が考案されています。

また、見かけることは少ないですが、マンデルブロ集合の内部にも、収束の速度や周期に応じて色分けをすることが考えられます。