Thursday, January 30, 2020

How to Code the Mandelbrot Set

A complex number plotted on the complex plan




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

And so on to infinity…

Alternatively, we could start with a seed of 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

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:

A complex number plotted on the 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: 

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


No comments:

Post a Comment