Arantium Maestum

プログラミング、囲碁、読書の話題

ClojureとQuilでClifford Attractor

昔のProcessing Advent Calendarをいろいろ漁っていたらこんな記事があった:

qiita.com

上記の記事の冒頭の絵は

{ (x_{n+1}, y_{n+1}) = (\sin(a y_n) + c \cos(a x_n), \sin(b x_n) + d \cos(b y_n)  ) }

で表されるアトラクターである。

以下の参考サイトにはもっと例が載っている。

Clifford Attractors

まさにClojureiterate関数の使いどころという趣があるので、Quilで書いて並べてみた:

Quil -L7l8xEpZC6Ma1Qckc63

コードを簡単に説明する。

a, b, c, dのパラメータを受け取り、新たに無名関数を返す高階関数clifford。無名関数lはアトラクターの計算式を表している:

(defn clifford [[a b c d]]
  (fn [[x y]]
    [(+ (-> y (* a) q/sin)
        (-> x (* a) q/cos (* c)))
     (+ (-> x (* b) q/sin)
        (-> y (* b) q/cos (* d)))]))

その高階関数に9つの違うパラメータを渡して、9つの違うアトラクター関数を作成する:

(def clifs 
  (vec 
    (map clifford 
         [[-1.4 1.6 1.0 0.7]
         [1.6 -0.6 -1.2 1.6]
         [1.7 1.7 0.6 1.2]
         [1.5 -1.8 1.6 0.9]
         [-1.7 1.3 -0.1 -1.2]
         [-1.7 1.8 -1.9 -0.4]
         [-1.8 -2.0 -0.5 -0.9]
         [-1.4 -1.6 -1.0 -0.7]
         [1.8 -1.5 0.4 0.9]])))

各フレームでのアップデートでは、最後に描画した点の座標をアトラクター関数に渡し、その結果をまた同じアトラクター関数に渡し、と200回繰り返して出来た200個の座標をvectorとして保持する、というのを各アトラクターごとに(つまり9回)行う。

update-stateで9つの関数と9つのvectorupdate-seriesmapし、update-series内でアトラクターごとに新しい座標200点を算出する:

(defn update-series [f vs]
  (->> vs last (iterate f) (take 200) vec))

(defn update-state [state]
  (vec (map update-series clifs state)))

ここでiterateを使っている。(iterate f x)(f x) (f (f x)) (f (f (f x))) ...の無限リストなのでClifford Attractorを表現するには完璧。

あとは9つの座標リストを少しずつずらして描画するだけ:

(defn draw-state [state]
  (doseq [[i vs] (map-indexed vector state)]
    (q/with-translation [(-> i (mod 3) (* 200) (+ 150))
                         (-> i (/ 3) int (* 200) (+ 150))]
      (doseq [[x y] vs]
        (q/point (* 40 x)
                 (* 40 y))))))

けっこう数字をいじっても面白いパターンが現れやすいので楽しい。