数学の神秘、マンデルブロ集合をJavaで描画してみた。
マンデルブロ集合、美しいです。全体が部分の自己相似になっているフラクタル。
私がマンデルブロ集合を知ったのは、TEDでマンデルブロさんがご講演をされていたのを視聴してからです。ちなみにその講演はこちらから見れるようです。
ということでちょっとJavaで作って見ました。実行した結果は以下の通り。
以降、マンデルブロ集合の定義を簡単に確認し、その上でJavaでの記述へ進みます。
マンデルブロ集合
マンデルブロ集合とは何か、簡単に記載したいと思います。Wikipediaの記載がある程度わかりやすかったので引用します。 [mathjax]\begin{array}{l} z{n+1}=z{n}^2+C ・・・(*)
z_{1}=0 \end{array} で定義される複素数列 {zn}n∈N∪{0} が n → ∞ の極限で無限大に発散しないという条件を満たす複素数 c 全体が作る集合がマンデルブロ集合である。
※また、このサイトは、これ以降の記述の際の参考にいたしました。
具体的に計算した方がわかりやすいので、実際に計算してみましょう。 例えばC=1(= 1 + 0i)とすると、 (*)の式は、 ■Z2 = Z1 * Z1 + C = 0 * 0 + 1 = 1 ■Z3 = Z2 * Z2 + 1 = 2 ■Z4 = Z3 * Z3 + 1 = 5 ・・・・となり n → ∞ の極限で無限大に発散することがわかります。 そのため、C=1はマンデルブロ集合に属しません。
例えばC=0(=0+0i)の場合は、 ■Z1 = 0 ■Z2 = Z1 * Z1 + C = 0 * 0 + 0 = 0 ■Z3 = Z2 * Z2 + 0 = 0 ■Z4 = Z3 * Z3 + 0 = 0 ・・・・となり n → ∞ の極限で無限大に発散しないことがわかります。 そのため、C=0はマンデルブロ集合に属します。
といった要領です。
その属する点を青、属しない点を黒に塗ると、最初の図になります。
プログラム
全ての座標を計算するのは不可能なので、空間をセルに分割し、その代表点においてマンデルブロ集合に属す/属さないを(計算して)決定します。というわけでそのセルの大きさ、粒度によって結果が変わってきますので、セルを極小さくすれば近似的に真のマンデルブロ集合の絵に近づきます。
また、発散の条件を考える必要があります。n -> ∞ の場合に発散するかどうかを考えるのはきつそうです。先ほど紹介した参考サイトを見ると|Zn| > 2 となった場合に発散するそうなので、これに習い、指定した回数(MAX_COUNT)計算し|Zn| > 2 となった場合に発散するとしました。当然これも近似ですので、MAX_COUNTを増やしてあげれば、より真のマンデルブロ集合に近づきます。
クラス概要
構築したクラスの概要は以下。項番 | クラス | 役割 | 概要 |
---|---|---|---|
1 | MandelbrotSetViewer.java | Viewer | JFrameを継承したクラス。Viewer。 |
2 | MandelbrotSetModel.java | Model | マンデルブロ集合の計算Modelとなるクラス。 |
3 | ComplexNumber.java | 虚数計算 | 虚数の計算に使用するクラス。 |
4 | CellData.java | セルデータ | 各セルのデータを格納するクラス。 |
ソースコード
以下、ソースコードを記載します。ただし、最低限のコードなので、設定値も全てベタ書きしています。■MandelbrotSetViewer.java [code lang=“java”] package mandelbrotset.example;
import java.awt.*; import java.awt.geom.Line2D; import java.awt.geom.Rectangle2D; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.border.EmptyBorder;
public class MandelbrotSetViewer extends JFrame {
private JPanel contentPane;
private MandelbrotWriter writer;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
MandelbrotSetViewer frame = new MandelbrotSetViewer();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public MandelbrotSetViewer() {
writer = new MandelbrotWriter();
writer.calc();
viewProcess();
}
private void viewProcess(){
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 600, 600);
contentPane = new JPanel(){
public void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D)g;
//the size of 1 cell on panel
double dx = contentPane.getWidth()/(double)MandelbrotWriter.ARRAY_WIDTH;
double dy = contentPane.getHeight()/(double)MandelbrotWriter.ARRAY_HEIGHT;
for( int yArrayIndex = 0 ; yArrayIndex < MandelbrotWriter.ARRAY_HEIGHT ; yArrayIndex++){
double coordinateY = dy * yArrayIndex;
for( int xArrayIndex = 0 ; xArrayIndex < MandelbrotWriter.ARRAY_WIDTH ; xArrayIndex ++ ){
double coordinateX = dx * xArrayIndex;
CellData cell = writer.getArray()[yArrayIndex][xArrayIndex];
//decide the color of each cell
if(cell.flag){
g2.setPaint(Color.BLUE);
}else{
if(cell.calcNum %3 == 0){
g2.setPaint(Color.YELLOW);
}else if(cell.calcNum %2 == 0){
g2.setPaint(Color.RED);
}else{
g2.setPaint(Color.GREEN);
}
}
//draw the cell
Rectangle2D rect = new Rectangle2D.Double(coordinateX,coordinateY, dx, dy);
rect.setFrame(rect);
g2.fill(rect);
}
}
}
};
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
}
}
[/code] ■MandelbrotSetModel.java [code lang=“java”] package mandelbrotset.example;
public class MandelbrotSetModel {
public static final int ARRAY_HEIGHT = 5000;
public static final int ARRAY_WIDTH = 5000;
//the width of bounds
private double width = 3.0;
//the height of bounds
private double height = 3.0;
//the top-left X coordinate of the bounds
private double start_x = -1.5;
//the top-left Y coordinate of the bounds
private double start_y = 1.5;
private CellData[][] space = null;
public static final int MAX_COUNT = 1000;
public MandelbrotSetModel(){
space = new CellData[ARRAY_HEIGHT][ARRAY_WIDTH];
}
public CellData[][] getArray(){
return space;
}
public void calc(){
double dx = width / ARRAY_WIDTH;
double dy = height / ARRAY_HEIGHT;
for ( int yArrayIndex = 0 ; yArrayIndex < ARRAY_HEIGHT ; yArrayIndex ++){
double coordinateY = start_y - dy * yArrayIndex;
for ( int xArrayIndex = 0 ; xArrayIndex < ARRAY_WIDTH; xArrayIndex ++){
double coordinateX = start_x + dx * xArrayIndex;
space[yArrayIndex][xArrayIndex] = calc(coordinateX, coordinateY);
}
}
}
private CellData calc(double coordinateX , double coordinateY){
ComplexNumber x0 = new ComplexNumber(0,0);
ComplexNumber constant = new ComplexNumber(coordinateX,coordinateY);
ComplexNumber nxt = x0;
for(int i = 0 ; i < MAX_COUNT ; i ++ ){
//if the
if(nxt.length() > 2){
return new CellData(false,i);
}else{
nxt = mandelbrotCalc(nxt,constant);
}
}
return new CellData(true,MAX_COUNT);
}
public ComplexNumber mandelbrotCalc(ComplexNumber c,ComplexNumber constant){
double tmp_re = c.re * c.re - c.im * c.im + constant.re;
double tmp_im = 2 * c.re * c.im + constant.im;
return new ComplexNumber(tmp_re,tmp_im);
}
} [/code]
■ComplexNumber.java [code lang=“java”] package mandelbrotset.example;
public class ComplexNumber {
double re = 0;
double im = 0;
public ComplexNumber(double re, double im){
this.re = re;
this.im = im;
}
public double lengthSquared(){
return re * re + im * im;
}
public double length(){
return Math.sqrt(lengthSquared());
}
public static double length(double re,double im){
return re * re + im * im;
}
} [/code]
■CellData.java [code lang=“java”] package mandelbrotset.example;
public class CellData {
boolean flag = false;
int calcNum = 0;
public CellData(boolean flag , int calcNum){
this.flag = flag;
this.calcNum = calcNum;
}
} [/code]
実行結果
■全体図
■拡大図
部分が全体の自己相似になっているのが確認できます。