This is a picture of the famous Mandelbrot set – generated from code we'll step through later on. There’s a very good
chance you’ve encountered this picture before in some shape or form – there are probably
thousands of implementations scattered around the web. If you’ve come here
because you’d like to learn how to make your own Mandelbrot generator, or would
just like to know more about it in general, then you’ve come to the right
place. By the end of this article you will have coded your own program which
displays this on screen, and will also have the know-how to create an
implementation entirely from scratch, perhaps in another language of your
choosing, as well.
We aren’t going to cover all the vast details and
mechanics underlying the Mandelbrot. The main goal of this article will be to
get you to the point that you can understand what’s going on fairly intuitively
within the system so you have enough background to code your own implementation
of it. I’ll then step through implementing a basic Mandelbrot generator in JavaScript
and HTML5.
We’ll address the concept of iterating functions and “orbits”
first. Chances are, you might not have heard these terms before but are already
aware of what they are on a conceptual level. In very simple terms, we find the
orbit of a particular mathematical function by plugging a particular value in a
mathematical function and iterating that value infinitely. Basically, we plug a
seed in, get the result, plug that result back into the function and then use
that result as the next value to be plugged in, and so on and so on.
For instance, let’s find the orbit of 2 in the
squaring function, x2. We
iterate like this:
(2) 2=4
(4) 2=16
(16) 2=256
(256) 2=65536
(4) 2=16
(16) 2=256
(256) 2=65536
And so on to infinity…
Alternatively, we could start with a seed of 0:
(0) 2=0
(0) 2=0
(0) 2=0
(0) 2=0
(0) 2=0
And so on… it’s easy to figure out that by starting
with 0 we’re always going to get back to 0.
Lastly, if we start with a fractional seed like ½, the
orbit will look something like this:
(½) 2=¼
(¼) 2=1/16
(1/16) 2=1/256
(¼) 2=1/16
(1/16) 2=1/256
We get increasingly closer to 0 each iteration, but
never quite land there.
The dynamics of this function are pretty boring –
there are only a few things that can happen with a given seed we provide:
- If we start with 0 and square 0, we’re going to end up with 0 again, which upon squaring will again result in 0, and again and again.
- If we start with a negative or positive fraction and square that, we will then get a positive fraction, which will then get repeatedly squared and continue to result in smaller fractions, continuously getting closer to zero but never quite getting there.
- If we start with any negative value slightly less than 1 (such as -1.005) or a positive value slightly higher than 1 (e.x: 1.005), and keep iterating we’ll eventually approach infinity.
- If we iterate with a seed of 1, we’ll just keep getting an output of 1.
It is important to point out that the orbits of 0 and
1 are fixed points – the sequence is constant.
Things get a little more interesting with functions
like x2 - 1. This function
exhibits the behavior like above but introduces a new type of orbit to it. If
we plug in -1 as a seed, we get the following sequence of outputs: 0,-1,0,-1,0,-1,
etc. This is known as a periodic point (a ‘2-cycle’ orbit).
You could use this information to generate a
visualization based on the behavior of the various outputs that you get for
each seed. For instance, with the squaring function – each seed whose orbit
flies off into positive infinity (for instance, 2), plot a white pixel, while
any of the seeds that stay within the a certain radius of the origin (seeds
<1 or >-1) plot a black pixel, etc. This however would yield a pretty
boring picture.
We aren’t going to do this, so why did I mention all
of that? Because it turns out that the Mandelbrot image is essentially an
illustration of the behavior of orbits generated from different seeds plugged
into a mathematical function – except this time, we’re dealing with a complex
function on the complex plane. And unlike the basic functions we talked about
earlier, this visualization will yield a more unique illustration.
But before we jump in and code a Mandelbrot generator,
there’s a couple more mathematical concepts you need to get up to speed with
like: what the complex plane actually is, and what is i? i represents imaginary
numbers. 1i or i, 2i, 3i, 4i, 5i, etc are all imaginary numbers. i2 is equivalent to -1. Why? Just roll with it for
now. Like I said, we could go on and on with the details here – but simply put,
imaginary numbers help us explain and come to solutions we wouldn’t be able to
with real numbers alone. For instance, knowing that i2 == -1, we can now say that the square root of -1 is i.
Imaginary numbers can be combined with real numbers to
form complex
numbers (2+4i, 3+7i, 6-2i, etc) are all complex numbers.
Take a look at the graph below to see an example of a
complex number plotted on a complex plane:
We’ll be working with the complex plane within our
Mandelbrot generator.
There are a multitude of properties inherit in these
numbers. Of immediate interest to us right now is the concept of a complex
number’s ‘absolute value’. Remember that the absolute value in regards to real
numbers is ‘distance from zero’. In this case, we can use the Pythagorean Theorem
to calculate a complex number’s absolute value based on its real and imaginary
components. We can calculate the absolute value of the complex number shown
above like so: which comes out to approximately 4.243. Keep that
method in mind – we’ll be using it again later in our code.
For the entirety of our time working with the
Mandelbrot set, we’ll be working with the following equation: Z2
+ C.
For Z, we’ll simply be starting with the value 0. The
C part will vary – we’ll choose various complex numbers along the plot to plug
in C and iterate from there.
Diving
into the code
Let’s write the code. We’re going to use Javascript and utilize the HTML5
canvas element for the graphics rendering (If the github gist isn't showing below, visit the full code on github).
The core of the logic begins with the solve function, which accepts two sets
of complex numbers – the first complex number represented by real0 and i0 will
be the value for the z part of the equation. The second
complex number (real1 and i1) will be the value for the C part of the equation. Solve() returns a complex number in the
form of a two element array for simplicity. Let’s go over how those two parts
are calculated.
First we have the real part which first involves
multiplying real0 by real0 (aka real0 squared) plus the product -1, and i0
squared. Why? Well let’s say we plugged 2+5i as a seed into our z2+c
equation:
(2+5i) 2 + (2+5i).
Which expands to: (2+5i)
* (2+5i) + (2+5i)
Ignore the third complex number in
gray for now. Let's foil this. The first step in calculating the new real part is the first set
of 2s (highlighted in blue) multiplied together, added to the result of the
first set of imaginary parts (the 5s highlighted in green) multiplied together.
And remember i2 is
equivalent to -1, which is why we’ll multiply whatever that value is by -1.
So we have realpart = (2*2)+(-1*5*5) = 4 + -25 = -21. At this point the real part of
our new complex number is equal to -24.
The next line starts to calculate the imaginary
portion of the complex number. As we continue foiling the example above, we
multiply 2 by 5, and then double that value (because we’ll be doing that
twice). So we have -21 for the real
portion of our complex number and 20 for the imaginary part: -21 + 20i.
Then there’s the last two lines right before the
return statement: we have to add in the complex number that we have for C (the
one above we have highlighted in gray). This part is easy: (-21 + 2) + (20i+5i)
= -19 + 25i or 25i – 19.
Then we come to the plotMandelbrot() function, where
everything comes together. The particular range on the complex plane
we’ll be targeting for the Mandelbrot is between -1.5 and 0.5 on the real axis,
and between -1 and 1 on the imaginary axis. Why this range? Anything beyond this
range is outside the Mandelbrot set – meaning that all the
orbits spiral off into infinity. You’ll get a better sense of what I mean once
you actually see this thing drawn on screen.
Next is the nested loop that traverses the complex plane and determine the orbits for each seed.
For each seed, we check whether it stays within the
bounds of the Mandelbrot or if it leaves the set. We do this by running the seed through our
solve() function repeatedly, and if after a certain arbitrary number of iterations the
magnitude of the complex number we left on exceeds 2.0, then we consider
that point to be outside the set and paint a black pixel at that point.
However, if the absolute value is <= 2.0 then we consider the point to be
within the bounds of the Mandelbrot and paint a black pixel. Lots of
arbitrary stuff here – you could swap the colors if you want, or even have
different colors represent points within and outside the set. You could even
introduce multiple colors – like having different colors represent how long specifically it takes for each
seed’s orbit to escape the set. Additionally, there are numerous ways to speed this code up and make it more efficient.
The full code for this Mandelbrot implementation here can be found on github:
The full code for this Mandelbrot implementation here can be found on github:
Further
Reading
In case you were wondering, imaginary and complex
numbers aren’t just for drawing neat looking pictures like the one above – they
do have significance importance in
various scientific and mathematical domains. Signal and sound processing,
certain branches of physics, for instance, are examples of fields which rely heavily on imaginary and
complex numbers.
Since this is a development and coding blog primarily,
I took a very high level approach to talking about the Mandelbrot in order to
get you to the level of being comfortable coding your Mandelbrot painters. I merely
scratched the surface of what’s going behind the chaos inside the Mandelbrot.
There are many questions you might be left with. For
instance, you might be wondering – what are those bulb like looking things in
the image? The answer to that question
along with more insight into the mathematics of dynamical systems in general
can be found by exploring this Boston University web page.
You might also want to check out this book: https://www.amazon.com/First-Course-Chaotic-Dynamical-Systems/dp/0201554062/.
This introduction to chaotic dynamical systems includes plenty of questions and examples relevant to the material in this tutorial, as well a myriad of other dynamical systems.
I hope this post demystified
Mandelbrot and provided some insight into putting together your own
implementation.
Once again, the full code featured here can be found on github: https://github.com/edev90/Mandelbrot-JS