Fractals — Complexity from Simplicity (and a bit of Clojure)

Fractals — Complexity from Simplicity (and a bit of Clojure)

Enter the Dragon

Fractals have always fascinated me. I remember seeing a render of a Mandelbrot set back in the early 90s (when color screens started being accessible back home), and being completely mesmerized by it. I remember being even more fascinated by seeing a code-golf version of such a renderer, implemented in some 300 bytes of C.

Some days ago I came across the "dragon curve", and decided to give it a go in Clojure, as a way to learn a bit more and have some fun. I implemented it as a Lindenmayer system, which reveals the simplicity behind it.

L-systems are string re-writing systems, and have a set of constants, variables, rules, and a starting axiom. One starts with the axiom, and applies the rules to it. Each rule maps a variable to a new string that substitutes it. Constants are unaffected by the re-write, and remain in place.

In the case of the dragon curve, it can be represented by the following L-system (described nicely in the wiki):

    variables : X Y
    constants : F + −
    start : FX
    rules : X → X+YF+
            Y → −FX−Y

In order to "grow" the dragon, we iterate by applying the rules to the axiom, and then to the result, and so on. For example, after two iterations on the axiom, we have:

Iteration 1
Axiom FX
Rule for X FX+YF+
Iteration 2
Rule for X FX+YF++YF+
Rule for Y FX+-FX-YF++-FX-YF+

These simple rules, and the simple starting point lead to an increasingly complex string, where structure starts becoming visible. Of course, doing this by hand is extremely tedious, so we can automate it with a bit of Clojure.

We can start by defining the axiom, and the re-writing rules.

  (ns dragon.core
    (:require [hiccup.core :as hiccup]))

  (def start [:F :X])

  (defn rewrite [i]
    (cond
      (= :X i) [:X :+ :Y :F :+]
      (= :Y i) [:- :F :X :- :Y]
      :true [i]))

We can now see what happens when we apply the rules to the axiom:

(map rewrite start)
([:F] [:X :+ :Y :F :+])

This is a bit noisy due to the brackets, but we can see it's the same result we got by iterating by hand. Let's add a bit more functionality:

(defn dragon [segments iter]
  (if (zero? iter)
    segments
    (let [segs (vec (mapcat rewrite segments))]
      (recur segs (- iter 1)))))

Now we can get rid of the extra brackets, and do some more iterations.

(map (partial dragon start) (range 4))
([:F :X] 
 [:F :X :+ :Y :F :+] 
 [:F :X :+ :Y :F :+ :+ :- :F :X :- :Y :F :+] 
 [:F :X :+ :Y :F :+ :+ :- :F :X :- :Y :F :+ :+ :- 
  :F :X :+ :Y :F :+ :- :- :F :X :- :Y :F :+])

As we can see in the (slightly reformatted) results, the string's size and complexity keeps increasing.

In order to visualize this fractal, we need some rules on how to interpret it. This is also very simple, and in fact, it is pretty much the same as the turtle graphics that were used in the Logo language.

In this case, we will interpret the F constant as forward (that is, draw a line in the direction the turtle is facing, and a fixed distance). The + and - constants will respectively mean right 90 and left 90, that is, to rotate the turtle by 90 degrees in a given direction. And that's all there is to it (for now, anyway).

We can again automate this quite easily:

(def pi:2 (/ Math/PI 2))
(def vec:size 3)

(defn points [segments theta acc x y]
  (if (empty? segments)
    acc
    (let [s  (first segments)
          xs (rest segments)]
      (cond
        (or (= s :X) (= s :Y)) (recur xs, theta, acc, x, y)
        (= s :+) (recur xs, (+ theta pi:2), acc, x, y)
        (= s :-) (recur xs, (- theta pi:2), acc, x, y)
        (= s :F) (let [xx (+ x (* vec:size (Math/cos theta)))
                       yy (+ y (* vec:size (Math/sin theta)))
                       new-acc (conj acc (vector xx yy))]
(recur xs, theta, new-acc, xx, yy))))))

