Mandelbrot Fractal
Last updated
Last updated
In this exercise you will implement a Mandelbrot set generator, or rather an image generator. You should do some reading so that you understand the basics of what the Mandelbrot set is and why it can generate some beautiful images; this text only contains a minimal explanation.
The Mandelbrot set is defined as the set of complex numbers for which the sequence does not approach infinity. The value is defined as follows:
If you remember how to do the square of a complex numbers you know everything there is to know to start:
How do we know if a complex number belongs to the Mandelbrot set? We could of course start to compute for higher values and see if we approach infinity but that would of course take a very long time (to say the least).
An observation saves us from spending the rest of our lives computing : if then there is no turning back, will only increase in size. The absolute value of a complex number is of course:
We still do not want to compute forever; if the number actually does belong to the Mandelbrot set we will of course never hit the threshold. Therefore, we set an upper limit that will be the maximum depth of our computation.
So given a maximum value of , we can for any complex number say if it definitely does not belong to or if it could possibly belong to the Mandelbrot set. In the case where we know for sure that the number does not belong to the set we also have a value which was the point where . This value is the color we need to generate a beautiful Mandelbrot image.
Since we are going to work with complex numbers we might as well start by implementing a module to handle these. Let's make it simple and represent a complex number as a tuple with its real and imaginary values. Create a module Cmplx
that exports the following functions:
new(r, i)
: returns the complex number with real value and imaginary
add(a, b)
: adds two complex numbers
sqr(a)
: squares a complex number
abs(a)
: the absolute value of
You might want to use the sqrt/1
function exported from the Erlang :math
module when calculating the absolute value. You call Erlang modules like any module but the Erlang modules all have atoms as names.
The Complex module implements an abstract data type; the internals of how complex numbers are represented should not be visible outside of the module. We of course know, but we should not make use of this knowledge.
Do some test to see that the function works:
Brot.mandelbrot(Cmplx.new(0.8, 0), 30)
Brot.mandelbrot(Cmplx.new(0.5, 0), 30)
Brot.mandelbrot(Cmplx.new(0.3, 0), 30)
Brot.mandelbrot(Cmplx.new(0.27, 0), 30)
Brot.mandelbrot(Cmplx.new(0.26, 0), 30)
Brot.mandelbrot(Cmplx.new(0.255, 0), 30)
What is happening? Which values could possibly belong to the Mandelbrot set - how sure are you? Do some more testing, why stop at thirty iterations? Try fifty!
Before carrying on we should make sure that we can generate an image. You should have the file PPM
module that will write the final image to a file. Make sure that you can use this module and that you know where files are located when created.
The API to the module is:
write(name, image)
: where the name is the name (possibly full path name) of the file and the image is a list of rows where each row is a list of tuples {:rgb, r, g, b}
(each value being in the range 0..255).
So once we know that it is working we can carry on to produce some images.
We create one module (that in the end will be the one that you want to play with the most), the Color
module. This module should export a function convert(depth, max)
that given a depth on a scale from zero to max gives us a color.
The conversion of depth information to RGB values can of course be done in many different ways and the one presented here is only for inspiration.
What colors does this correspond to? Does it look anything like a rainbow? Close to a rainbow? The mapping from depth to colors is one thing that one can play with, its not at all given that the colors should be chosen base only on the depth, one might even want to know the distribution of depths in the whole image or reuse colors at different depths.
So we know how to find the depth of a complex number so why not try to compute the depth at all points in a rectangular plane. We create a module Mandel
that should calculate an image. The function that will be our interface to the module looks like this:
generating the complex number that corresponds to the pixel
calculate the depth of this value
convert the depth to a color
The only tricky issue is to generate the rows in "correct" order, it is easy to generate the image up side down or mirrored. In the end it does not mean very much but try to get it right.
When you can generate an image, write it to a file using the PPM
module. This code would hopefully give you a first look at the Mandelbrot set.
Generate a nice looking image, you will find the most interesting things close to the edge of the black set. This is where the fractals start to spin out of control and the beauty of the Mandelbrot set is found. It's amazing that so much information could be hidden in a function this simple.
Could we speed up the calculations? Are there any operations that are of unnecessary complexity? Can you include an image in your report (you would probably have to convert it to png)?
For no reason at all we will call our first module Brot
, it will implement the computation of the value given a complex value . We must of course give it a maximum iteration depth or we risk getting stuck in an infinite computation.
Implement a function mandelbrot/2
that, given the complex number and the maximum number of iterations , return the value at which or if it does not for any i.e. it should always return a value in the range .
The test/4
function should of course test if we have reached the maximum iteration, in which case it returns zero, or if the absolute value of z
is greater than , in which case it returns i
. Make sure that you use the functions exported from the Cmplx
module.
Let's assume that we have a depth of a point , with the maximum possible depth being . We could create five sections that divides the range to . Divide by and so that you have a fraction . Then multiply this fraction by four to generate a floating point from to . Now truncate the value to give you an integer from to (this is the section) and generate an offset that is the truncated value of .
The two values and will now be used to give you an RGB value. You can use the following transformation:
:
:
:
:
:
What is happening here? We want to generate an image of the size Width by Height. The upper left corner of this image is the point and the offset between two points is . This means that the first pixels of the upper row should correspond to the "depth" of $$x+yi$, $(x+k) + yi$, $(x+2k) + yi$$ etc and that the second row starts with $x + (y-k)i$.
To help the Mandelbrot generator from keeping track of this we simply provide a function that does the work. The trans function will take a pixel position (, ) and return a complex number that is the one we should compute the depth of. It is better to do this here and then we could more easily change the function rather than passing all the necessary information in arguments.
Now the rows function should return a list of rows, where each row is a list of colors. Each item in a row corresponds to a pixel at (, ) and the color is computed by: