Continuing with my theme of making every hobby programming project I do have some relation to the Mandelbrot set, I recently whipped up a fun little app that allows you to explore the Mandelbrot set’s associated Julia sets.
You can check the app out here or keep reading if you want to understand what’s going on first.
The Mandelbrot set is defined as the set of complex numbers such that the sequence
does not diverge. Roughly put, a point is in the Mandelbrot set if we can continue squaring it and adding the original value forever without it getting arbitrarily large. We can generate images of the Mandelbrot set by giving assigning each pixel a complex value based on its x and y coordinates. (Typically, we’ll have x coordinates represent the real part, and the y coordinates represent the imaginary part, so the pixel at position would represent the complex number , where is the imaginary unit, .) We can then assign colors to each pixel depending on whether they are or aren’t inside the set.
In practice, we set some maximum number of iterations for each point, which we can call . We can take advantage of the fact that the sequence always diverges if there is any point with , and assume that a point belongs to the set if its corresponding sequence hasn’t gone outside that limit after iterations (i.e., if ). We can then even create a gradient of colors outside the set based on how many iterations it takes until we get a value bigger than 2. (Of course, this won’t be 100% accurate for any finite , but we don’t have infinite computing power to check iterations until the end of time, so we have to make do with checking finitely many iterations.)
Here’s some code we can use to check each point (adapted from here):
mandelbrotIteration(c: Complex) {
let z = c;
for (let i = 0; i < this.maxIterations; i++) {
z = z.mul(z).add(c);
if (z.abs() > 2) {
return i / this.maxIterations;
}
}
return 1;
}
Given some function that maps complex numbers onto other complex numbers (and satisfies some other technical requirements), we can define a set of complex numbers such that the sequence
does not diverge.
The Julia set for is the boundary of this set.
Here, we’re interested in the Julia sets based on functions of the form , which, you may notice, is the same as the function used to define the Mandelbrot set. The difference here is that, when generating the Mandelbrot set, the number that we add at each step is different for each point in the set (and equal to the starting value of the iteration), whereas when we generate these Julia sets, we add the same number regardless of the starting value of our iteration. That is, every number in the complex plane generates its own Julia set.
The code below shows an iteration we could use to render the Julia set for (adapted from here).
juliaIteration(z: Complex) {
for (let i = 0; i < this.maxIterations; i++) {
z = z.mul(z).add(this.c as Complex);
if (z.abs() > 2) {
return i / this.maxIterations;
}
}
return 1;
}
Notice that the value we add at each iteration is independent of the starting value . This means that each point in the Mandelbrot set has its own associated Julia set. In fact, the Julia set for will be nonempty if and only if is within the Mandelbrot set.
The app I created allows you to explore the relationship between the position of these points in the Mandelbrot set and their associated Julia sets. Check it out here.