Rosio Pavoris a blog

Mandelbrots

I was bored, so I made this.

Rorschach test on fire

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.

real line

This is the number line, which you’re probably familiar with. It represents ℝ, so every real number has a place on this line.

complex plane

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

(Note that 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 set

The 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.

11 Comments

  1. John McCarthy said,

    > I prefer C89 for various reasons

  2. John McCarthy said,

    Could you add a footnote or a comment on this? Sorry for the double post.

  3. Cairnarvon said,

    No deep considerations, really.
    I feel that many of the changes in C99 are an undeserved acknowledgment of bad habits picked up from lesser languages like C++ (like // comments, which mean C now has significant newlines) and ugly non-standard extensions that elbowed their way into becoming de facto standards (which I realise is also what C89 was, for the most part). I also prefer the clarity of declaring all variables at the beginning of a block, which encourages cleaner code and forces you to pay attention to what you’re doing.
    There’s also the issue of spotty compiler support, though that’s barely an issue anymore.

    C99 isn’t all bad, and some of the changes were long overdue, but given a choice, I slightly prefer C89.

  4. Haxus the Lesser said,

    Real men use ‘j’ ;)

    > don’t even know if they teach complex numbers to most people
    Complex numbers were taught in my final year of High School in the UK (or maybe it was the penultimate year), but at that point Maths is no longer a requirement and so I’d say most people don’t know about them.

    Python’s complex number support seems a little incomplete to me. I can make peace with using abs for magnitude, but it doesn’t provide a method for getting the angle (Yes, I know arctan(imag-part/real-part), but still). Also, math.sqrt(-1) raises an exception. I’ll stick with Scheme.

  5. Cairnarvon said,

    Python 2.6 has cmath.phase for the angle.
    For square roots, there’s cmath.sqrt, the reasoning being that most users might get confused otherwise, which I can sort of see; though complex(-1)**.5 works just fine, and there’s really no good reason for having math.sqrt(complex(-1)) fail with a TypeError when the type of the argument is explicit. It even checks for complex numbers specifically, and suggests you use abs first, which is retarded in any situation.

    Blame Gweedo. He ruins everything.

  6. Asztal said,

    Haxus the Lesser: It was worse than that for me: I was taught complex numbers in the Pure 4 module, which wasn’t even taught in in A-Level maths course. That module was part of AS-Level Further Maths (which I took concurrently with the second year of A-Level maths).

  7. j0hn said,

    > 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

    can you expand or link to get some moar info about the why on that?

  8. Cairnarvon said,

    To be honest, no. The trite answer is that you can try to plot it with an arbitrarily large number of iterations, and observe that no point outside of the circle with radius 2 centered on the origin (the lightest gray area in the images) is ever in the set, no matter how many iterations you take, but that isn’t a proof.
    I’m told that there actually is a proof, but I haven’t been able to find it. I’ll keep an eye out, but if anyone else has it, please do share.

    (It seems like it shouldn’t be that hard to construct, actually, starting from the geometric definition of complex numbers.)

  9. j0hn said,

    Allright, i just would like to know why cuz there’s a lot of tutorials and stuff “teaching” how to plot mandelbrot set but noone actually explains why beyond distance 2 the set diverges… so, if you find it let me know :)

  10. Bo said,

    The arrow in the third paragraph is pointing the wrong way.

  11. Cairnarvon said,

    No it isn’t. It’s an assignment operator, not a function arrow (which is usually ↦).

Post a Comment

RSS feed for comments on this post · TrackBack URL