I was bored, so I made this.
Basic introduction to the Mandelbrot set and what this image represents follows.
As you may know from that Jonathan Coulton song, the Mandelbrot set is the set of points c in the complex plane for which the repeated application of z ← z² + c doesn’t diverge to infinity, where z is initially the origin of the complex plane.
This may not be very clear to the average person (I don’t even know if they teach complex numbers to most people), so let’s bring in some illustrations.
This is the number line, which you’re probably familiar with. It represents ℝ, so every real number has a place on this line.
The complex plane is just a two-dimensional extension of this, and it represents ℂ. The x-axis is the real component of the complex number, and the y-axis is the imaginary component. Every complex number has a point on this plane. We’ve marked the point with the real component 3, and the imaginary component 2. The shorthand notation for this number is 3 + 2i, i being the square root of -1.
The history of and need for complex numbers isn’t particularly relevant here, and the mathematical operations on complex numbers will be plain to anyone familiar with vector calculus (and for those who aren’t, the naive approach works just fine: (a + bi) + (c + di) = (a + b) + (b + d)i, and (a + bi) * (c + di) = ac + adi + bci + bdi² = ac + (ad + bc)i + bd(-1) = (ac – bd) + (ad + bc)i).
To see if a complex number is part of the Mandelbrot set, what we do is take the origin of the complex plane, the complex number 0 + 0i, square that, and add our initial complex number to it. Then, we take this new number, square it, and add our original number to it again. And then we do that again an infinite number of times. If we’re left with an infinite number at the end of this, our number is not in the Mandelbrot set.
Infinite iterations are pretty heavy on our CPU, though, so fortunately we can apparently also just do twenty iterations, and if the magnitude of our vector in the complex plane (that is, the distance from our point to the origin) is greater than 2, we know it will never return to the origin, but instead move out to infinity. If we program this in Python (which is a decent choice of language, because complex numbers are primitives in Python), this test looks something like this:
> def mandelbrot(c): > z = complex(0, 0) > > for i in range(20): > z = z * z + c > > return abs(z) < 2
abs doesn’t have its usual meaning when applied to complex numbers, but calculates the magnitude instead; if your language doesn’t support complex numbers, there will be slightly more code, but you can derive the length of that vector through a simple application of the Pythagorean theorem.)
We can represent the Mandelbrot set graphically by treating our canvas as the complex plane, and marking points inside the set in one color, and points outside another. The complex numbers, like the reals, aren’t enumerable, so each of our pixels will represent an infinite number of complex numbers, but that’s the price we pay for not having infinite-resolution screens.
The whole point of the Mandelbrot set is its fractal self-similarity (see right; that one’s from Wikipedia), of course, so that if you zoom in endlessly you’ll forever be running into bits that look like the whole set, so if you’re actually going to write this yourself (as everyone must at some point), it’d probably be good idea to build in some sort of zoom function.
Obviously just marking points based on whether they’re inside the set or not is only going to produce a monochrome image, so why do we (and most images of the set) have more colors? The answer is that the shades of grey represent how quickly z goes beyond the cut-off point. If
abs(z) is greater than 2 after only one iteration, it will be colored lighter than if it takes the full twenty iterations. This leaves us with the nicely (if not continuously) shaded image below:
mandelbrot function then just becomes:
> def mandelbrot(c): > z = complex(0, 0) > > for i in range(20): > z = z * z + c > if abs(c) > 2: > return i > > return 20
And the color is just the RGB tuple
(a, a, a), where
a = 255 - mandelbrot(c) * 255 / 20.
To make the animated GIF, though, what I did was vary the number of iterations, so instead of iterating twenty times in the worst case, it just went once to generate the first frame of the GIF, then twice for the second, &c. This generated successively more accurate approximations of the set, until we finally get to the actual set at twenty. The color formula is the same, except instead of dividing by 20, I divided by the maximum number of iterations.
(And actually I went all the way up to forty, to make the shaded areas not actually in the set lighter.)
The code, in C rather than Python, is here, for the curious. C99 has native support for complex numbers, but I prefer C89 for various reasons, so my code is a bit more complex. Hah.
Exercise for those playing at home: write a similar thing for a class of Julia sets; instead of squaring z, square c, and let z‘s initial value be something other than 0 + 0i. You may also want to increase the number of iterations, to get lighter colors, or come up with a better way of assigning colors altogether.
For good values for z, try -0.8 + 0.156i for a pretty one, or 0.285 + 0.01i for the one below.