今回はrust nannouでマンデルブロ集合を描いてみました。
マンデルブロ集合集合は漸化式\(z_{n+1}=z_n^2+c,\; (n=0,1,2,\cdots, \; z_0=0)\)で定義される数列を無限にもってったときに発散しないっていうのを条件にした時の複素数の集合です。
詳しくは、調べてください。
カラー化のマンデルブロ集合の解説もあるよ⇒
マンデルブロ集合を描く前準備
マンデルブロ集合を描いていくために必要な要素は、複素数の計算、塗りつぶすの2つです。それぞれ見ていきましょう。
複素数の計算
マンデルブロ集合を描くのに必要な複素数の計算。複素数は、実部と虚部っていう2つの数によってつくられる数\(c=a+bi\; (i^2=-1)\)こんな数です。
マンデルブロ集合では、複素数の足し算と掛け算を使います。定義でいくと足し算は、実部と虚部をそれぞれ足した値、掛け算はそれぞれの要素を掛け算するんですが、多項式の掛け算のように計算します。
足し算:\((a+bi)+(c+di)=(a+c)+(b-d)i\)
掛け算:\((a+bi)(c+di)=(ac-bd)+(bc+ad)i\)
rustで複素数を扱う方法
rustで複素数を扱うのに今回は、今回は「num_complex」っていうクレートを使いました。
num_complexクレートを使うと、複素数を用いた計算が楽になるので、ぜひ使ってください。
今回は、足し算掛け算ぐらいしか使わないので、強引自分で実装することも可能ですが、せっかくなのでこのクレートを使ってみましょう。
num_complexの簡単な説明
複素数\(c=a+bi\; (i^2=-1)\)を宣言する
let c = Complex::new(a,b);
複素数c1,c2の足し算、掛け算は、
let c = c1+c2
let c = c1*c2
複素数の大きさ\(|c|=|a+bi|\)
c.norm();
今回使うのはこれぐらいです。
塗りつぶし
次に、塗りつぶす方法です。
マンデルブロ集合を描くときには、条件を満たしている個所を塗りつぶしていきます。塗りつぶす方法はいたって単純です。
まずは単純に塗りつぶす方法
for i in 0..n{
for j in 0..m{
draw.ract().w_h(1.0,1.0).x_y(i,j).color(WHITE);
}
}
こんな感じでコードを書けば、n,mの範囲を塗りつぶしていけますね。
このコードを拡張して、マンデルブロ集合の範囲を塗りつぶしていきます。
マンデルブロ集合描画の実際
では、前準備できたので、マンデルブロ集合を描いていきましょう。
手順は、「複素数を作って、それがマンデルブロ集合に含まれるかどうか判別、含まれていれば塗る、含まれていなければ塗らない。」という作業を繰り返していきます。
マンデルブロ集合の判別関数を作る
複素数cがマンデルブロ集合に含まれるかどうか、判別する関数を作っていきます。
マンデルブロ集合は、漸化式\(z_{n+1}=z_n^2+c,\; (n=0,1,2,\cdots, \; z_0=0)\)で定義される数列を無限に持ってった時に発散しないっていう条件を満たす集合なので、この漸化式を計算して、それが発散するかどうか調べます。
発散するかどうかっていうのは漸化式を計算していって\(|c|>2\)になると発散するかどうか調べればいいらしいです。この証明は調べるとすぐ出てくるので是非検索してみてください。
では、これを関数にしていきましょう
fn check(c: Complex) -> bool {
let mut z = Complex::new(0.0, 0.0);
for _i in 0..30 {
z = z * z + c;
if z.norm() > 2.0 {
return false;
}
}
return true;
}
複素数cを受け取って、発散するかどうか調べて、発散するとfalseを発散しなければ(マンデルブロ集合に含まれていれば)trueを返します。
let mut z = Complex::new(0.0, 0.0);
最初に複素数z(0,0)を定義します。漸化式を計算するのでmutつけないとエラーになります。
3行目以降のfor文の中で、漸化式を計算していっています。30回ぐらい計算してますが、これを深くすれば深くするほど、より細かく計算できます。深く計算するともちろん計算量も増えるのでマシンパワー使います。
5行目のif文で、発散するかどうか(|z|>2かどうか)判別
if z.norm() > 2.0 {
return false;
}
30回計算して一度も発散しなければ、trueを返します。
これで、マンデルブロ集合の判別関数は完成
集合の塗りつぶし
マンデルブロ集合に含まれるかどうかチェックする関数はできた。では、次に複素数を生成して、それが含まれていれば塗る作業をしていきましょう。
for i in 0..600{
for j in 0..400{
let re = i as f32 /200.0 - 2.0;
let im = j as f32 /200.0 - 1.0;
let c = Complex::new(re,im);
if check(c){
draw.rect()
.w_h(1.0,1.0)
.x_y(re * 200.0 + 150.0, im * 200.0)
.color(WHITE);
}
}
}
こんな感じです。
まずは、forでループ回していきます。今回は600回やりましょう。これ増やせば解像度が上がると思ってください。
3から5行目で複素数を定義しています。
reが実部、imが虚部。200で割って2引いてとかっていう計算をしてます。i,jは整数なので、それを小数点に変換して、ちょろっと平行移動って感じです。これを細かくすると細かい計算ができます。
6行目以下で、塗りつぶしをしています。
x_y()のところで、実部と虚部を座標に変換しています。倍率を今200倍にしてますが、大きくすれば大きくなります。小さくすれば小さくなります。あとは、画面に合わせて平行移動させています。
まとめ
こんな感じで、実部を600回虚部を400回ずつ計算して、その結果を塗りつぶす作業を繰り返して、マンデルブロ集合を描いています。
ネットを見ると、いろんな色がついていたり、マンデルブロ集合の周りがグラデーションになっていたりするものがあります。
それは、チェック関数で、深さをどこまで深く計算するか指定して、その深さによって色分けするようにして表現しているそうです。
そこらへんも今後実装できたら、また、アップします。
では今回はこの辺で!
コメント
[…] rust nannouでマンデルブロ集合を描いてみよう! […]
[…] これは、マンデルブロ集合ご描いた時とほぼ同じ手法です。気になる方は、マンデルブロ集合の描画の記事もどうぞ>>rust nannouでマンデルブロ集合を描いてみよう! […]