ABCLでマンデルブロ集合


この記事は、 Lisp Advent Calendar 2019の2日目の記事です。

ABCLでマンデルブロ集合

ABCL(Armed Bear Common Lisp)でマンデルブロ集合を表示させてみました。

ABCL(Armed Bear Common Lisp)はJavaで作られたCommonLispです。そのため、Javaが動く環境ならばどこでも動きます。また、Javaの豊富なライブラリを利用することができます。JavaのSwingを使ってマンデルブロ集合を描画してみました。







jnewでJavaのインスタンスの作成、jcallでメソッドの呼び出し、jstaticでクラスメソッドの呼び出し、jfieldでフィールドの参照ができます。詳細はソースを見てください。

test/Mandelbrot.lisp
; Mandelbrot Set
; (load "test/Mandelbrot.lisp")

; Java> new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
(defun createImage (w h)
    (jnew "java.awt.image.BufferedImage" 
        w h (jfield "java.awt.image.BufferedImage" "TYPE_4BYTE_ABGR")))

; Java> image.setRGB(x, y, rgb);
(defun putPixel (image x y rgb)
    (jcall "setRGB" image x y rgb))

; Java> g.setColor(color);
; Java> g.fillRect(x1, y1, x2, y2);
(defun fillRect (g x1 y1 x2 y2 color)
    (progn
        (jcall "setColor" g color)
        (jcall "fillRect" g x1 y1 x2 y2)))

; Java> Color.HSBtoRGB(h, s, b);
(defun convertHSBtoRGB (h s b)
    (jstatic "HSBtoRGB" "java.awt.Color" h s b))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; マンデルブロ集合の漸化式
; z[n+1] = z[n]^2 + c
(defun f (x y a b)
    (cons
        (+ (- (* x x) (* y y)) a)
        (+ (* 2 x y) b)))

; 発散するか? 絶対値が2以上になったか?
(defun isDiverge (x y)
    (> (+ (* x x) (* y y)) 4.0))

; 点(a,b)の発散するまでの回数を計算する。発散しない場合は-1を返す。
(defun calc (a b maxCount)
    (block exit
        (let* ((zx 0) (zy 0) (i 0))
            (loop
                (if (> i maxCount) (return-from exit -1))
                (if (isDiverge zx zy) (return-from exit i))
                (setq zz (f zx zy a b))
                (setq zx (car zz))
                (setq zy (cdr zz))
                (setq i (+ i 1))))))

; 計算した回数により色分けする。
(defun toRgb (count)
    (if (= count -1)
        (convertHSBtoRGB 0 0 0)     ; 黒
        (convertHSBtoRGB (* (mod count 1000) 0.002) 1.0 1.0))) ; グラデーション


; 指定された点を打つ
(defun drawMandelbrot1 (image a0 b0 zoom maxCount x y)
    (setq a (+ a0 (* (- x 100) zoom)))
    (setq b (+ b0 (* (- y 100) zoom)))
    (setq count (calc a b maxCount))
    (setq rgb (toRgb count))
    (putPixel image x y rgb))

; 画像に描画する
(defun drawMandelbrot (image a0 b0 zoom maxCount)
    (dotimes (y 200)
        (dotimes (x 200)
            (drawMandelbrot1 image a0 b0 zoom maxCount x y))))


; メイン
(let ((frame (jnew "javax.swing.JFrame" "Mandelbrot set"))
    (image (createImage 200 200)))
    (progn
        (jcall "add" frame (jnew "javax.swing.JLabel" (jnew "javax.swing.ImageIcon" image)))
        (jcall "pack" frame)
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.02d0 100)   ; Mandelbrot_001.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.01d0 100)   ; Mandelbrot_002.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.005d0 100)  ; Mandelbrot_003.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.002d0 1000) ; Mandelbrot_004.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.001d0 1000) ; Mandelbrot_005.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.0005d0 1000)    ; Mandelbrot_006.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.0002d0 1000)    ; Mandelbrot_007.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.0001d0 1000)    ; Mandelbrot_008.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.00005d0 1000)   ; Mandelbrot_009.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.00002d0 1000)   ; Mandelbrot_010.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.00001d0 1000)   ; Mandelbrot_011.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.000005d0 1000)  ; Mandelbrot_012.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.000002d0 1000)  ; Mandelbrot_013.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.000001d0 1000)  ; Mandelbrot_014.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.0000005d0 1000) ; Mandelbrot_015.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.0000002d0 1000) ; Mandelbrot_016.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.0000001d0 1000) ; Mandelbrot_017.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.00000005d0 1000)    ; Mandelbrot_018.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.00000002d0 2000)    ; Mandelbrot_019.png
        (drawMandelbrot image -0.745428d0 -0.113009d0 0.00000001d0 2000)    ; Mandelbrot_020.png
        (jcall "setVisible" frame T)))

実行方法は以下のとおり。実行には数秒~数十秒かかります。

C:\devel\lisp\abcl-bin-1.6.0>java -jar abcl.jar
Armed Bear Common Lisp 1.6.0
Java 10.0.1 Oracle Corporation
Java HotSpot(TM) 64-Bit Server VM
Low-level initialization completed in 0.23 seconds.
Startup completed in 4.251 seconds.
Type ":help" for a list of available commands.
CL-USER(1): (load "test/Mandelbrot.lisp")
T
CL-USER(2): (exit)

C:\devel\lisp\abcl-bin-1.6.0>

既知のツッコミ

  • CommonLispでは複素数が使えます。知らなかったので、実部と虚部に分解して計算しました。
  • デフォルトで浮動小数点は単精度だったので、数値の末尾にd0をつけて倍精度にしています。*read-default-float-format*で指定する方法もあります。

ではでは