プログラム
import java.applet.Applet; import java.awt.*; import java.awt.image.*; import java.awt.event.*; public class Mandel extends Applet implements ItemListener,ActionListener,MouseListener,MouseMotionListener{ int X0=10,Y0=120; final static int SIZE=400; Choice choice1,choice2,choice3; Label label1,label2,label3,label4,label5,label6; Button button1,button2,button3,button4; TextField tfield1,tfield2,tfield3; double creal,cimag,width; //Cの実数部の中心、Cの虚数部の中心、 //表示の範囲 double creal_d,cimag_d; //同上のクリックによる変化分 int count_max,color_base; //繰返し回数の上限、カラー階調数 int ratio; //クリック倍率 public class Complex{ double re,im; //実数部と虚数部 public Complex(){ //コンストラクタ re=0.0; im=0.0; } public Complex(double a, double b){ //コンストラクタ re=a; im=b; } public Complex add(Complex a){ //加算 return new Complex(this.re+a.re,this.im+a.im); } public Complex mult(Complex a){ //乗算 return new Complex( this.re*a.re-this.im*a.im,this.re*a.im+this.im*a.re); } public Complex conj(){ //共役(conjugate) return new Complex(this.re,-this.im); } public Complex sqr(){ //二乗 return new Complex( this.re*this.re-this.im*this.im,2*this.re*this.im); } public Complex cube(){ //三乗 return new Complex( this.re*(this.re*this.re-3*this.im*this.im), this.im*(3*this.re*this.re-this.im*this.im) ); } public double abs2(){ //絶対値の二乗 return this.re*this.re+this.im*this.im; } } public void init(){ //------------------------ GUI関係準備 ------------------------ // 中略 //----------------------- 初期化設定準備 ----------------------- // 中略 } //------------------------ GUI関係メソッド ------------------------ //チョイスが変更になった場合のメソッド public void itemStateChanged(ItemEvent ie){ // 中略 } //ボタンをクリックしたときのメソッド public void actionPerformed(ActionEvent ae){ // 中略 } //マウスを移動したときのメソッド public void mouseMoved(MouseEvent me){ int x=me.getX(); int y=me.getY(); double cr=(x-(X0+SIZE/2))*width/SIZE+creal; double ci=-(y-(Y0+SIZE/2))*width/SIZE+cimag; Graphics g=getGraphics(); g.setColor(Color.white); g.fillRect(X0+SIZE+15,Y0,200,100); g.setColor(Color.black); if(x>X0 && x<X0+SIZE && y>Y0 && y<Y0+SIZE){ g.drawString("Cの実数部=",X0+SIZE+25,Y0+20); g.drawString(String.valueOf(cr),X0+SIZE+35,Y0+40); g.drawString("Cの虚数部=",X0+SIZE+25,Y0+60); g.drawString(String.valueOf(ci),X0+SIZE+35,Y0+80); } } public void mouseDragged(MouseEvent me){} //マウスをクリックしたときのメソッド public void mousePressed(MouseEvent me){ int x=me.getX(); int y=me.getY(); creal_d=(x-(X0+SIZE/2))*width/SIZE; //変化分を記憶させる creal+=creal_d; cimag_d=(y-(Y0+SIZE/2))*width/SIZE; //変化分を記憶させる cimag-=cimag_d; width/=ratio; repaint(); } public void mouseEntered(MouseEvent me){} public void mouseExited(MouseEvent me){} public void mouseClicked(MouseEvent me){} public void mouseReleased(MouseEvent me){} //数値をカラーに変換するメソッド private Color countToColor(int count, int base){ int d,r,g,b; if(count<0) return Color.black; d=count % base; d*=256/base; int m=(int)(d/42.667); switch(m){ case 0: //青→シアン r=0; g=6*d; b=255; break; case 1: //シアン→緑 r=0; g=255; b=255-6*(d-43); break; case 2: //緑→黄 r=6*(d-86); g=255; b=0; break; case 3: //黄→赤 r=255; g=255-6*(d-129); b=0; break; case 4: //赤→マゼンタ r=255; g=0; b=6*(d-171); break; case 5: //マゼンタ→青 r=255-6*(d-214); g=0; b=255; break; default: r=0; g=0; b=0; break; } Color color=new Color(r,g,b); return color; } public void paint(Graphics g){ int count; int x,y; tfield1.setText(String.valueOf(creal)); tfield2.setText(String.valueOf(cimag)); tfield3.setText(String.valueOf(width)); double value=0.0; Complex c=new Complex(); double step=width/SIZE; for(x=-SIZE/2;x<=SIZE/2;x++){ c.re=x*step+creal; for(y=-SIZE/2;y<=SIZE/2;y++){ c.im=y*step+cimag; Complex z=new Complex(); count=0; do{ z=z.sqr().add(c); //Mandelbrot集合の繰返し演算(Z=Z*Z+C) value=z.abs2(); //絶対値(の二乗)の計算 count++; if(count>count_max){ //発散しないと判断 count=-1; break; } } while (value<4.0); //これを抜けると発散と判断 g.setColor(countToColor(count,color_base)); g.drawLine(X0+SIZE/2+x,Y0+SIZE/2-y,X0+SIZE/2+x,Y0+SIZE/2-y); } } } }
プログラムの使い方
アプレットを実行すると、上方左側に[繰返し回数の上限][カラー階調数][クリック倍率]の選択ボックスが現れますが、とりあえずはデフォルトのままでも使えます。再描画は、右上の[実行]ボタンをクリックするか、図形上でマウスをクリックします。[最初に戻す]で初期状態に戻れます。
[クリック倍率]は、粗く探索する場合には、「5」に設定しておくと良いでしょう。図形上でマウスをクリックすると、その位置が中心に移動し、同時に5倍に拡大されます。
[繰返し回数の上限]を増やして行くと、それまで黒かった部分(発散しないマンデルブロー集合のメンバーと思われていた部分)がカラーで表示される(発散することが分かる)ようになり、新しい発見が得られます。しかし、処理時間は長くなります。
「カラー階調数」を小さくしておくと、急激な変化部分では、きれいに表現できません。階調数が大き過ぎると平凡な色付けになってしまいます。最適な階調数があります。
その下には、[Cの実数部の中心][Cの虚数部の中心][表示の範囲]の数値入力領域があります。最初は、それぞれ初期状態になっていて、マウス・クリックによる探索に応じて変化しますが、外部から入力したい場合には、上書きが可能です。外部から入力した場合には、[外部設定]ボタンのクリックが必要です。その右にある[クリック前に戻す]ボタンは、マウス・クリックによる部分探索を1ステップ戻すのに使います。
画面上でマウスを移動させると、その時のCの実数部とCの虚数部の値が表示されますので、探索の助けにして下さい。
プログラムの実行結果
下図は実行結果の一例で、マンデルブロー図形のフラクタル性が良く現れています。初期設定の「表示の範囲」が2.5であったのに対し、ここでは8x10-6になっていますので、長さで約30万倍に拡大されています。ここにも、やはりマンデルブロー集合の基本形が現れています。[繰返し回数の上限]は4096になっていますが、1024では回数が不足し、発散しないので黒色で表示されてしまいます。16384にする必要はありませんでした。
まとめ
マンデルブロー集合の描画については、多数の著書や資料、ネット上のサイトがあります。しかし、プログラムを詳述したり、Javaアプレットのコードを添付してあったりするものはわずかで、内容的にも十分とはいえません。
一方、マンデルブロー集合の美しさに魅せられて、細かい部分の探索を行ない、色付けを工夫してすばらしい画像を提供しているホビイストも居ます。しかし、これらは手の内を明かしてくれません。
本稿は、それらのギャップを埋めるようなお役に立てば幸いと思っています。
繰り返し回数を上げて行かないと、新しい発見はありません。Javaでは時間がかかるので、忍耐力が要りますが、根気良く粘って、ホビイストに仲間入りして下さい。
参考資料
この記事は、筆者のホームページ『Visual C++ 6.0の易しい使い方(12) ―複素数計算ライブラリーを用いて、Mandelbrot図形を描く-(ただし、現在はVisual C++ 2005 Express EDition版に改定)』を改良してJava言語に書き直し、分かりやすく加筆したものです。
ウエブ上には、多数の優れた資料があり、下記はその一例です。
- 『The Encyclopedia of the Mandelbrot Set at MROB』
- 『Mandelbrot set - from MathWorld』
- 『Mandelbrot set - Wikipedia,the free encyclopedia』
プログラム的な解説では、以下の著書があります。
- 『Javaによる図形処理入門』 山本芳人 著、工学図書、1998年3月
- 『理工系のJava』 小国力・三井栄慶 著、朝倉書店、2004年3月
- 『Javaによるアルゴリズム事典』 奥村晴彦・杉浦方紀・津留和生・首藤一幸・土村展之 著、技術評論社、2003年5月
- 『CによるフラクタルCG』 渕上季代絵 著、サイエンス社、1993年4月
マンデルブロー集合を美的に論じたものでは、以下の著書があります。
- 『数理科学美術館-数学とアートの融合』 森川浩 著、工学社、2005年4月
- 『哲学者クロサキと工学者アイハラの神はカオスに宿りたもう』 合原一幸・黒崎政男・高橋純 著、アスキー、1997年7月