"Of all the possible pathways of disorder, nature favors just a few"--James Gleick, Chaos: Making a New Science.
The introduction of Chaos Theory into the world of deterministic science shattered previous conceptions of what science should be, and spawned the entire discipline of Dynamic/Complex Systems as a subfield in nearly all of the hard sciences. This project is not an effort to break new ground, but to glean a visceral understanding of one key facet of Chaos Theory, and share what I have learned.
The Mandelbrot Set
Known colloquially as "God's Thumbprint", The Mandelbrot Set is a beautiful intersection of rigorous mathematics and unconstrained art. The Mandelbrot Set is one example of a type of pattern known as a factal; other examples include: the Julia Set, the Sierpinski Gasket, and von Koch Snowflake. This project displays it as a graph on the complex plane representing the inclusivity of complex coordinates in a set defined by a specific mathematical function. That function is:
Z = Z^2 + Ci
This is a recursive function, which means it is expected to be run more than once, and one iteration's output is used as the next iteration's input. A point, represented by
Ci in the equation above can be said to be in the Mandelbrot Set if, and only if, after an arbitrary number of iterations, it has not diverged towards infinity. For most points--exluding (0,0)--it can never be know with certainty if it is in the set; after a 1,000,000 iterations it may not have diverged, but it still might do so on the 1,000,001st. This is where chaos comes into the picture, because despite some semblance of a pattern when viewed as a graph, there is no heuristic that can predict whether or not a point is in the set other than running the recursive algorithm above. This inherent unpredictability explains why the graph contains such jagged edges. These jagged edges however, are not just random perterbations. They are a display of a property called self similarity; this is the notion that if one were to zoom in on a small section of the graph, one would see a shape that is similar (though not exactly the same) as the graph as a whole--no matter how far one zooms in, the graph never becomes smooth. This roughness is maintained ad infinitum. It can be easily shown, however, that points sufficiently far from the origin (i.e. outside of some radius) diverge quickly enough to be know, for certain, not to be in the set. This fact, along with self-similary, implies that the Mandelbot Set has a bounded area, but an infinitely long perimiter!
How to know if a point is in the set
Z, from the equation above, is initialized to
0 + 0i, and C is a constant complex number representing a point's coordinate on the complex plane. The recursive function is then run until the abitrary iteration limit is reached, or the length of vector Z has diverged towards infinity.
Who is Mandelbrot?
Benoit Mandelbrot was a Mathmatician (and somewhat of a Renaissance man) known for his discovery of the interesting properties of the eponymous Mandelbrot Set. He had many careers in which he was known for valuable contributions; his careers included engineering at IBM, teaching mathematics at Harvard, performing economics research, and several others. He is also known for coining the word fractal.
One key differentiator of this project from others is the choice of languages it was written in. Clojure/ClojureScript were chosen because of their practical mix of Functional Programming and useability. Clojure is in the LISP family of languages of which the poignant feature is the ability to treat code as data, resulting in constructs known as macros which are useful for an abundance of reasons.
The Quil library was used for visualization and event handling. The library is based on ProcessingJS, so much of its code is highly optimized. One issue I ran into though, was the performance of the
fill command. In a project like this,
fill needs to be called for every single pixel, and after using Chrome's built-in profiler, that function call was determined to be very costly.
Optimizing performance turned out to be the biggest challenge in this project. The initial development included highly functional code (no state or mutability), but when performance improvements needed to be made, it was uncovered through targeted benchmarking that the frequent function calls, as well as the creation and addition to lists had a severe performance impact. Functions which generated data to be fed through a transformation pipeline were replaced by
doseq which is an iterator that does not return a list of values, only
nil. The Mandelbrot recusion itself had to be pared down to its bare minimum as well, the process of which taxed the modularity and readability of the numerics namespace. This was necessary, however, because when iterating over every pixel on the screen and doing a deep recursion on each one, the overhead of any single operation can grow quickly.
In this project, that number of iterations for the Mandelbrot function has been hard-coded to 50. Using more would broaden the range of colors displayed, but can quickly become too expensive--whatever iteration count is chosen, must be run for every pixel on the screen.
In practice, it is useful to know as early as possible when a value is diverging. Usually, if the length of vector Z has exceeded 2, it will continue to diverge. So, with this in mind, 2 was used as the escape radius. However, from the code in the Mandelbrot iteration function (seen below), it can be seen that the number 2, does not appear anywhere.
(defn mandelbrot-set-iterations "Returns number of iterations of Mandelbrot procedure" [real imaginary max-iter] (loop [x 0 r 0 i 0] (if (and (< x max-iter) (< (+ (* r r) (* i i)) 4.0)) (recur (inc x) (+ real (- (* r r) (* i i))) (+ imaginary (+ (* r i) (* r i)))) x)))
The number 4.0 is actually used instead, because instead of calculating
sqrt(r^2 + i^2) and comparing it to 2, it is much faster to skip the
sqrt operation and compare it to 4 (which is
The improvement which stands out as most salient is certainly the performance. A refactoring of the visualization code so that RBG values are directly inserted into the JavaScrtipt VM's ImageData array rather than relying on Quil's
fill command is likely to offer a substantial gain. It might also be a psychological boon to render each line, one at a time, to provide a sense of progress.
View a running demo here, but please note that the server goes to sleep after 30 minutes of inactivity, so it make take ~30 seconds to load the page the first time.
The following is mostly provided by the Chestnut template
Open a terminal and type
lein repl to start a Clojure REPL
In the REPL, type
The call to
(run) starts the Figwheel server at port 3449, which takes care of
live reloading ClojureScript code and CSS. Figwheel's server will also act as
your app server, so requests are correctly forwarded to the http-handler you
(browser-repl) starts the Figwheel ClojureScript REPL. Evaluating
expressions here will only work once you've loaded the page, so the browser can
connect to Figwheel.
When you see a line like
Successfully compiled "resources/public/jackal.js" in 21.36 seconds., you're ready to go. Browse to
http://localhost:3449 and enjoy.
Attention: It is not needed to run
lein figwheel separately. Instead we
launch Figwheel directly from the REPL
Trying it out
If all is well you now have a browser window saying 'Hello Chestnut',
and a REPL prompt that looks like
resources/public/css/style.css and change some styling of the
H1 element. Notice how it's updated instantly in the browser.
src/cljs/jackal/core.cljs, and change
dom/h2. As soon as you save the file, your browser is updated.
In the REPL, type
(ns jackal.core) (swap! app-state assoc :text "Interactivity FTW")
Notice again how the browser updates.
Lighttable provides a tighter integration for live coding with an inline browser-tab. Rather than evaluating cljs on the command line with the Figwheel REPL, you can evaluate code and preview pages inside Lighttable.
Steps: After running
(run), open a browser tab in Lighttable. Open a cljs file
from within a project, go to the end of an s-expression and hit Cmd-ENT.
Lighttable will ask you which client to connect. Click 'Connect a client' and
select 'Browser'. Browse to http://localhost:3449
View LT's console to see a Chrome js console.
Hereafter, you can save a file and see changes or evaluate cljs code (without saving a file).
CIDER is able to start both a Clojure and a ClojureScript REPL simultaneously,
so you can interact both with the browser, and with the server. The command to
do this is
Put this in your Emacs configuration (
(setq cider-cljs-lein-repl "(do (user/run) (user/browser-repl))")
M-x cider-jack-in-clojurescript (shortcut:
C-c M-J, that's a capital
Meta-Shift-j), point your browser at
you're good to go.
To run the Clojure tests, use
lein doo phantom
Deploying to Heroku
git init git add -A git commit heroku create git push heroku master:master heroku open
Running with Foreman
Heroku uses Foreman to run your
app, which uses the
Procfile in your repository to figure out which
server command to run. Heroku also compiles and runs your code with a
Leiningen "production" profile, instead of "dev". To locally simulate
what Heroku does you can do:
lein with-profile -dev,+production uberjar && foreman start
Now your app is running at http://localhost:5000 in production mode.
Distributed under The MIT License (MIT)
Created with Chestnut 0.14.0 (66af6f40).