We basically go over the generated string, and interpret it according to the rules described above (ignore constants, draw when we find an F, and turn in the indicated direction when we find a + or a -. This gives us a list of points which we can then use to build an SVG polyline.

  (defn render-svg [plot-points]
    (let [width 1000
          height 1000
          xmlns "http://www.w3.org/2000/svg"
          style "stroke:#474674; fill:white;"
          points (apply str (map #(str (first %) "," (second %) " ") plot-points))]
      (hiccup/html [:svg {:width width
                          :height height
                          :xmlns xmlns}
                    [:polyline {:points points
                                :style style}]])))

Here we are using the hiccup library for rendering HTML, and creating a (potentially very large) polyline element with all the points we defined above.

Now we can render our dragon and enjoy it in all its glory!

  (let [d    (dragon start 15)
        pts  (points d 0 [] 500 500)
        html (render-svg pts)]
    (spit "dragon15.html" html)

/img/fractals/dragon15.png

If you are reading this in a modern browser with proper SVG support, you can also open the generated HTML output.

Tidying Up

The code snippets above are fun for playing in the Clojure REPL, but they're don't generalize well, and can be improved upon significantly. In particular, they require some fiddling with the SVG rendering view-port and the starting point of the fractal drawing. I've cleaned up the code a bit, and made it generic so it can support many other L-systems. It now lives on Github, along with some other fractal code that we'll discuss in an upcoming post.

The L-system code itself, if you are interested, lives here.

More Fractal Goodness

Lindenmayer systems can give rise to very many interesting fractals, and many self-similar constructs can also be represented as L-systems. Below you can see some of these.

Box Fractal

axiom : F+F+F+F
rules : F → FF+F+F+F+FF

Five iterations of this system yield this:

/img/fractals/box5.png

HTML/SVG version

Crystal Fractal

axiom : F+F+F+F
rules : F → FF+F++F+F

Like the Box above, this one also starts with a square (F+F+F+F). Six iterations yield:

/img/fractals/crystal6.png

HTML/SVG version

Hilbert Curve

axiom : X
rules : X → +YF-XFX-FY+
        Y → -XF+YFY+FX-

The Hilbert curve is a well-known space-filling fractal. It does get quite dense rather quickly. After only 9 iterations, it looks like this.

/img/fractals/hilbert9.png

HTML/SVG version (LARGE FILE!)

Zooming in a bit, we can better discern the structure:

/img/fractals/hilbert9zoom.png

Koch Curve

axiom : F+F+F+F
rules : F → FF+F+F+F+FF

This one also becomes complex very quickly. At four iterations, it looks like this:

/img/fractals/koch4.png

HTML/SVG version

Koch Snowflake

axiom : F--F--F
rules : F → F+F--F+F

This is another well-known fractal.

/img/fractals/snowflake9.png

HTML/SVG version

Rings

axiom : F+F+F+F
rules : F → FF+F+F+F+F+F-F

This one is quite eye-catching. After six itertations, we get:

/img/fractals/rings6.png

HTML/SVG version

Sierpiński Triangle

axiom : F-G-G
rules : F → F-G+F+G-F
        G → GG

The Sierpiński triangle is another very well known fractal. Unlike the ones seen above, it has two forward variables, F and G. After 8 iterations, it results in this:

/img/fractals/sierpinski8.png

More to Come!

In this post we have covered the basics of L-system based fractals. There is a bit more to them, as more "commands" can be embedded in the fractal string, such as moving the turtle without drawing, and pushing and popping state onto a stack, to allow for the generation of more complex patterns. The L-system processing code on Github has some partial support for those, but the renderer doesn't, so some further work is needed there.

A second part to this incursion into fractals is upcoming. There we'll look into two different types of fractals. The first type is based on affine transformations and stochastic processes, and the second one is the famous Mandelbrot set.

Avatar
Martín Varela
QoE Guy

I work mainly in Quality of Experience (QoE) for online services, and QoE management.