Uploaded by nicolastanki

Make Your Own Mandelbrot - Tariq Rashid

advertisement
MAKE YOUR
OWN
MANDELBROT
A gentle journey through the mathematics of the
Mandelbrot and Julia fractcals, and making your own
using the Python computer language.
TARIQ RASHID
www.allitebooks.com
Contents
Prologue
The Mandelbrot Set
Introduction
Who is this book for?
What will we do?
How will we do it?
Author’s Note
The Journey
Part 1: Concepts
Fractals
Some Mathematics
Functions
Iteration
Divergence, Convergence
Periodic Cycles ‘Flip Flopping’
Chaos
Milestone Check
Complex Numbers
Visualising Complex Numbers
Complex Functions
The c=(0.33 + 0.577i) Neighbourhood
Divergence Atlas
We’re ready!
Now We Can Make Our Own Mandelbrot
Completing the Atlas
The Recipe
Part 2: DIY
Working with Python
Python
Interactive Python, IPython
A Very Gentle Start with Python
Mandelbrot Set in Python
Exploring the Mandelbrot Set
Part 3: Even More Fun
Julia Sets
Mandelbrot and Julia Mountains
Surface Plot
Mandelbrot Mountains
Julia Mountains
Gentler Landscapes
Epilogue
Resources
www.allitebooks.com
Prologue
The Mandelbrot Set
The organically intricate and beautiful object you see before you is the Mandelbrot set. Despite
its complexity, and even haunting beauty, it is in fact the result of extremely simple mathematics.
The following images show close­ups, as if we had a microscope, of various parts of this
Mandelbrot set. You can see both the diversity of detail and also a constant theme with
recurring elements. They are so detailed and diverse that you might not believe that they are, in
fact, parts of the Mandelbrot set in the first image above.
www.allitebooks.com
www.allitebooks.com
www.allitebooks.com
www.allitebooks.com
www.allitebooks.com
The Mandelbrot set is, in fact, infinitely detailed. You could keep on zooming in and exploring it
forever without running out of detail or the patterns starting to repeat themselves exactly. All this
from a very simple and easy to understand mathematical recipe. That’s what this book is about.
www.allitebooks.com
Introduction
Who is this book for?
This book is for anyone who wants to understand what a Mandelbrot set is, how to make your
own, and the few easy but exciting mathematical concepts that go into making it.
This guide is not aimed at experts in mathematics. You won’t need any special knowledge or
mathematical ability beyond school maths. If you can add, multiply, subtract and divide then you
can make your own Mandelbrot set.
Interested readers or students may wish to use this guide to go on an exciting and beautiful
mathematical excursion. Yes, mathematics can indeed be exciting and beautiful, something
that's not so easy to see when you're focused on the school curriculum, solving quadratic
equations and churning through trigonometry puzzles.
Teachers can use this guide to demonstrate the genuine excitement and beauty of mathematics
­ often difficult to show through standard curriculum topics ­ at a level appropriate for secondary
school students.
I wish a guide like this had existed when I was a teenager struggling to work out what this
beautiful intricate Mandelbrot set actually was and how to make my own. I'd seen it in books, on
posters and even in music videos. At that time I could only find difficult academic texts aimed at
those already experts in mathematics and its jargon. All I wanted was for someone to explain it
to me in a way that a moderately curious school student could understand.
What will we do?
In this book we’ll learn about the few mathematical concepts that are needed to understand
what a Mandelbrot set is. These will be explained in a really easy, clear way, and will assume
absolutely no previous knowledge or expertise beyond very simple school mathematics. The
concepts we’ll walk through are functions, iteration, convergence and divergence, chaos and
complex numbers. These sound scary but they’re not ­ they’re really simple and actually exciting
because they’ll surprise us with their strange behaviour!
We want to understand these concepts before we actually provide a recipe for constructing a
Mandelbrot set because it's much more satisfying seeing the ideas come to life. It's like baking
your own cake from scratch. If you’re really not interested in the underlying concepts feel free to
jump straight to the recipe.
www.allitebooks.com
Once we’ve made our first Mandelbrot set, we’ll take idea and run with it in different directions.
We’ll try different colouring schemes, we’ll explore the related Julia sets, and even try to extend
the flat world of Mandelbrot sets into 3 dimensions.
Ultimately, I want to show how really simple mathematics can surprise us by behaving in ways
that are not boringly linear and predictable but actually unsettlingly chaotic. Even more than this,
I want to show that this really simple mathematics can be intricately and organically beautiful
when it treads a delicate path between boringly predictable and apparently random messy
disorder. This is the same beauty we see everywhere in nature, from snowflakes to clouds, from
the structure of lungs to the patterns of a cauliflower.
How will we do it?
The aim of this guide is to open up the concepts behind a Mandelbrot set to as many people as
possible. This means we’ll always start an idea somewhere really comfortable and familiar. We’ll
then take small easy steps, building up from that safe place to get to where we have just
enough understanding to appreciate something really cool or beautiful about the Mandelbrot set.
To keep things as accessible as possible we’ll resist the temptation to discuss anything that is
more than strictly required to make your own Mandelbrot set. There will be interesting context
and tangents that some readers will appreciate, and if this is you, you’re encouraged to
research them more widely.
This guide is deliberately split into three distinct parts. The first part will take us on the journey of
understanding and exploring the mathematical concepts behind the Mandelbrot set. We will
avoid talking about how we might practically get a computer to draw our own Mandelbrot set
because the details of getting a software environment up and running, and then learning to use
a computer programming language to calculate and plot the set can distract us from
understanding and exploring the key mathematical concepts.
The second part will take us on the practical journey to calculating and plotting our own
Mandelbrot set using computers. We’ll start by gently introducing a mathematical computing
environment, before gradually building up the computer instructions, or program code, to make
a Mandelbrot set. We’ll do this piece by piece so that everyone can understand the code, no
matter how new to computer programming they might be.
The final part will extend the core ideas into new directions. We’ll explore Julia sets, which are
intimately related to Mandelbrot sets, and are in my view even more beautiful. We’ll also create
and visualise three dimensional versions of these fractals.
And don’t worry, all the tools we’ll use will be free and open source so you won’t have to pay to
use them.
www.allitebooks.com
Author’s Note
I will have failed if I haven’t given you a sense of the true excitement and surprises in
mathematics. I will have failed if I haven’t shown you how mathematics can be beautiful, and in
a way that is organic, intricate and detailed much like nature around us. I will have failed if
anyone with school level mathematics stumbles and doesn’t complete the journey of discovery
and understanding of the mathematical concepts.
I welcome feedback to improve this guide. Please get in touch at makeyourownmandelbrot at
gmail dot com.
You will also find discussions about the topics covered here and others sparked by ideas
presented here at ​
http://makeyourownmandelbrot.blogspot.co.uk​
. You will also find an errata
there too.
The Journey
Here’s a map of the journey we’ll take. Feel free to speed through the bits you already
understand.
Part 1: Concepts
In this section will gently introduce just enough ideas and mathematical concepts to appreciate
the exciting dynamics underlying the Mandelbrot Set.
We’ll introduce fractals, functions, iteration, the sometimes surprising behaviour of functions,
and complex numbers, before we make our own Mandelbrot set.
We’ll stick to ideas in this part, and intentionally leave the distractions of computer programming
for Part 2.
Fractals
Fractals are really interesting objects. They’re interesting because they bust the myth that
mathematics and its objects are always boring, predictable, and are cannot be as beautiful or
intricate as objects we see in nature; clouds, mountains, flowers, water streams, snowflakes.
Let’s look some of these so­called boring, predictable, flat objects. A square, a circle, and even
a 3­dimensional cube or cylinder.
These objects are certainly simple compared to those we see in nature. Actually some people
do find these objects really fascinating, but I suspect most people wouldn’t want to stare at
these objects for a long time, exploring their nooks and crannies. This last bit is important ­
these objects don’t have nooks and crannies, they don’t reveal new details for us to explore the
longer we look at them. If we looked at them with a magnifying glass we wouldn’t find anything
new or surprising. In that sense, we can call them boring.
Now lets look at something totally unordered, noisy, following no rule or regulation, other than
being totally random.
The picture is of the familiar white noise we used to see on old television sets when they weren’t
properly tuned to a broadcasting channel. It’s not that interesting either. Actually we humans
have a tendency to try to project recognisable things onto noise, which is why we love finding
faces and dragons in the clouds! If we set that human tendency aside, we’d find purely random
objects boring.
Now look at the following objects; some clouds, tree branches, a romanesco cauliflower and
some leaves.
There is something about these objects that leads us to explore them a little longer. It’s not just
that they are very detailed, but that there is some semi­regularity about them. There appears to
be a theme that pervades them, a repeated pattern, but repeated not in that boring and easily
predictable way, but in a way that is somewhere between the two extremes of boring regularity
and totally messy randomness. This is an important idea. The idea that objects that appeal to
our human senses and minds are somewhere between boring trivial regularity and utter
randomness.
We’ll be coming back to this idea later, carefully treading a path between regularity and
randomness where mathematics produces some really intricate beautiful objects.
If we zoomed into the above images with a microscope or a telescope it would be difficult to
guess at what scale we were looking at the object. Each little floret of the cauliflower looks like a
collection of smaller ones, and you can’t guess whether the bit you’re looking at is 5 centimetres
wide or 5 millimetres. Each bit of cloud fluff looks like a collection of smaller cloud fluffs ­ is it a
metre wide or a kilometre? This is called s
​elf­similarity​
at different scales.
The following objects are less organic and natural than clouds and plants, but they do have that
self­similarity we talked about above. Their construction is much more regular. We include them
here because when people talk about fractals they often think of these objects too. Personally I
think they’re interesting to look at but the interest wears off quickly because they’re closer to the
‘regular’ world than the more organic and less predictable natural objects.
The first one is a Koch snowflake. The second is a Sierpinski gasket. You can easily find out
how to make them, but the basic idea is to take a simple regular shape, such as a hexagon or
triangle, and repeatedly add smaller versions of this shape at ever greater levels of detail.
The following summarises this spectrum of objects, from totally random to totally ordered, with
the interesting objects between the two. It’s as if the tension, the constant battle between total
order and total randomness, produces the most natural, organic, intricate, beautiful objects.
Some Mathematics
In this section we’ll introduce some mathematical concepts that are needed to make the
Mandelbrot set. For each concept we’ll start at an elementary place which should be familiar to
everyone, needing nothing more than basic school mathematics. We’ll then take easy steps on
a gentle journey of understanding to reach a place where we’ve understood just enough make a
Mandelbrot set, and have seen enough to appreciate some of the surprising behaviour of really
simple mathematics that leads to unexpectedly intricate and beautiful forms.
For the most part you’ll only need to be able to add and multiply. This will will take us quite far.
There will be a section where it will be helpful, though not essential, to be able to expand
brackets of simple expressions like (x+a)(y+b) and collect terms. Most 11­14 year olds can do
this with ease. You won’t need any mathematical capability beyond this at all.
You can skip this section if you really aren’t interested and want to jump straight to the recipe for
making a Mandelbrot set, but you’ll be missing out on some interesting insights and unexpected
but pleasant surprises.
Functions
Functions are an easy idea that we’ll build a lot on, so it’s worth making sure we’re familiar with
it. Let’s start, not with a definition, but some familiar examples.
The following picture shows a machine which we’ve labelled “+1”. It’s a machine that does work.
Its job is to take numbers in one end, and spit out a new number out the other end. This
machine adds one to whatever number is pushed into it. It can be used on any number we
throw at it, it’ll simply add one and throw out the new number. We call these reusable machines
that work on numbers, ​
functions​
.
Let’s see what this function does to other numbers.
It shouldn’t surprise anyone that this ‘add one’ machine, or function, simply increments the
number that is thrown onto it, so 1 is turned into 2, and 99 is turned into 100.
That was a really easy example but it was ideal to introduce the idea of a reusable function.
Some more jargon while we’ve got it easy: the number thrown into this function is called the
input​
, and the number that pops out is called the o
​utput​
. Here’s a summary of these mildly
technical words:
Because the idea of a function is an important building block, we’ll make sure we’re really
comfortable by looking at some more examples.
We can see functions for ‘add 3’ and ‘subtract 7’, ‘multiply by ­1’, ‘square’ and ‘divide by 3’ all of
which perform as expected. These functions are doing nothing more complicated than the
arithmetic children learn at school.
Iteration
Now think about what happens when we feed the output of one of these functions back into it
again as the input. This is not such a crazy idea. Many things in nature evolve according to
external forces and their current state.
If you were asked to predict the rabbit population for next season, you’d need to know about
factors like weather and food availability. You’d also need to know the current population,
because the next population very much depends on the current population. This is why
scientists find modelling using iteration useful for modelling natural phenomenon which evolve
www.allitebooks.com
over time.
The following diagram shows the idea of iterating a function. The input and output are the same
as before but this time we take the output and put it back into the function as input for the next
iteration.
Let’s try it with a starting number of zero, and the function “+ 1”. The first time we apply this
function we get 0 turned into 1, just as we saw before. Now we take this output 1 and pop it
back into the function as input and we get 2 popping out. Again, and we get 3. Then 4, 5, 6, 7,
… you get the idea. The numbers keep getting bigger in steps of 1.
Now let’s try it will a different number and a different function. Let’s start with “9” and apply the
function “divide by 3”. We get the sequence 9, 3, 1, ⅓, … You can see the numbers getting ever
smaller. We could go on for longer and the numbers would get miniscule. In fact, they’d never
get to zero, they’d just keep getting smaller by a factor of 3. No matter how small a number is,
dividing it by 3 never gives zero, just a smaller number.
Now lets try the function “multiply by ­1”. If we start with 4, we get the sequence 4, ­4, 4, ­4, …
and so on forever. The values simply flip and flop between positive 4 and negative 4, and they
don’t get any bigger or smaller.
Iteration ​
simply means doing the same thing again and again to produce a series of outputs,
just as we did above.
Let’s now find a more concise way of presenting how an initial value evolves as a function is
repeatedly applied. A table showing the initial value, and the subsequent values at each step, or
iteration, is a good start.
The following table shows how the initial value 2 evolves as the function “multiply by 2” is
applied 10 times. Some people call the i​
nitial value​
, the ​
seed value​
.
Function “multiply by 2”, initial value 2
Iteration
Value
0
2 (initial value)
1
4
2
8
3
16
4
32
5
64
6
128
7
256
8
512
9
1024
10
2048
This is a more compact view than the function diagrams we’ve used before. You can quickly see
the value grows bigger, and does so in a rather accelerated way. Let’s plot the information in
this table as a graph to visualise the growth.
Plotting graphs like this is a really good way of quickly portraying the broad nature of how a
function evolves an initial value. If we only had tables of numbers to look at it wouldn’t be easy
to appreciate this broad nature. We can see that starting with a value of 2 and repeatedly
applying the function “multiply by 2” results in values which grow not steadily but at an
accelerated rate, growing ever faster.
Now let’s visualise similar graphs for other functions to get a feel for the broad nature of the
evolution they create. This one is for the function “divide by 3” starting with a seed value of 10,
applied 10 times. You can see that the values get smaller but this time changes between
successive values are smaller too. That is, the speed at which the initial value of 10 get
progressively smaller actually slows down. This is in contrast to the previous “multiply by 2”
function which showed accelerated growth.
We considered before how the function “multiply by ­1” simply flipped the value from positive to
negative. Let’s be adventurous and visualise that function, but tweaked a little to apply a
shrinking factor at each step, a bit like the “divide by 3” function but weaker. The following plot
shows the evolution of values for the function “multiply by ­0.8” with a starting value of 2.
Now this is a more interesting plot. The values evolve and do flip between positive and negative
values, as you’d expect if you repeated multiplied by a negative number. The values also seem
to get smaller too, and this is the effect of multiplying by a fraction of 1.
We’ve reached a milestone here. We’ve found a really simply function “multiply by ­0.8” which to
most people looks really boring, but which produces some quite dynamic behaviour. Behaviour
which reflects some of the more interesting things we might see in nature, such as the decay of
a pendulum swing or the vibrations of a plucked guitar string fading away.
It is worth restating the importance of this again. We’ve connected very simple mathematical
functions to behaviour which is unexpectedly more dynamic and complex. Unexpected because
for many of us who laboured away with functions and mathematical expressions at school found
they behaved in a really boring way. The functions we worked with, particularly because of the
way we applied them, behaved quite linearly and predictably. And if they weren’t linear they
were fairly boring curves. Remember endlessly plotting quadratic functions?
Divergence, Convergence
Looking back at the graphs we’ve already plotted we notice that the values which result from
repeated applying a function keep growing forever, or they get smaller and smaller.
If the values keep growing forever we say the values d
​iverge​
. They sort of get out of hand. We
saw the graph for the function “multiply by 2” with starting value 2, show the successive values
getting bigger and bigger. There is no upper limit to how big those values could get. If you think
of a huge number, that value will eventually get bigger than your huge number given enough
applications of “multiply by 2”. Even if had another go at coming up with an even bigger number,
this function will eventually produce values bigger than your humongous number. We say this
function is ​
divergent​
.
Now look at graph for the function “divide by 3” with starting value 10. We saw the successive
values get smaller and smaller. This function will produce values smaller than any small number
you can think of, given enough iterations. We can see the values got closer and closer to zero,
but never reach it. We say this function c
​onverges t​
owards zero. The function “divide by 3” is
convergent​
.
Do all functions either diverge to ever bigger numbers or converge to ever small numbers
approaching zero? Are these two the only two possibilities? This is a good question to ask. It’s
the kind of thinking that mathematicians like to do. They like to explore some specific examples,
just like we have above, and then try to generalise and see if the ideas apply everywhere.
Sometimes they do, which is great for mathematicians, but sometimes they don’t and need
more exploring and refinement. Sometimes the ideas never go anywhere, but hey, that’s life!
Let’s consider another function “add 0.8 * (5 ­ input value)“ with start value 1. Let’s be clear what
this function means. It means take the input value, and take it from 5, then multiply the result by
0.8, and add this resultant value to the input value. You can see we’re using the previous input
value twice when we apply the function. This is okay, it doesn’t break any rules.
Just to be sure, let’s try it once. We have a starting value of 1, so (5­1) is 4. Then we’re making
it a little smaller by multiplying it by 0.8. So 0.8*4 is 3.2. Finally we’re adding this to the input
value which was 1, to get 1+3.2 = 4.2. It’s a little more involved but still only uses the very basic
adding and multiplying we’re all familiar with.
The following table shows the values worked out as this function is repeatedly applied 10 times.
Function “add 0.8 * 5 ­ input value)”, initial value 1
Iteration
Value
0
1 (initial value)
1
2
3
4
5
6
4.2
4.84
4.968
4.9936
4.99872
4.999744
7
8
9
10
4.9999488
4.99998976
4.999997952
4.9999995904
We can see that the successive output values get closer and closer to 5. If we carried on
working out many more iterations, the values would even closer to 5. In fact you could think of
any number that is really really close to 5, this function would eventually produce values that
were even closer than the number you thought of.
This is an important find, because we have a function that converges to a value that isn’t zero,
in this case it converges to 5. The graph of successive values makes this visually clear.
Out of interest, does this function still converge towards 5 if we started with a different seed
value? Let’s try it again starting not with a value of 1, but with 9, a value deliberately chosen to
be on the other side of 5. The following plot shows this function’s outputs do indeed converge
towards 5 from 1.
We explored different starting values because we wanted to show that some functions will
converge to their target no matter what the starting values are. You can try other starting values
for any of the functions we’ve looked at above, and you’ll see this is true. Mathematicians would
say these functions are not sensitive to their starting values.
In contrast, the simple iterated function “square the input” is sensitive to its starting value. The
following graph shows three sequences from this function, with starting values 0.9, 1.0, and 1.1.
These seed values are chosen deliberately to be close to 1.0.
We can see that even though the seed values are very close to each other, the resultant
sequence of values from iterating the function gives starkly different results. Unsurprisingly, a
seed of x=1.0 results in subsequent values of 1 forever. However, a seed of c=0.9 results in a
sequence which converges towards zero. A seed value of x=1.1 very rapidly diverges to ever
larger values ­ it explodes!.
We can start to think about ​
regions o
​f the input space and categorising them according to how
they impact the evolution of values from an iterating function. So for this “square the input”
function, we have 3 regions; convergence to zero where the seed x is less than 1, convergence
to a finite value when the seed x is exactly 1, and divergence when x is more than 1. The
following shows these three regions for the “square the input” function.
This idea of marking regions according to the kind of function behavior that results from them is
a core idea in making our own Mandelbrot set. This is because the Mandelbrot set is just that, a
kind of atlas marking out the regions according to their behaviour when a function is applied
iteratively.
All of this helps us answer the question we asked ourselves above; do all functions diverge to
ever bigger numbers or converge towards zero, and nothing else? We’ve done some really
good mathematical research and shown that there is another case, which is functions
converging to non­zero values.
Let’s summarise what we’ve found so far: functions which diverge, and functions which
converge towards zero, and some which converge towards non­zero values.
Periodic Cycles ‘Flip Flopping’
Are there really no other kinds of function behaviour beyond diverging or converging towards
zero or a non­zero value?
We already know the answer to this question because we saw the flip flopping function “multiply
by ­1, with starting value 4” which resulted in values +4, ­4, +4, ­4, … and these don’t grow ever
larger, nor do they get ever small towards zero. Let’s plot it, so we can visualise it.
We have a new interesting class of function here, whose values flop flop between two values. A
more scientific way to say this is that the values ​
cycle b
​etween two values. Some people say
the function is ​
periodic​
.
We can now update our “world map” of functions to include periodic functions.
Chaos
We’re now ready to explore a really interesting behaviour of functions, which was only really
appreciated in the last 100 years of history. Given that many mathematical ideas go back
thousands of years BC, this is really remarkable. It’s amazing that here we are, having covered
some fairly basic concepts using only school mathematics, and we can now explore some really
interesting behaviour of functions that seemed to elude mathematicians for those thousands of
years.
Let’s look at an iterative function which has was developed by scientists trying to model
population growth. Its’ called the Logistic Map, but the name doesn’t really explain much so let’s
not worry about it. The following diagram shows this function.
This function seems to contains strange new symbols but don’t worry it doesn’t contain any new
concepts that we haven’t already done. Let’s explain it. The input is the same as before, its the
number we put into the machine to get an output, another number. We’ve now given a name to
the input and called it x.
Mathematicians often call things x when they really just want to say “any number” or
“something” but want to use less words. They’re not just being lazy, it’s a neat way of saying
something that applies to numbers in general. An example is saying “if x is a whole number,
then 2x is always even”. But why did we bother naming the input x when we haven’t before? We
did this because we use this input value more than once in the function and it would get terribly
wordy if we didn’t have a concise way of recalling the input value.
www.allitebooks.com
Looking at the function we recognise some parts of it easily. We can see that we need to take
the input value x from 1. That’s the part shown as (1­x). Then we can see we need to multiply it
by the input value again. That’s the x * (1­x) part.
It does seem odd that we’ve used the input value twice, but that’s fine, there’s nothing weird or
rule breaking about it. In fact the function we used before “multiply by 2” could have been written
as “x + x” which is the same thing as multiplying x by 2, and uses the input value twice.
All this leaves us with the part where we have to multiply the x * (1­x) with something called r.
Again mathematicians like to give short names to things they don’t want or need to be specific
about. So here r is just a number we haven’t specified. It could be 1, or it could be 2, or it could
be a small 0.25 or a big 7.18. We do however need to stick to a value we choose for the
duration of all the iterations of this function, and make sure we’ve told anyone reading our
results which one we chose.
There’s one more thing. The diagram of the function says the seed value for x must be between
0 and 1. That’s fine, mathematicians sometimes do define functions which are only valid for
certain ranges of input. Nothing will prevent us from trying seed values greater than 1 or less
than 0 but the function was designed to be used for seed values between 0 and 1.
So let’s try it. We need to make two choices, firstly the value of r we’ll stick to, and secondly the
starting value of x we use as the first input. We’ll choose a starting value of x as 0.2 and set r as
1. This time we’ll also do more iterations than the 10 we did before; we’ll do 50 iterations.
Because this function seems like it’s a little more complex than those we looked at before, we’ll
show the table for the first 10 and the last 10 iterations. And to avoid any doubt, let’s explain the
very first iteration. We start with x as 0.2 and r=1. Then r.x.(1­x) is 1 * 0.2 * (0.8) = 0.16 which is
the output value. This is then put back into the function as input for the next iteration which gives
0.1344, and so on.
Function “r.x.(1­x)”, initial value x=0.2, r=1
Iteration
0
1
2
3
4
Value
0.2 (initial value)
0.16
0.1344
0.11633664
0.10280242619351
5
6
7
8
9
10
… (not showing iterations 11 to 40)
41
42
43
44
45
46
47
48
49
50
0.092234087362238
0.083726960490693
0.076716756577683
0.070831295837884
0.06581422336781
0.061482711370302
...
0.020671254252617
0.020243953500241
0.019834135846921
0.019440742902127
0.01906280041754
0.018699410057781
0.018349742121272
0.018013029085355
0.017688559868525
0.017375674718303
Let’s plot a graph to visualise how the function behaves.
The values seem to get smaller but at a slowing rate, like a gentle children’s slide. Nothing
exciting here, we’ve seen this kind of behaviour before.
Let’s try this again but this time set r as 2, not 1 as we did above. The following is the resultant
graph of successive function values.
This is surprising! The same function but with the innocuous parameter r changed from 1 to 2
has changed the behaviour entirely. With r=1 we had the values converging towards zero, now
we have them converging towards 0.5. We didn’t change the function, only the value of one
parameter within the function, and we got different behaviour.
We’ve tried r=1 and r=2, now try r=3. Will we be surprised again? The following is the graph for
this function with r set to 3.
Wow! The same simple innocent looking function, has now given us behaviour which oscillates,
like the periodic flip­flopping function we saw earlier. Again, we didn’t change the function to get
this very different behaviour, we simply modified the value of one of its parameters
It’s really worth taking a step back here to appreciate what we’ve done. We’ve found a function
which is really simple in its makeup. It’s doing nothing more than really basic school maths,
multiplying and subtracting. And by varying one part of it, the parameter r, we’ve had the
function behave in very different ways. We’ve seen it converge to zero, converge to a finite
non­zero value, and we’ve seen it show flip­flopping oscillatory behaviour. This is amazing
because we’ve managed to crack open really interesting and varied behaviour from a really
simple function. We didn’t need advanced mathematical operators, or a large complex function
of many parts. Just three bits multiplied together, r, x, and (1­x).
For those of us who suffered through school plotting boring uneventful graphs of equations like
y=x+3 or y=(x­3)(x+4) this is a real discovery. We’ve opened a crack into a much more
interesting world, one with unexpected behaviours around each corner.
Could there be more? Let’s try the same function and try r set to 4.
Whoa! What’s going on here? Has there been an error? Did my computer go wrong? The
answer is no, nothing went wrong. Try it yourself. The behaviour we’re seeing appears
unpredictable, unruly, random. There is no discernible pattern like a gentle convergence, or
even a rapid divergence, not even something we can pick out as a regular periodic oscillation.
What we have found, is ​
chaos​
. This is one of the major discoveries of the last hundred years,
and we did it here too.
The thing to note about this is that the behaviour of the function can vary drastically with small
changes in one of the starting conditions, in this case the parameter r. Mathematicians say that
this function is highly sensitive to initial conditions. You may have heard the popular phrase,
“​
the butterfly effect​
”, which suggests that the weather system is similarly highly sensitive to
some conditions, so that the tiny air movements caused by the wings of a butterfly in one
continent can lead eventually to tornados in another continent.
There are two amazing things here. First that such rich and varied behaviour can emerge from
such innocently looking simple mathematical functions. Second that such behaviour was only
really discovered and studied in the last 100 years when the basic mathematics to do so has
been around for thousands of years, practised by great ancient civilisations.
We need to update our world map of function behaviours to now include chaos.
Milestone Check
Let’s pause for a moment and see where we are. We’ve done a lot of work, what’s left? Where
are we on this journey to the Mandelbrot Set.
We’ve built up almost all the key mathematical concepts needed to understand and make our
own Mandelbrot set. We’ve covered the idea of functions, iterating them repeatedly to get a
sequence of values, different types of behaviour including convergence, divergence, oscillation
and chaotic behaviour. These are almost all the ideas we need to understand.
The remaining one concept is about changing the things we apply these functions to. We’ve
applied these functions to numbers. That’s what most people do, and that’s what we do lots and
lots when we’re at school or at work. But there isn’t a reason why we couldn’t invent functions
which work not on ordinary numbers but on very different things, like words for example. We
could have functions which count the letters in a word, or join them together to make compound
words like “top­floor”, or even join lots together to make sentences. These functions are just
machines that do some work, after all.
What we’re doing here is challenging ourselves to let go of habits and specifics, and think more
generally, to let go of the comfortable world of numbers that we’ve been using since we were
small children, and go on a holiday to another land with different kinds of objects. The idea of
functions as machines that do something to objects and give a result is generic enough to apply
when we move away from the familiar number system.
We’re going to try to apply functions to an extension of the normal numbers called complex
numbers. It’s a terrible name because they’re not complex. They have 2 pieces to them, and it
would have been better if history had called them 2d numbers or something else that was
actually descriptive.
Complex Numbers
We’re now going to introduce the last idea we need to understand before we can make our own
Mandelbrot set.
The big idea is simple. We previously had functions which we thought of as machines. These
machines took a number as input, did something using this input, and then pushed out an
output number. We’re now going to challenge ourselves and see if we can work in a world
where the basic units of currency aren’t the numbers we’ve been used to but something else.
This might seem like a difficult thing to do, especially as we’ve been working with numbers like
2, 4, 10.4, ⅗
, ­16 and so on, since we were very young children. Our entire world seems to work
on these numbers, from fuel prices to the size of our shirts, from the length of our mortgages to
the rate of interest on our credit cards.
Can it be possible to live in a different universe, where functions take an entirely different kind of
animal as input? Well yes. Think of a function which operates on words. Words aren’t numbers.
and yet it’s not so hard to think of a useful function which counts the letters in a word, or
perhaps takes two words and joins them together. So it’s not so far fetched to contemplate
functions which work on things that aren’t the very familiar numbers we’ve been used to.
The objects we’re going to look at are actually not that different to ordinary numbers. They’re
called complex numbers. I really dislike this name as there’s nothing complex about them, and
the name puts off a lot of people. The term “complex” doesn’t even portray any aspect of these
things, so it’s not even informative. I wonder how many books entitled “Complex Analysis” have
scared potential readers away? Anyway, rant over, let’s make a gentle start.
The normal numbers we’ve been very used to have only one direction to them. They can only
describe one aspect of any physical object. They can’t describe two or more aspects at the
same time. For example, we might say that a road is 10 miles long, or a box contains 15 apples.
We can’t use those same numbers, 10 or 15, to say anything about the road’s width or the
weight of those apples. For that we need another number, and that’s fine. We are very happy to
use pairs of numbers to describe the length and width of a swimming pool, say (5,25) to mean it
has a width of 5m and a length of 25 metres. The important point here is that a single ordinary
number can’t tell you both, it can only tell you about one physical aspect. Mathematicians like to
call these object, these numbers, ​
1­dimensional​
, and for once the name is actually informative.
Complex numbers were intentionally designed to be 2
​­dimensional​
. That is a single complex
number would have within it 2 parts that told us about 2 different things. In fact I wish they were
called “2­d” numbers or something informative like that, instead of the horrible “complex”
numbers. I’m ranting again!
If we had to write a complex number, what would it look like? It would have to have 2 parts, so
why not something like the following?
(2, 3)
That should work, shouldn’t it? In fact you could indeed write complex numbers like this, and in
the early history people did. If you recall doing vectors at school mathematics, this isn’t very
different at all. Vectors could be composed of more than one component, and complex numbers
can be thought of as vectors with 2 parts.
As it happens, history took a different course and the following become the convention for
writing complex numbers. If you ever think how strange it looks, just keep in mind that it’s only
an accident of history that complex numbers came to be written like this. Myself I still think the
term “complex” was a bad accident of history.
The illustration shows a complex number with the 2 parts we expected. The first part that looks
like a familiar number 2.The second part also has a familiar number 3 but it’s got an “i” added to
it. Well someone had to think of something to distinguish the two otherwise we wouldn’t know
which of the two numbers referred to the first or second part, the width or the length if we using
the earlier swimming pool analogy. We could have written it as (2a, 3b) or (2x + 3y) which would
have been perfectly fine. Again it’s just how history turned out that the first part was not given a
special appendage, but the second part was given an “i”.
Even weirder are the names that seemed to have become attached to these parts. The first part
is now commonly called the “real” part, and the second part is called the “imaginary” part. Trying
to find meaning in these names isn’t fruitful, so let’s not worry too much about the names
themselves.
Naming conventions aside, it’s worth remembering where we started this discussion. We
wanted an object which told us about two different independent things. That’s the most
important thing to remember about complex numbers. The two parts are different, and always
remain different, like apples and oranges. You can’t compare apples and oranges, which is why
we need to write them both down separately. We can compare apples with apples, and collect
them together if we find sets of them. Mathematicians call these apples and oranges
independent​
. The real and imaginary parts of a complex number, like apples and oranges, are
independent. You can’t combine them, and you need to keep track of them separately. Two
oranges + 3 apples doesn’t equal 5 apples. This insight will help us when we try to do arithmetic
with complex numbers.
So what can we do with these new kind of object, these complex numbers? Can we add them?
Can we multiply them? Can we subtract and divide them? The answer to this question is yes but
the reason why is interesting as we’ll see. Mathematicians are quite fond of inventing new kinds
of objects, and when they do, the challenge for them is not finding out whether they can add and
subtract these objects but whether the rules they’ve defined for them allow them to do arithmetic
in a way that’s consistent.
Let’s dig a little bit deeper into this. We may not be conscious of it, but we are already very
familiar with the rules governing the familiar normal numbers. These rules tell us that 2+3 is the
same as 3+2. These rules tell us that multiplying two negative numbers gives a positive number,
for example, ­3 * ­4 = +12. These rules also tell us that dividing by zero is undefined, 12/0= ?.
These are the ​
rules of arithmetic​
, and we need to stick to them, otherwise law and order would
break down and anything would go, which might be fun for a short while but would eventually
confuse everyone and not be productive. Rules allow us to do mathematics which is
unambiguous and have other people agree with us on the answers. These rules need to be
consistent, we can’t have rules that contradict each other.
As an example of consistent rules framework, consider criminal law or financial regulations for
companies. We know these rules need to unambiguous and consistent. If they weren’t then it
would be impossible for citizens to agree what was legal or not, and even worse for the police
and regulators to decide if a crime had been committed or not.
So for complex numbers, the key question was not whether we can add or multiply them, but
instead, what were the consistent and logical rules governing arithmetic, rules that didn’t
produce contradictory results.
As it turns out, most mathematicians have reached a consensus around the rules of arithmetic
for complex numbers. These rules are simple, and very familiar if you did a little bit of algebra or
vectors at school.
The following table shows the rules for adding, subtracting and multiplying. We’ve left out
dividing because it is a little bit more involved, and actually we don’t need it for the purposes of
making our own Mandelbrot set.
Operation
How to do it
Addition
Add the two complex numbers
(a + bi) and (c + di)
(a + bi) ​
+​
(c + di) = (a+c) + (b+d)i
That is, add the real and imaginary parts
independently.
Subtraction
Subtract the complex number (c + di) from
(a + bi)
(a + bi) ​
­​
(c + di) = (a­c) + (b­d)i
That is, subtract the real and imaginary parts
independently.
Multiplication
Multiply the two complex numbers
(a + bi) * (c + di)
2​
(a + bi) ​
*​
(c + di) = (ac + adi + bci + dbi​
)
= (ac­bd) + (ad+bc)i
That is, expand out the terms and apply the
2​
special rule that i​
is ­1. Then collect real and
imaginary parts to make a neat answer.
If these rules leave you a little lost, let’s go back to something really familiar. All these rules are
doing is helping us to addition, subtraction and multiplication for complex numbers in a way that
makes sense for us if we’re only used to doing arithmetic with ordinary numbers. They do this by
breaking down the task into smaller easier tasks, each one only using ordinary number
arithmetic. Why all the brackets? The brackets are needed to keep the real and imaginary parts
collected together neatly, so we don’t lose them. But what do we mean when we expand
brackets? It’s easiest to think about a simple example 3 * (4 + 5). We could do the addition first
to make 3 * 9 = 27. Another way is to expand this as (3*4) + (3*5) which is each element of the
bracketed sum multiplied by 3 individually. This is 12 + 15 = 27 as we expected. Why would we
do this the long way around? It’s useful when the things inside the brackets can’t be combined,
like apples and oranges, or the real and imaginary parts of a complex number. It is common for
mathematicians to break everything out into such small pieces then collect all the similar kinds
of things with the hope of ending up with a much tidier result.
So the rules for complex numbers are exactly the same as for normal algebra but with only one
extra special rule applied which is that whenever we come across an imaginary part multiplied
2​
by an imaginary part, the resultant i​
is replaced by ­1.
All this may look really difficult but it’s really nothing more than the school algebra we learned
when adding and multiplying expressions like x, y, (x+y), (3x + 4y), and so on. Exactly the same
2​
except with the special rule about i​
being the same as ­1.
It’s worth that there are no more special rules for working with complex numbers around the
2​
corner. Normal algebra with this special rule that i​
= ­1 is all there is to working with complex
numbers.
Let’s help this settle in our minds by gently working through some examples of addition,
subtraction and multiplication.
Example 1: Add two complex numbers. (2 + 3i) +
​​
(4 + 5i)
Let’s apply our school algebra here, and expand this out first:
(2 + 3i) + (4 + 5i) = 2 + 3i + 4 + 5i
Again like school algebra, let’s collect terms. This means bringing together objects or animals of
the same type. In this case this means collecting real parts and imaginary parts.
(2 + 3i) + (4 + 5i) = 2 + 3i + 4 + 5i = (2+4) + (3+5)i = ​
(6 + 8i)
That was as easy as doing something familiar from school like (2x + 3y) + (4x + 5y) = (6x + 8y).
Example 2: Subtraction (8 + 6i) ​
­​
(5 + 4i)
Again let’s apply the same school algebra and expand out the terms with the aim of collecting
similar animals together later.
(8 + 6i) ­ (5 + 4i) = 8 + 6i ­5 ­4i
Collecting terms gives us the very familiar
(8 + 6i) ­ (5 + 4i) = 8 + 6i ­5 ­4i = (8­5) + (6­4)i = ​
(3 + 2i)
Again that was as easy as doing school algebra like (8x + 6y) ­ (5x +4y) = (3x + 2y)
Example 3: Multiplication (8 + 6i) ​
*​
(5 + 4i)
Lets do what we always do and expand out the terms.
2
(8 + 6i) * (5 + 4i) = 40 + 32i + 30i + 24i​
Before we collect terms in the way that we are familiar with, we must apply that single special
2​
rule for complex numbers. Whenever we see an i​
we must replace ti with a ­1.
2​
(8 + 6i) * (5 + 4i) = 40 + 32i + 30i + ​
24i​
= 40 + 32i + 30i ­​
​
24
Now we can collect the terms as usual
2​
(8 + 6i) * (5 + 4i) = 40 + 32i + 30i + 24i​
= 40 + 32i + 30i ­24 = ​
(16 + 62i)
Visualising Complex Numbers
Before we move onto functions which operate on complex numbers, let’s see how we might
visualise complex numbers. We found earlier that visualising the sequences of values from
iterative functions helps us to quickly get a feel for the behaviour of a function in general terms.
The plain old numbers we’ve used since we were children are 1 dimensional as we’ve already
discussed. That means that can be used to describe one physical aspect of an object, such as
length or width, but not both at the same time. Visualising these familiar numbers is easy, we
can plot a bar of the right length on a chart, or a dot at the right height on an x­y graph. We did
this when we plotted the value of functions as they evolved over many iterations. We plotted the
1­dimensional output values at the right height vertically above the horizontal axis representing
iterations.
Because complex numbers are two dimensional, that is they have 2 independent parts, any
visualisation must be able to show both of these parts. Why not simply use the familiar x­y plot
and have the x­axis represent the real part, and the y­axis represent the imaginary part? You
can indeed do this, and this is in fact the most common way to show complex numbers. In fact
because complex numbers have 2 independent parts, they can be used as coordinates on a
2­dimensional plane such as your graph paper or your computer screen.
Let’s plot a couple of examples. The following plots show (3 + 2i) and (­2 ­ 2i). There’s nothing
unfamiliar here, these are just like plotting coordinates (x,y), and this time we’re using the real
part as the horizontal x value and the imaginary part as the vertical y value.
It’s useful to ask how far these points are from the origin. If you remember doing vectors then
this is the same as asking what the magnitude of the vector was, irrespective of direction. The
same idea applies to complex numbers. The magnitude of a vector or a complex number tells
you one thing, its a 1­dimensional quantity, and that is its size. A 1­dimensional quantity can’t
tell you which direction a vector was pointing, nor can it tell you whether the real or imaginary
part of the complex number contributed most to its magnitude. Nevertheless, it’s a useful
quantity to know. Like the wind, it’s sometimes useful just to know that its going to be a strong
80mph and we don’t care which direction it’s blowing because our trees will suffer regardless.
The following diagram illustrates this familiar idea of a magnitude. Just like vectors, we work out
the magnitude by squaring each horizontal and vertical component, summing these squares,
and taking the square root. This is just the Pythagoras theorem many of us learned at school for
working out the long side of a right angled triangle from its shorter sides. So for the example
2​
2​
shown, the magnitude is the square root of ( 3​
+ 4​
) = sqrt(25) = 5.
Visualising how a function changes a complex value over many iterations might be a challenge
because paper and computer screens only allow us to see 2 dimensions easily. There are two
ways to do this as shown below.
The first way we can visualise the evolution of complex numbers is simply to plot them on the
grid as we did above and see if we can follow successive iterations. If the values move around
gently we can see the progression, but if they are densely packed or jump around wildly then we
can’t easily follow their evolution. Adding connecting lines helps. Some people call this kind of
plot an ​
orbit plot​
.
The second way discards the real and imaginary components and instead combines them into
the magnitude, as we did earlier, and these are plotted against the iteration number, just as we
have been doing for many of the plots earlier.
In reality, when exploring complex numbers, looking at both kinds of plot together is useful to
get the most insight visually.
Complex Functions
Now that we can do the basic operations with complex numbers and visualise the results, we
can now try to define iterative functions in the way we did for normal numbers.
We’re doing this to see if we can find interesting behaviour, just as we did before.
We’ll work with a function that’s slightly simpler than the Logistic Map we explored earlier, but
retains a parameter we can tune. We’ll call it the Test Map for now.
This looks just like the functions we worked with before. There is a machine which takes an
input, does something to it, and throws out an output. In this case however, the function works
on complex numbers, not just normal numbers.
Let’s unpack how this function might work on complex numbers. First we need to see what that
2​
z​
+ c looks like in terms of real and imaginary parts.
Any complex number z consists of two parts (a + bi), where a is the real part and bi is the
imaginary part. The function squares this complex number, which means the multiplying it by
2​
2​ 2​
itself. So z​
is (a + bi)(a+bi) = (a​
­ b​
) + (2ab)i. We worked through how to multiply complex
numbers earlier. We then add the complex number c, which we keep constant just like we did
2​
2​ 2​
with the r parameter for the Logistic Map. If c = (e + fi) then z​
+ c becomes (a​
­ b​+ e) + (2ab +
f)i.
The diagram also states that the starting value of z is always zero. What does this mean if it is a
complex number. It just means that both real and imaginary parts are zero, (0 +0i). It’s become
conventional to shorten this to simply zero, and everyone understands you mean that both parts
are zero.
If we have no choice about the starting value of z, only the complex number c remains to vary
and explore. We can investigate how this Test Map behaves with different c. As a reminder,
varying c means trying different values for both it’s real and imaginary parts. Because c is
2­dimensional, we’re exploring not points on a 1­dimensional line, but a 2­dimensional plane.
You can probably guess already that the Mandelbrot set lives in such a 2­dimensional plane of
complex numbers.
Now let’s try this function with an example, just to be really sure we understand the calculations.
2​
2​ 2​
Let’s set z = (1 + 2i) and c = (10 + 11i). Using the above results, z​
becomes (1​
­ 2​
) + (2*1*2)i
2​
= (­3 +4i), and so z​+ c becomes (7 + 15i).
Because we’re new to iteratively applying a function to complex numbers, let’s try iterating the
Test Map a few examples of c.
Example 1: c = (2 + 2i)
The following table sets out the iterations of the Test Map with c set to (2+2i)
Function: z2​
​+ c,
for complex z, c,
seed z=(0+0i),
c set to (2 + 2i)
iteration
real
imaginary
magnitude
0 (initial value)
0
0
0
1
2
3
4
5
2
2
­94
7074
­12273758
2
10
42
­7894
­111684310
2.82842712474619
10.1980390271856
102.95630140987
10599.8449045257
112356709.793491
The table shows the initial value for z as (0+0i) as required by the definition of the Test Map
function ­ we don’t have a choice about the initial value of z. The magnitude of this z is also
zero, worked out using Pythagoras’ formula as we did earlier.
To work out the next iteration of z we use this current value and also the value of c, set at (2+2i)
for this example. This leads to an output value of (2+2i). Iterating again leads to (2+10i), and so
on, as shown in the table. We can see the by looking at the table that the real and imaginary
parts of the Test Map output grow in size quickly. The column of magnitudes for each output
shows that the complex numbers are getting further and further away from the origin point (0+0i)
at an ever quickening pace. This looks just like the early example of divergence we saw using
ordinary numbers.
Indeed this Test Map function does diverge for c=(2+2i). Let’s plot an orbit plot and a magnitude
plot to confirm it.
The orbit plot does show the outputs getting larger and larger because they are moving further
and further away from the origin at (0+0i). What is surprising is that the curve coils around,
almost like a backwards running clock. This is not unusual for functions working on complex
numbers, but does surprise us if we’ve been used to looking at relatively uneventful graphs of
boring functions working on ordinary numbers.
The second plot showing the magnitude of the Test Map function outputs does indeed confirm
the magnitude diverges. In fact it blows up so rapidly that we were only able to plot the fifth
iteration before the magnitude reached almost 120,000,000. The plot might be misleading as it
seem to show zero magnitude for iterations before the fifth one. The magnitudes are in fact
non­zero but since they are much smaller than the output 112,356,709 they appear like zero in
comparison when plotted.
Example 2: c = (0.4 + 0.4i)
The following table sets out the iterations of the Test Map with c set to (0.4+0.4i)
Function: z2​
​+ c,
for complex z, c,
seed z=(0+0i),
c set to (0.4 + 0.4i)
iteration
real
imaginary
magnitude
0 (initial value)
0
0
0
1
2
3
4
5
6
7
8
9
10
11
12
0.4
0.4
0.0416
­0.55084544
0.471874179078554
0.605729556078194
0.690077739364831
0.33481127142546
­1.49157882159919
0.808082152860167
­12.0577561298287
116.065912138556
0.4
0.72
0.976
0.4812032
­0.130137176866816
0.277183252996741
0.735796177580052
1.41551312571545
1.34785949868043
­3.62087736544608
­5.45193275342464
131.876151154039
0.565685424949238
0.82365041127896
0.976886155086661
0.731428204582674
0.489490475580398
0.666137261267182
1.00876325334951
1.45457072586597
2.01035633886789
3.70995278425089
13.2330288911604
175.677588791963
The magnitudes seem to be growing but not at the rapid rate as before. Let’s see what the orbit
and magnitude plots show us.
The orbit plot is interesting. It shows the Test Map outputs apparently orbiting not far from the
origin before finally relenting to some external force and diverging. The magnitude plot shows
this too, with the magnitude actually falling after the third iteration, before relenting and diverging
after about the eighth iteration.
Could there be values of c which result in orbits which don’t eventually diverge? Which are
somehow trapped in an orbit, and don’t escape?
Example 3: c = (­0.3 + 0.4i)
Lets not waste space with the table and jump straight to the resultant plots for the Test Map with
c set to (­0.3 + 0.4i).
The orbit plot now shows the values first circulating a point and then falling into it, almost like an
asteroid getting trapped by the gravity of a planet, circulating it before falling into it. The Test
Map for c=(­0.3+0.4i) converges to a non­zero value. If we did present the table of values, we’d
see it approached a value of z=(­0.284 + 0.255i) approximately.
We can see the value of plotting magnitude plots now. The plot shows the magnitude oscillating
before settling down and approaching 0.3819 approximately. This is characteristic of orbits
which get trapped and fall towards an ​
attractor​
point. The word attractor is actually used by
mathematicians to describe points which seem to pull in successive values of an interactive
function.
These kinds of orbit plots remind me of a spider circling a bath drain before falling into it!
When we were exploring different kinds of behaviours for functions working with ordinary
numbers, we found divergence, convergence, oscillation and chaotic behaviour. I wonder if we
can find oscillatory behaviour for complex functions?
Example 4: c = (0.3 + 0.5i)
Again jump straight to the resultant plots for the Test Map with c set to (0.3 + 0.4i).
The orbit plot shows the values visiting approximately the same four points repeatedly in turn.
So this is not a divergence, nor is it a convergence to a single point. Instead of falling into a
point attractor, the values from this Test Map seem happy to cycle around these four points in
turn. This is a more interesting kind of attractor.
The magnitude plot reflects the periodic nature of the orbit.
What is not clear is whether with this, or similar orbits, is whether these orbits will remain cycling
or whether they are converging to a single points very slowly. The above plots were from 50
iterations, so it would take many many more to see any convergence if it was there at all.
The c=(0.33 + 0.577i) Neighbourhood
Now that we know how to apply iterative functions to complex numbers, and follow sequence of
outputs visually using orbit and magnitude plots, let’s now look more closely at the behaviour of
the same Test Map function for values of c close to (0.33 + 0.577i).
What do we mean by “close to” when talking about complex numbers? Well, the ordinary
numbers 1.4 and 1.6 are close to 1.5 compared to the number 99.8 which is much farther from
1.5. In the same way, we can think of complex numbers which are close together. We do this by
picking real and imaginary parts which differ very slightly from each other. If you think back to
complex numbers as grid references on a 2­dimensional flat map, then such complex numbers
are close together on this 2­dimensional plane.
Let’s consider the Test Map function for the three values of c as (​
0.32​
+ 0.577i), (​
0.33​
+ 0.577i),
and (​
0.34​
+ 0.577i). The imaginary parts of these three choices don’t change but the real part
changes by 0.01. These are close together compared to a large complex number like (100 +
100i).
The following diagram shows these three choices for c represented on the 2­dimensional
complex plane. You can see that because the imaginary part doesn’t change, the three points
are at the same height. The only difference is small changes in the horizontal real value.
Mathematicians call this ​
exploring the neighbourhood​
of c=(0.33 + 0.574i), and their choice of
words actually conveys the right sense.
Let’s look at just the orbit and magnitude plots for these three orbits.
For c = (0.32 + 0.577i):
We can see that the Test Map output values do escape and diverge, despite circulating around
a finite point initially. The magnitude plot confirms the eventual escape and divergence.
For c = (0.33 + 0.577i):
www.allitebooks.com
We can see that for this value of c, the Test Map values don’t escape and diverge. They seem
to have a roughly stable periodic orbit, and the magnitude plot confirms this.
How do we know that the values don’t escape at a later iteration, perhaps after thousand
iterations, or maybe a billion iterations? We can’t easily say for sure. If the orbit obviously
collapses or explodes then we have some certainty about the ultimate fate, If the orbit is only a
roughly regular then we can’t be as certain. This uncertainty may seem uncomfortable but we
can live with it for the purposes of making our own Mandelbrot set.
In fact, if you came up with quick mathematical shortcut to knowing the ultimate fate of output
values from iterative functions working on complex numbers, you’d have done something
mathematicians still haven’t found. As it is, we too often have to do the hard work of calculating
many iterations and seeing how confident we are in further projecting the results we have.
For c = (0.34 + 0.577i):
For this choice of c, only 0.01 real part units far from the previous one, the orbits again escape
after initially going round a fixed point. The magnitude plot has the now familiar signature of a
divergent sequence.
What does this all tell us? It tells us that close to c = (0.33 = 0.577i) the behaviour of the Test
Map iterative function different drastically. Remember when we found functions that were
sensitive to even small changes in starting conditions? This Test Map is highly sensitive to
choices of c.
The three points we just tested are all on a the same horizontal line because we didn’t change
the imaginary values. So let try exploring two further points just above and below the middle
(0.33 + 0.577i) point. That will give us a fuller view of how the Test Map function behaves in the
neighbourhood of this middle point. We’ll choose points 0.01 imaginary part units above and
below the middle point.
For c = (0.33 + 0.587i):
For this choice of c just 0.01 imaginary part units greater than the middle point, the orbit
circulates then diverges. The magnitude plot confirms this. The divergence is actually much
sooner than at the other choices of c. How quickly or slowly an orbit takes to escape and
diverge is worth keeping in mind as something to distinguish otherwise similar behaviours.
For c = (0.33 + 0.567i):
For this c just below the middle point, we see a very regular periodic oscillation, and the
magnitude plot confirms this.
Divergence Atlas
We did a lot of work in that last section exploring the neighbourhood of the point (0.33 + 0.577i)
to see how the Test Map behaved when applied iteratively. Let’s summarise the results by
drawing a different kind of diagram. For each point we tested, we’ll colour it black if it appeared
to converge or at least not escape, and we’ll colour it blue if the sequence of Test Map values
exploded and diverged. We’ll call this diagram an atlas, as it tells us about the land of complex
numbers by colouring regions according to how they behave. We saw at this idea of colouring
regions marking common behaviour when we looked at the simple function “square the input”
for ordinary numbers.
Ok so it’s not a very complete atlas, but its a start. The five coloured points look simple, but you
now know the work that went into them. For each point we iterated the Test Map function to see
if the values escaped and diverged, or whether they converged or at least remained in an orbit
without escaping for all the while we kept iterating.
You may be wondering why I’ve chosen my words carefully in that last sentence. It’s because
we don’t know for certain whether a fairly regular and stable orbit won’t break out and diverge at
a much later iteration. We know this because we saw examples ourselves of apparently stable
orbits suddenly exploding. This is why it is more correct to say that a point we test either does
diverge, or that for all the iterations we’ve done it hasn’t yet.
We chose the word atlas to avoid confusion by using the word map which we’ve used a few
times already more properly when referring to functions which take an input, do something and
pop out an output.
What does this small atlas show us? It doesn’t show us much, but it teases us to the possibility
that the regions of convergent and divergent points might be interesting shapes, not just a
boring circle or square.
We’re ready!
All the hard work learning new concepts and crunching through calculations is mostly behind us.
We have enough now to make our own Mandelbrot. At last!
Now We Can Make Our Own Mandelbrot
Completing the Atlas
The Mandelbrot set is simply the points on the atlas we started making that don’t diverge.
That’s it! It’s as simple as that. If we complete the atlas we started and mark the regions that
diverge and those that we don’t think diverge we have plotted the Mandelbrot set.
It’s worth restating that last point, because too often the simple explanation of what the
mandelbrot set is isn’t presented well.
The mandelbrot set is the set of complex numbers, for which the iterated function
described above doesn’t diverge.
Because complex numbers can be thought of as coordinates on a flat 2­dimensional surface,
this particular set of numbers can be marked out by visually, by colouring them a colour distinct
from those numbers for which the iterated function does diverge. This coloured atlas of
diverging and non­diverging regions is the most common representation of the map.
The previous section chose some points in the neighbourhood of (0.33 + 0.577i) and coloured
them according to whether they diverged or not. Let’s make a fuller atlas, showing many points
in a preselected box. What location and size of box shall we choose? Let’s try a box of width 10
units wide along the real axis, and 10 units high along the imaginary axis, and lets test and
colour many points 0.1 units apart. This means the bottom left corner of this atlas is at (­5 ­5i)
and the top right is at (5 + 5i).
Naively we might expect a box or a circle or other boring shape to show which points diverge
and which ones don’t. We know we’re in for a surprise because the points we tested in the
neighbourhood of (0.33 + 0.577i) were coloured in a way that suggested an atlas that wasn’t
going to be boring.
The following is the result.
This is interesting! The black region in the middle isn’t a boring circle or square, or any other
regular shape. Remember this is the region which has the complex numbers which don’t
diverge when iterated with the Test Map function. The surrounding white space are the points
which do escape and diverge.
It does make some sense that large initial values of the parameter c result in divergence, a bit
like large initial values diverging under the “square it” function we saw earlier. Similarly, it makes
sense that small values don’t diverge, again like small values under the “square it” function.
The unusual behaviour here is that we would have expected a circular border between these
two regions. What we see instead is a strange shape! It kind of looks like a beetle with arms and
legs.
2​
This is amazing. An extremely simple function “z​
+ c” when iterated over complex numbers
seems to result in a strange insect like shape. Have we unlocked something about the way
simple laws of nature lead to organic natural looking forms?
When researchers saw their first glimpse of this Mandelbrot set, they were convinced they’d
made an error. When they realised that there was no error, the magnitude of what they had
discovered dawned on them. They had discovered an extremely rich shape generated by
extremely simple mathematics.
Let’s improve the picture we have. Let’s reframe it by choosing a rectangle closer to the shape,
from (­2.25 ­ 1.5i) to (0.75 + 1.5i). Let’s also improve the detail by testing points 0.005 apart
along the real and imaginary axes, a hundred fold increase in the number of points tested. The
resulting image is this.
Mandelbrot Set: bottom left (­2.25 ­ 1.5i), top right (0.75 + 1.5i)
Behold! This is our first proper view of the Mandelbrot set.
You can see the amazing level of detail. You can see self similarity in the patterns, but in a way
that isn’t exact repetition like a synthetic boring object, but more like an object created by
nature. You can in fact see what looks like smaller versions of the big shape itself, attached by
what look like very fine threads. The object is haunting in its intricacy and natural beauty. And all
this from a very simple mathematical function.
Let’s zoom in and look at some sections of this shape. We can do this again by choosing a new
rectangle and testing many points within it.
Example 1: bottom left (­1.5 ­0.5i), top right (­0.5 +0.5i)
This closer view of the first “head” shows detail that doesn’t appear to diminish. Also visible are
yet more smaller shapes that look similar to the whole set, but placed on what look like very thin
threads.
Example 2: bottom left (0.0 ­0.9i), top right (0.6 ­0.3i)
This view of one of the bulbs shows more of the filaments with more bulbous shapes attached to
them. Again the detail doesn’t diminish.
Example 3: bottom left (­0.22 ­ 0.70i), top right (­0.21 ­0.69i)
This is close up just under the top bulb, and shows intricate swirling shapes, in contrast to just
endless bulbs with smaller bulbs. The detail appears to be diminishing but this is because we
set an artificial limit to the number of iterations to apply to a test point. If we increased this
number, the detail would return. We make a trade of between the detail visible and the effort to
do all those calculations.
Colour
If you recall the introduction to this book, it showed the Mandelbrot set with coloured gradients
around it. Those colours are not arbitrary but actually mean something. They indicate not just
that the points diverge, but how quickly they do this. A rough and ready way to do this is to keep
a note of the number of iterations it took for the magnitude of the function output values to grow
beyond our chosen threshold of 4.0. This way, a quick divergence with fewer iterations would be
given a different colour than a slow divergence with many iterations.
You could choose a larger threshold than 4 but once the magnitude of the complex number has
increased beyond 4, we know the function diverges. Actually you can work this out by looking at
the mainview of the Mandelbrot set. The circle described by complex numbers with magnitude 4
is outside the main shape, meaning the points with that and larger magnitudes always diverge.
Let’s replot the same views above, but colouring the points according to how quickly they
escape, if at all.
Coloured Mandelbrot Set: bottom left (­2.25 ­ 1.5i), top right (0.75 + 1.5i)
This colour coding to show how quickly, or slowly, points diverge makes clearer the very fine
filament structures that weren’t clear before. You can see now that all the bulbs of the
Mandelbrot set are connected. That is, there is only one shape with fine detail around the
edges, and there are no parts that break away from the main shape.
Coloured Example 1: bottom left (­1.5 ­0.5i), top right (­0.5 +0.5i)
This close up again highlights the intricate structures, both bulbous and filament like. The
filaments look like electrical lightning.
Coloured Example 2: bottom left (0.0 ­ 0.9i), top right (0.6 ­ 0.3i)
Coloured Example 3: bottom left (­0.22 ­ 0.70i), top right (­0.21 ­0.69i)
This much closer view really emphasises the intricate detail, much more so than the
non­coloured view.
The Recipe
The recipe for making your own mandelbrot is summarised here. Mathematicians use the word
algorithm​
for recipe. Let’s show it as a diagram then explain it in plain English.
1. Pick a rectangle on the complex plane.
If you select the rectangle with bottom left corner at (­2.25 ­1.5i) and top right corner at (0.75 +
1.5i) you’ll get a good view of the Mandelbrot set. If you chose a rectangle that’s far away from
the centre of the Mandelbrot, or is completely within the set, then you won’t see anything
interesting.
2. Each point in this rectangle is a complex number representing c, the parameter of our
iterated function.
You should pick a reasonable number of points, evenly spaced out. Too many and you’ll have
lots of unnecessary calculations to do without adding much detail. Too few points and you’ll
have gaps in your plot and insufficient detail.
2​
3. For each of these points, c, iterate the function z​
+c many times.
The complex number z starts at zero, or more properly (0 + 0i). Keep a note of the completed
iterations.
4. Stop iterating when either (i) you’ve reached the maximum number of iterations you’ve
set yourself, or (ii) the magnitude of the function output grows more than 4.
If the magnitude grows greater than 4, the point has diverged and you don’t need to keep
calculating further iterations as they’ll diverge further, and may even grow so big as to cause an
error in your calculator or computer.
If you’ve reached the maximum iterations, the point c very likely doesn’t diverge.
5. Colour the point c using the number of iterations reached to indicate either (i) the rate
of divergence, or (ii) non­divergence for points within the set.
Remember that we have an upper limit on the number of iterations. If it is reached then the
iterations did not diverge and so the point is likely to be within the Mandelbrot set. If the
iterations are prematurely stopped because the magnitude of z has broken the limit we set of 4,
then the orbit does diverge, and the iteration count we did reach indicates how slow or fast the
divergence happens. A small iteration count means a rapid divergence.
You could chose other colouring schemes if you wanted to experiment.
Part 2: DIY
In this section we’ll be making the Mandelbrot set ourselves using computers to do the
calculating and plotting.
We’ll introduce the Python programming language, and use the IPython environment to
experiment and eventually develop our instructions to calculate and plot the Mandelbrot Set.
Working with Python
In this section we’ll go through the steps to practically make our own Mandelbrot set.
We’ll use a computer because, as you know from the before, there will be many thousands of
calculations to do, and many points to plot. Computers are good at doing many calculations very
quickly, and they make plotting charts and pictures easy too.
We will tell a computer what to do using instructions that it can understand. Computers find
normal human languages hard to understand precisely and without ambiguity. In fact humans
have trouble with precision and ambiguity when communicating with each other using these
human languages, so computers have very little hope of doing better!
Python
We’ll be using a computer language called P
​ython​
. Python is a good language to start with
because it is relatively easy to learn, it is easy to read and understand. It is also very popular,
and used across many different fields, from genetics research to global scale technology
infrastructures. Python is increasingly being taught in schools, including for use with the
Raspberry Pi, also popular in teaching. In fact you could do all exercises in this book using a
Raspberry Pi.
There is a lot that you can learn about Python, or any other computer language, but here we’ll
remain focussed on making our own Mandelbrot set, and only learn just enough about Python to
achieve this.
Interactive Python, IPython
Rather than go through the error­prone steps of setting up Python for your computer, then all
the various extensions to help do mathematics and graph plotting, I’d recommend you use a
prepackaged solution, called I​
Python​
. IPython contains the Python programming language and
several common numerical and graph plotting extensions, including the ones we’ll need.
IPython also has the advantage of presenting interactive notebooks, which behave much like
pen and paper notepads, ideal for trying out ideas and seeing the results, and then changing
some of your ideas again. This keeps the unnecessary stuff like program files, interpreters and
libraries away from us. We’re more interested in working with an electronic mathematical
notebook that can plot graphs, and not with that other geeky stuff.
In fact, I’d recommend you don’t even install anything on your computer if you can avoid it, and
use online versions of IPython accessed purely through a web browser. This has many
advantages. It means you don’t have to change or install anything on your computer. It also
means you can use any computer at any time because your work is kept online. Trendy people
call this “working in the cloud”. In fact you can use anything that has a suitable web browser, be
it a tablet, smartphone, or even a Raspberry Pi. This is just like using webmail, online shopping
and social networking sites, which you can use from any device with a suitable browser, and
without installing software.
I’m using the online IPython from ​
http://wakari.io​
who currently offer limited, but good enough for
us, free trial accounts. If you must install your own IPython, if you don’t have easy internet
connectivity for example, the ​
http://ipython.org​
site gives you some options for where you can
get prepackaged IPython. I’m using the one from Anaconda package from Continuum.io when I
don’t have access to the web and wakari.io.
A Very Gentle Start with Python
We’ll assume you now have access to IPython, following the instructions at wakari.io or at
ipython.org.
Notebooks
Once we’ve fired it up and clicked “New Notebook”, we’re presented with an empty notebook as
follows.
The notebook is interactive, meaning it waits for you to ask it to do something, does it, and then
presents back your answer, and waits again for your next instruction or question. It’s like a robot
butler with a gift for arithmetic that never gets tired.
If you have want to do something that is even mildly complicated, it makes sense to break it
down into sections. This makes it easy to organise your thinking, and also find which part of the
big project went wrong. For IPython, these sections are called cells. The above IPython
notebook has an initial empty cell, and you may be able to see the typing caret blinking, waiting
for you to type your instructions into it.
Lets instruct the computer! Let’s ask it to multiply two numbers, say 2 times 3. Let’s type “​
2*
3​
” into the cell and click the run cell button that looks like a audio play button. The computer
should quickly work out what you mean by this, and present the result back to you as follows.
You can see the answer “​
6​
” is correctly presented. We’ve just issued our first instruction to a
computer and successfully received a correct result. Our first computer program!
Don’t be distracted by IPython labelling your question as “In [1]” and its answer as “Out [1]”.
That’s just it’s way of reminding you what you asked (input) and and what it replied with (output).
The numbers are the sequence you asked and it responded, useful for keeping track if you find
yourself jumping around your notebook adjusting and reissuing your instructions.
Basic Python
We really meant it when we said Python was an easy computer language. In the next ready cell,
labelled “In [ ]”, type the following code and click play. The word c
​ode i​
s widely used to refer to
instructions written in a computer language. If you find that moving the pointer to click the play
button is too cumbersome, like I do, you can use the keyboard shortcut ctrl­enter, instead.
print “Hello World!”
You should get a response which simply prints the phrase “Hello World!” as follows.
You can see that issuing the second instruction to print “Hello World!” didn’t remove the
previous cell with its instruction and output answer. This is useful when slowly building up a
solution of several parts.
Now lets see what’s going on with the following code which introduces a key idea. Enter and run
it in a new cell. If there is no new empty cell, click the button with the downward pointing arrow
labelled “Insert Cell Below”, not to be confused with the one labelled “Move Cell Down”.
x = 10
print x
print x+5
y = x+7
print y
print z
The first line ​
“x = 10​
” looks like a mathematical statement which says x is 10. In Python this
means that x is set to 10, that is, the value 10 is placed in a virtual box called x. That 10 stays
there until further notice. We shouldn’t be surprised by the “​
print x​
” because we used the
print instruction before. It should print the value of x, that is “10”. Why doesn’t it just print “x”?
Because the tendency of Python is to evaluate whatever it can, and x can be evaluated to the
value 10 so it prints that. The next line “print x+5” evaluates x+5, which is 10+5 or 15, so we
expect it to print “15”.
The next bit “y = x+7” again shouldn’t be difficult you work out if we follow this idea that Python
evaluates whatever it can. We’ve told it to assign a value to a new box labelled y, but what
value? The expression is x+7, which is 10+7, or 17. So y holds the value 17, and the next line
should print it.
What happens with the line “print z” when we haven’t assigned a value to it like we have with x
and y? We get an error message which is polite and tells us about the error of our ways, trying
to be helpful as possible so we can fix it. I have to say, most computer languages have error
messages which try to be helpful but don’t always succeed.
The following shows the results of the above code, including the helpful polite error message,
“name z is not defined”.
These boxes with labels like x and y, which hold values like 10 and 17, are called v
​ariables​
.
Variables in computer languages are used to make a set of instructions generic, just like
mathematicians use expressions like “x” and “y” to make general statements.
Automating Work
Computers are great for doing similar tasks many times ­ they don’t mind and they’re very quick
compared to humans with calculators!
Let’s see if we can get a computer to print the first ten squared numbers, starting with 0
squared, 1 squared, then 2 squared and so on. We expect to see the a print out something like
0, 1, 4, 9, 16, 25, and so on.
We could just do the calculation ourselves, and have a set of instructions like “print 0”, “print 1”,
“print 4”, and so on. This would work but we would have failed to get the computer to do the
calculation for us. More than that, we would have missed the opportunity to have a generic set
of instruction to print the squares of numbers up to any specified value. To do this we need to
pick up a few more new ideas, so we’ll take it gently.
Issue the following code into the next ready cell and run it.
range(10)
You should get a list of ten numbers, from 0 up to 9. This is great because we got the computer
to do the work to create the list, we didn’t have to do it ourselves. We are the master and the
computer is our slave!
You may have been surprised that the list was from 0 to 9, and not from 1 to 10. This is because
many computer related things start with 0 and not 1. It’s tripped me up many times when I
assumed a computer list started with 1 and not 0. Creating ordered lists are useful to keep count
when performing calculations, or indeed applying iterative functions, many times.
You may have noticed we missed out the “print” keyword, which we used when we printed the
phrase “Hello World!”, but again didn’t when we evaluated 2*3. Using the “print” keyword can be
optional when we’re working with Python in an interactive way because it knows we want to see
the result of the instructions we issued. When Python is used in other contexts, where there is
no human having a two way dialogue with Python, then instructions are carried out but the
results are not necessarily printed out, and in that case the “print” keyword asks Python to print
out the result.
A very common way to get computers to do things repeatedly is by using code constructs called
loops​
. The word loop does give you the right impression of something going round and round
potentially endlessly. Rather than define a loop, it’s easiest to see a simple one. Enter and run
following code in a new cell.
for n in range(10):
print n
pass
print “done”
There are three new things here so let’s go through them. The first line has the “range(10)” that
we saw before. This creates a list of numbers from 0 to 9, as we saw before. The “for n in” is the
bit that creates a loop, and in this case it does something for every number in the list, and keeps
count by assigning the current value to the variable n. We saw variables earlier and this is just
like assigning n=0 during the first pass of the loop, then n=1, then n=2, until n=9 which is the
last item in the list.
The next line “print n” shouldn’t surprise us by simply printing the value of n. We expect all the
numbers in the list to be printed. But notice the indent before “print n”. This is important in
Python as indents are used meaningfully to show which instructions are subservient to others, in
this case the loop created by “for n in ...“. The “pass” instruction signals the end of the loop, and
the next line is back at normal indentation and therefore not part of the loop. This means we
only expect “done” to be printed once, and not ten times. The following shows the output as we
explained it.
It should be clear now that we can print the squares by printing “n*n”. In fact we can make the
output more helpful by printing phrases like “The square of 3 is 9”. The following code shows
this change to the print instruction repeated inside the loop. Note how the variables are not
inside quotes and are therefore evaluated.
for n in range(10):
print "The square of", n, "is", n*n
pass
print "done"
The result is shown as follows.
This is already quite powerful! We can get the computer to potentially do a lot of work very
quickly with just a very short set of instructions. We could easily make the number of loop
iterations much larger by using range(100) or even range(100000) if we wanted. Try it!
Can we relate this loop to the iterated functions we were looking at much earlier in this guide?
The main thing we have to do is to keep the output value of a function and feed it back into the
function without losing it as we go round the loop. The following code does this, and we’ll
explain it.
# function "triple it"
# initial value of x is 2
x=2
print "initial x = ", x
for n in range(10):
# update x with the current value of x multiplied by 3
x=x*3
print x
pass
The first line begins with a hash symbol #. Python ignores any lines beginning with a hash.
Rather than being useless, we can use such lines to place helpful comments into the code to
make it clearer for other readers, or even ourselves if we came back to the code at a later time.
Working through the code, we can see that x is set to the initial value 2 and is printed out. Then
a loop is iterated 10 times, as we saw before. The code inside this loop multiplies the current
value of x and assigns this new value to x again. Each updated x is printed for us to see. This
effect of this loop is just like the iterated functions we saw earlier with the output being used as
input for the next iteration.
We can see that rather than printing out the triples 6, 9, 12, 15, 18 and so on, the results are
what we expect if the output of the application of the function “triple it” is fed back into this
function as input. So, 2*3 is 6. This 6 is fed back as input, so the next output is 6*3 or 18, and so
on.
Complex Numbers
We’re getting closer to implementing the recipe for making our our Mandelbrot. We now need to
be able to do calculations with complex numbers.
Python is a really good choice because it can work with those 2­part numbers we discussed
called complex numbers. We don’t need to tell Python about the special rules for expanding out
2​
brackets, collecting similar terms, and the extra special part about i​
being replaced by ­1.
Python, unlike many other computer languages, can work with complex numbers out of the box.
Let’s try it. The following code shows how to create a complex number variable. We use the
form “complex(a,b)” to tell Python we mean (a + ib) where a is the real part and b is the
imaginary part of the complex number.
# assign the complex number (2+3i) to c
c = complex(2,3)
print c
# print c multiplied by (1 ­ 4i)
print c * complex(1,­4)
# print c squared
print c*c
The code shows how (2+3i) is assigned to c and printed. What might surprise us is that the
printout says (2+3j) and not (2+3i). It is common to use j to denote the imaginary part of a
complex number in the engineering community, whereas i is more prevalent in the mathematics
community. They both mean the same thing. I suppose the language designers for Python
chose j and it stuck, and there was never a good enough reason to change. Perhaps i was too
easily misread as 1. In any case, the use of j instead of i is harmless.
The code also shows that multiplying complex numbers is easy. In the old days we didn’t have
this luxury and if you wanted your computer to calculate with complex numbers you’d have to
explicitly teach it how to, using the rules we discussed earlier in this guide.
Functions
We spent a lot of time earlier working with mathematical functions. We thought of these as
machines which take and input, do some work, and pop out the result. And those functions
stood in their own right, and could be used again and again.
Many computer languages, Python included, make it easy to create reusable computer
instructions. Like mathematical functions, these reusable snippets of code stand on their own if
you define them sufficiently well, and allow you to write shorter more elegant code. Why shorter
code? Because invoking a function by its name many times is better than writing out all the
function code many times. And what do we mean by sufficiently well defined? It means being
clear about what kinds of input a function expects, and what kind of output it produces. Some
functions will only take numbers as input, so you can’t supply it with a word made up of letters.
Again, the best way to understand this simple idea of a function is to see a simple one and play
with it. Enter the following code and run it.
# function that takes 2 numbers as input
# and outputs their average
def avg(x,y):
print "first input is", x
print "second input is", y
a = (x + y) / 2.0
print "average is", a
return a
Lets talk about what we’ve done here. The first two lines starting with # are ignored by Python
but for us can be used as comments for future readers. The next bit “def avg(x,y)” tells Python
we are about define a new reusable function. That’s the “def” keyword. The “avg” bit is the name
we’ve given it. It could have been called “banana” or “pluto” but it makes sense to use names
that remind us what the function actually does. The bits in brackets (x,y) tells Python that this
function takes two inputs, to be called x and y inside the forthcoming definition of the function.
Some computer languages make you say what kind of objects these are, but Python doesn’t do
this, it just complains politely later when you try to abuse a variable, like trying to use a word as
if it was a number, or other such insanity.
Now that we’ve signalled to Python that we’re about to define a function, we need to actually tell
it what the function is to do. This definition of the function is indented, as shown in the code
above. Some languages use lots of brackets to make it clear which instructions belong to which
parts of a program, but the Python designers felt that lots of brackets weren’t easy on the eye,
and that indentation made understanding the structure of a program instantly visual and easier.
Opinions are divided because people get caught out by such indentation, but I love this
innovation. It’s one of the best ideas to come out of the geeky world of computer programming!
The definition of the avg(x,y) function is easy to understand as it uses only things we’ve seen
already. It prints out the first and second numbers which the function gets when it is invoked.
Printing these out isn’t necessary to work out the average at all, but we’ve done it to make it
really clear what is happening inside the function. The next bit calculates (x+y)/2,0 and assigns
the value to the variable named a. We again print the average just to help us see what’s going
on in the code. The last statement says “return a”. This is is the end of the function and tells
Python what to throw out as the functions output, just like machines we considered earlier.
When we ran this code, it didn’t seem to do anything. There were no numbers produced. That’s
because we only defined the function, but haven’t used it yet. What has actually happened is
that Python has noted this function and will keep it ready for when we want to use it.
In the next cell enter “avg(2,4)” to invoke this function with the inputs 2 and 4. By the way,
invoking a function is called ​
calling a function​
in the world of computer programming, and we’ll
do that too as it’s very common. The output should be what we expect, with the function printing
a statement about the two input values and the average it calculated. You’ll also see the answer
on it’s own, because calling the function in an interactive Python sessions prints out the returned
value. The following shows the function definition and the results of calling it with avg(2,4) and
also bigger values (200, 301). Have a play and experiment with your own inputs.
You may have noticed that the function code which calculates the average divides the sum of
the two inputs by 2.0 and not just 2. Why is this? Well this is a peculiarity of Python which I don’t
like. If we used just “2” the result would be rounded down to the nearest whole number. This
would be fine for avg(2,4) because 6/2 is 3, a whole number. But for avg(200,301) the average
is 501/2 which should be 250.5 but would be rounded down to 250. This is all just very silly I
think, but worth thinking about if your own code isn’t behaving quite right. Dividing by “2.0” tells
Python we don’t want it to round down to whole numbers.
Let’s take a step back and congratulate ourselves. We’ve defined a reusable function, one of
the most important elements of both mathematics and in computer programming.
Arrays
Arrays are just tables of values. Like tables, you refer to particular cells according to the row and
column number. If you think of spreadsheets, you’ll know that cells are referred to in this way,
B1 or C5 for example, and the values in those cells can be used in calculations, C3+D7 for
example.
We’ll use arrays to represent the complex plane on which the Mandelbrot set is plotted, so let’s
get familiar with them. Enter and run the following code.
a = zeros( [3,2] )
print a
This creates an array of shape 3 by 2, with all the cells set to the value zero and assigns the
whole thing to a variable named a. We then print a. We can see the representation of this array
full of zeros in what looks like a table with 3 rows and 2 columns.
Now let’s modify the contents of this array and change some of those zeros to other values. The
following code shows how you can refer to specific cells to overwrite them with new values. It’s
just like referring to spreadsheet cells or a street map grid references.
a[0,0] = 1
a[0,1] = 2
a[1,0] = 9
a[2,1] = 12
print a
The first line updates the cell at row zero and column zero with the value 1, overwriting
whatever was there before. The other lines are similar updates, with a final printout with “print
a”. The following shows us what the array looks like after these changes.
Now that we know how to set the value of cells in an array, how do we look them up without
printing out the entire array? We’ve been doing it already. We simply use the expressions like
a[1,2] or a[2,1] to refer to the content of these cells which we can print or assign to other
variables. The code shows us doing just this.
print a[0,1]
v = a[1,0]
print v
You can see from the output that the first print instruction produced the value 2.0 which is what’s
inside the cell at [0,1]. Next the value inside a[1,0] is assigned to the variable v and this variable
is printed. We get the expected 9.0 printed out.
The column and row numbering starts from 0 and not 1. The top left is at [0,0] not [1,1]. This
also means that the bottom right is at [2,1] not [3,2]. This catches me out sometimes because I
keep forgetting that many things in the computer world begin with zero not 1.If we tried to refer
to a[ 3,2] we’d get an error message telling us we were trying to locate a cell which didn’t exist.
We’d get the same if we mixed up our columns and rows. Let’s try accessing a[0,2] which
doesn’t exist just to see what error message is reported.
Plotting arrays
Just like large tables or lists of numbers, looking at large arrays isn’t that insightful. Visualising
them helps us quickly get an idea of the general meaning. One way of plotting 2­dimensional
arrays of numbers is think of them as flat 2­dimensional surfaces, coloured according to the
value at each cell in the array. You can choose how you turn a value inside a cell into a colour.
You might choose to simply turn the value into a colour according to a colour scale, or you might
colour everything white except values above a certain threshold which would be black.
Let’s try plotting the small 3 by 2 array we created above. Enter and run the following code.
imshow(a, interpolation="nearest")
The instruction to create a plot is imshow(), and the first parameter is the array we want to plot.
In the old days, plotting arrays was much more involved. The kids these days don’t know how
easy they have it. That last bit “interpolation” is there to tell Python not to try to blend the colours
to make the plot look smoother, which it does by default. Let’s look at the output.
How exciting! Our first plot shows the 3 by 2 sized array as colours. You can see that the array
cells which have the same value also have the same colour. When we plot the Mandelbrot set,
we’ll be using this very same imshow instruction to visualise an array of values.
The IPython package has a rich set of tools for visualising data. You should explore them to get
a feel for the wide range of plots, and even try some of them. Even the imshow() instruction has
many options for plotting for us to explore, such as using different colour palettes.
We’ve now covered enough basic Python to start building up the instructions to calculate and
print a Mandelbrot set.
Mandelbrot Set in Python
We’ll build up the set of instructions to calculate and plot a Mandelbrot set, by breaking the task
down into smaller parts. This approach is encouraged in all of computing. It helps clarify thinking
about the problem to be solved, encourages the creation of well developed reusable code, and
reduces errors through working on smaller problems rather than one large complex problem.
2​
The Iterative Function z​
+c
Let’s start at the core of the Mandelbrot calculation. We know from before that each point of the
2​
Mandelbrot set is tested to see if it diverges or not, when the z​
+c function is applied repeatedly.
As a reminder, the complex number c represents the point being tested. Like all complex
numbers it has two parts, a real part and an imaginary part. Together these can be thought of as
coordinates or a grid reference on a 2­dimensional surface. The function starts with a complex
number z set to zero, or more specifically (0 + 0i).
When the function is repeatedly applied, with the output put back in as input, the resulting
values may diverge rapidly, or they may follow an orbit which is doesn’t escape. As we saw
earlier, we could choose colours for each point depending on whether the point diverges or not.
This produced some intricate plots using only black and white. We then changed what we
plotted, and coloured according to how quickly a point diverged. This produced the beautiful
plots of the Mandelbrot set and it’s coloured surrounding regions.
This means the Python function we want to write returns the number of iterations it takes to
2​
diverge. The function still needs to calculate successive values of z​
+c, it just doesn’t have to
2​
return them. A good way to see if a point will diverge is to see if the output values of the z​
+c
function get larger than 4 in magnitude. If it does, we don’t have to keep iterating to see if the
orbits might come back and the values not diverge. If you look at plots of the Mandelbrot set you
can see that any point sufficiently far away from the centre will always diverge. The only ones
that don’t are closer to the centre point at (0 + 0i). A distance of 4 is a safe choice to be sure
that any point which ever end up beyond that distance from the centre will always diverge. Why
not choose a threshold of, say, 100 instead of 4. There wouldn’t be anything mathematically
wrong with that, but choosing a lower but still valid threshold of 4 means we reduce significantly
the amount of unnecessary calculations to be done.
Let’s start writing some code. The definition of the core calculating function must take the
chosen point represented by complex c, and return the number of iterations taken to breach a
threshold on its way to divergence. So the start and end of this function look like the following.
def mandel(c):
..
..
return iterations
The mandel function takes the parameter c, the complex number representing the point to be
tested for divergence. The function returns the number of iterations it took to breach the
threshold. What if the point doesn’t diverge and has a nice tight orbit? Will we keep running the
iterations forever because the threshold will never be breached? Well if we did that we’d never
progress beyond that single point being tested, and eventually our code would fail as it ran out
of space to keep track of the iterations. We need a way of stopping the calculations when we
are satisfied the point won’t diverge. A rough and ready, but good enough way, is to define the
maximum number of iterations the function is to be applied. It must be large enough to convince
us that the point won’t diverge, remembering that earlier we did see some cases of a belated
divergence after an initial period of what looked like a safe orbit.
We could set this maximum iteration number once and for all, or we could make it a parameter
we pass into the mandel(c) function, so that we can easily change it if needed. The function
would then look something like like mandel(c, maxiter). Why would we need to change it? Well,
as you explore the Mandelbrot set’s fine detail, you need more iterations to decide if a point will
diverge or not, and to get a more accurate view of the rate of divergence. Too few iterations and
the finer details, when plotted, are not sufficiently defined. Two points close to each other may
appear like they don’t diverge, but with more iterations it may become apparent that one does
and the other doesn’t. This distinction defines the detail that is uncovered and plotted.
Let’s write some more of this mandel(c, maxiter) function and explain it. Look at the following.
def mandel(c, maxiter):
z = complex(0,0)
for iteration in xrange(maxiter):
…
…
…
pass
return iteration
We’ve now updated the mandel() function to take the complex c point to be tested, and also the
maximum iterations as maxiter. We set the starting value of z to be zero, or more precisely
(0+0i). Then we write the “for ..” code loop which iterates a maximum of maxiter times, keeping
count in the variable named iteration. The end of the function is still returning the iteration count,
whether that reaches the maximum maxit, or is stopped sooner by a magnitude threshold test.
2​
What’s left is to fill in the code describing the iterated function z​
+c and the check to see if the
threshold has been breached. These are easy so let’s write them out and explain them.
def mandel(c, maxiter):
z = complex(0,0)
for iteration in xrange(maxiter):
z = (z*z) + c
if abs(z) > 4:
break
pass
pass
return iteration
Here we’ve added the “z = (z * z) + c” instructions which calculates the next value of z based on
the current value and the chosen c. We then check to see if the magnitude, or absolute value
denoted abs() in Python, of c is greater than 4, and if it is, the instruction “break” simply breaks
out of the “for” loop. Once this happens there are no more instruction after the loop, and so the
mandel(c,maxiter) function returns the value of iteration to whoever called it in the first place. If
the point doesn’t diverge, then abs(z) is never more than 4, so the for loop simply keeps running
until the count reaches the maximum iterations, and it is this maximum that is then finally
returned by the mandel(c, maxiter) function.
The following shows the mandel() function in a cell, and an example call of this function mandel(
2​
complex(0.5, 0.5), 40) returning the value 5. That is, the z​
+c iterative function was tested at the
point (0.5 + 0.5i) with a maximum limit of 40 iterations. The returned value of 5 is the number of
2​
iterations it took for the magnitude of the output of z​
+c to breach the threshold telling us the
point diverges. Enter the code and try it for some other values of c and maxiter.
If we were being pedantic we’d note that the return value from mandel() is in fact the one less
than the actual iterations because the count starts at 0 not 1. We won’t bother changing the
return value to “iteration + 1” because it doesn’t make a difference to which points are identified
as diverging or not, and the slight change in colour that is plotted is also not worth fretting about.
Along this vein, you may also notice that the test to see if the magnitude of the values output
2​
from z​
+c at each iteration is calculated according to the Pythagorean definition, that is, the
square root of the sum of the two parts squared. Again, you could shave off some calculation
effort by not taking the square root and just comparing the sum of the squares with 16. We won’t
bother because the code is simpler with abs(z) and these days computers are so fast that we
won’t be waiting long for the calculations to complete. In the 90s, Mandelbrot sets took hours to
plot on home computers, not seconds! Avoiding unnecessary calculations was a serious task.
The Atlas
We’ve defined the Python function which does the important calculations to test whether a point
on a two dimensional plane is within the Mandelbrot set or not, and if not, what colour it should
be assigned to indicate how fast it diverges.
Points on this two dimensional plane, as we discussed earlier, can be represented by complex
numbers because they have two independent parts, the real and imaginary parts, to act like
coordinates or grid references. Mathematicians call this the ​
complex plane​
, and again I dislike
the term “complex” because it’s not complicated and only puts people off. We called it the atlas
of regions earlier when we were trying to grasp these ideas as gently as possible.
We now need to define in Python which part of this complex plane we are interested in because
we can’t plot the entire plane as that goes on forever in each direction. Rectangular or square
sections are easiest because the shape matches our computer screens, and the complex
numbers are already in a rectangular form with one part describing the horizontal distance and
the other the vertical distance from the origin.
We also need to divide up this section into regularly spaced points. Again there are an infinite
number of points in any rectangle or square and we need to constrain ourselves to a finite
amount of work. Regular spacing means we can easily represent these points as pixels in an
image.
Let’s play with a Python function that conveniently creates a series of numbers from a starting
value, up to an upper value, spaced regularly apart. In a new cell enter and run the code
“linspace( 0.0, 5.0 , 5)” to see what happens.
The linspace function has taken the starting point 0.0 and an endpoint 5.0 and divided it such
that there are 5 evenly spaced points, including the endpoints. Five points means 4 sections,
just as 3 points means 2 sections, that start, middle and end. This readymade linspace() Python
function is a really good start to defining the section of the complex plane we are interested in
and dividing it up into evenly spaced points to test and plot.
Let’s plough on. We can use the convenient linspace function to define a rectangular section of
the complex plane by using it once for the horizontal side of the section and again for the
vertical side. The following diagram illustrates this to make it clearer to see.
www.allitebooks.com
Let’s imagine we want a rectangle with bottom left at (­2,­2) and the top right at (4,2). This has a
horizontal length of 6, and a vertical height of 4. Let’s divide the horizontal length into 12
sections, and the vertical into 8. The following Python code shows how you create a list of each
of these dividing points along the horizontal and vertical sides using the linspace() function.
The horizontal points are all spaced 0.5 units apart, and the vertical points are 1 whole unit
apart.
But these lists only divide the sides. How can they be used to define all the points we want to
test inside a rectangular section? We do this by taking combinations of points from these lists.
All the combinations will result in a list of all the points in the rectangle of interest. We can
combine these lists in a systematic way using Python loops. Let’s do it using the same rectangle
and divisions we’ve just defined. In the next IPython cell enter and run the following code.
x_list = linspace( ­2.0, 4.0, 13)
y_list = linspace( ­2.0, 2.0, 5)
for x in x_list:
for y in y_list:
print x,y
pass
pass
You can see that we first create lists of the x and y coordinates using the now familiar linspace()
function, and assign these lists to variables named x_list and y_list. If you printed these
variables you’d get the same output as we saw before. Next we can see the familiar Python
loops. The outer loop works through the x_list and the inner loop works through the y_list. The
inside of the loops only prints the current x and y values. In English, what this is doing is “for
each value in the x_list go through the y_list, and for each of combination print out the values”.
This does mean the y­list is walked through many times, once for every item in the x_list. You
can see that if we were doing a lot of work inside the central loop, such as calculating the
mandel() function, this results in a lot of work. It’s just as well a computer is doing it and not us
with a pencil and paper!
The output of this code is quite long as there are many combinations of x and y. The following
shows what my notebook looks like.
You can see the list of all points in the rectangle of interest starting with (­2, ­2), (­2, ­1), (­2,0) ..
and so on.
We’ve made good progress, and we’re almost there. We have the mandel() function to test
given points, and we now have a means to create such points. Next we need to find a way of
associating these points with the pixels in an array of colour values that could be plotted using
the imshow() function we used earlier.
Complex Plane to Image Translation
Why do we need a translation? Why can’t we just plot the rectangular region of the complex
plane? Surely this is a perfectly reasonable request of a computer? There are two reasons.
The first is that the complex plane region is just a list of points, or complex numbers, and these
don’t have a colour associated with them to plot. We need to give the imshow() plotting function
something which contains colour information.
Secondly, the imshow() function expects to plot a 2­dimensional array, not a long list of complex
numbers like the ones we created earlier. It expects an array where the contents of an array cell
represent the colour to be plotted, and the position of the cell in terms of row and column is the
position in the image plotted.
It’s worth saying that we could work with long lists of complex numbers and associated colour
values, including plotting them, if we used different extensions to Python. However we won’t
here as we want to keep things simple and use only the most common extensions.
So we need to to create an array of the right size to cover the desired region of the complex
plane that we previously defined using the linspace() functions. The following diagram shows
how these two are related.
You can see from the diagram that the bottom left point of the complex plane region is
represented by the cell at position [0,0] of the array. That was easy to agree with. What about
the top right point of the complex plane region? If it is at, say for example (0.77 + 0.44i), what
row and column of the array would this be? We can’t even use the real and imaginary parts of
the complex number to represent the row and column because the values are fractional, and
even worse, they are less than 1! We can’t have a row that is number 0.77, we need whole
numbers!
Given that the rows and columns of the plotted array need to increment in whole units, we can
simply place each of the evenly spaced points between the bottom left and top right into the
array. So if the points were 0.5 units apart on the complex plane, they would be 1 unit apart in
the array. So this means the array has the same number of columns as the number of divisions
along the horizontal side of the region. Similarly it has the same number of columns as the
number of divisions of the vertical side of the rectangular region. The previous diagram also
illustrates this.
It’s easy to get confused between rows and columns, and trying to remember which one
represents the real horizontal axis and which one the vertical imaginary axis of the complex
plane. We won’t worry too much because the worst that can happen is the Mandelbrot set is
plotted on its side which is really easy to rectify.
Let’s now define the complex plane region. Enter and run the following code. It makes sense to
place this at the top of your IPython notebook because it sets out up front which region you are
interested in. Use the button marked as “Insert cell above” to create a new cell at the top.
# set the location and size of the complex plane rectangle
xvalues = linspace(­2.25, 0.75, 1000)
yvalues = linspace(­1.5, 1.5, 1000)
# size of these lists of x and y values
xlen = len(xvalues)
ylen = len(yvalues)
The first instruction creates a list of 1000 points evenly placed between ­2.25 and 0.75,
inclusive. These will be the horizontal divisions of the rectangle, and we’ll call the list xvalues.
Similarly yvalues is the list of 1000 evenly spaced points between ­1.5 and 1.5. This rectangle
with bottom left (­2.25 ­ 1.5i) and top right (0.75 + 1.5i) is chosen as it nicely frames the
Mandelbrot set. You can later choose your own rectangle to explore the set. Choosing a smaller
width and height means you are zooming into the set, as if using a microscope.
The last two lines simply take the length of the lists and assign them to variables. The len() of a
list [1,2,3] is 3 because it has 3 elements. The variable xlen is becomes 1000 because xvalues
contains 1000 points and so len(xvalues) is 1000. Why didn’t we just say xlen = 1000? Because
it is good practice in computer programming to set changeable parameters once and
automatically derive subsequent values. If we didn’t it becomes cumbersome to change all
references to this number of subdivisions, and if we forget, we cause incorrect calculations to
happen. This way, if we want to change the number of subdivisions from 1000 to say 500 we
only change the code once.
The following code creates the array of colour values. We can see it is created to have a size of
xlen by ylen, so each cell represents one of the evenly spaced points on the complex plane.
Enter and run it in a new cell at the bottom of the notebook.
atlas = empty((xlen,ylen))
We’re almost there! All that remains is to fill this array with colour values and plot it using
imshow(). What values do we put int? Well we know the colour value has to be related to the
number of iterations it takes a point to diverge so why not simply use the returned value from
the mandel() function? Let’s do it.
Add the following code to the same cell as you created the atlas array.
for ix in xrange(xlen):
for iy in xrange(ylen):
cx = xvalues[ix]
cy = yvalues[iy]
c = complex(cx, cy)
atlas[ix,iy] = mandel(c,40)
pass
pass
You’ll recognise that this code is simply two loops, one inside the other. The loops count
through the rows and columns of the atlas array using variables ix and iy. These counts are
perfect for referring to the contents of the array which are also counted from 0 to xlen­1, and not
1 to xlen. You may have noticed we use xrange instead of range. The range would work but for
very large lists xrange is more efficient because it doesn’t actually create a list, but gives you the
contents as you ask for them. This is an under­the­hood detail and you don’t need to worry
about it if you don’t want to.
These two loops allow us to refer to every cell of the array using atlas[ix,iy]. The code inside the
loops uses the counts ix and iy to look up the actual complex number to be tested by the
mandel() function. The real and imaginary parts were in the xvalues and yvalues lists we
created earlier, and can be dug out using xvalues[ix] and yvalues[iy].
Let’s be really clear about what ix and iy do for us. Because the loops take ix and iy through all
the possible values in the atlas array, and the array was deliberately created to match all the
points in the complex plane we wanted to test, the array cell atlas[ix,iy] corresponds to the
complex number in the region of interest with real part xvalues[ix] and imaginary part yvalues[iy].
You can see the code constructing the complex number c using these values.
The last part inside the loops is updating the contents of the array with the return value from the
mandel() function.
That’s it! We’re done! Now let’s see the results! In a new cell, enter the following code.
figsize(18,18)
imshow(atlas.T, interpolation="nearest")
The first line sets the size of the plot to 18 by 18 because the default is too small. The imshow
instruction plots the array. As before we are telling Python not to smooth the colours or blend
them, just show them as they are. We also refer to atlas with a “.T” appended to it. That’s
because the array is plotted on its side compared to what we want to see.
Run the code and you’ll see the Mandelbrot set.
You can zoom into parts of the Mandelbrot set by changing the bottom left and top right points
of the complex plane region. We simply change the code which sets the xvalues and yvalues.
For example using the rectangle from earlier in this guide with (­0.22 ­ 0.70i) bottom left and
(­0.21 ­0.69i) as top right means setting the following xvalues and yvalues as follows:
# set the location and size of the atlas rectangle
xvalues = linspace(­0.22, ­0.21, 1000)
yvalues = linspace(­0.70, ­0.69, 1000)
The resulting image was quite undefined because we set too low a value for maximum
iterations. Changing it from 40 to 120 is done as follows:
atlas[ix,iy] = mandel(c,120)
The result is a more detailed image, as follows. It’s really quite beautiful!
Mandelbrot Set in Python
The following presents the complete Python code we’ve built up to plot our own Mandelbrot
fractals for you to look over. I’ve added comments to help remind you what each code section
does.
# loads the numerical and plotting extensions to Python
# needed if you're using a locally installed IPython, not for some
online IPython providers
%pylab inline
# set the location and size of the atlas rectangle
xvalues = linspace(­2.25, 0.75, 1000)
yvalues = linspace(­1.5, 1.5, 1000)
# size of these lists of x and y values
xlen = len(xvalues)
ylen = len(yvalues)
# mandelbrot function, takes the fixed parameter c and the maximum
number of iterations maxiter, as inputs
def mandel(c, maxiter):
# starting value of complex z is 0+0i before iterations update it
​
​z = complex(0,0)
​ start iterating and stop when it's done maxiter times
#
for iteration in xrange(maxiter):
​
​ the main function which generates the output value of z
#
from the input values using the formula (z^2) + c
z = (z*z) + c
​
​ check if the (pythagorean) magnitude of the output complex
#
number z is bigger than 4, and if so stop iterating as we've diverged
already
if abs(z) > 4:
​
break
pass
pass
​# return the number of iterations we actually did, not the final
value of z, as this tells us how quickly the values diverged past the
magnitude threshold of 4
return iteration
​
# create an array of the right size to represent the atlas, we use
the number of items in xvalues and yvalues
atlas = empty((xlen,ylen))
# go through each point in this atlas array and test to see how many
iterations are needed to diverge (or reach the maximum iterations
when not diverging)
for ix in xrange(xlen):
for iy in xrange(ylen):
​# at this point in the array, work out what the actual real
and imaginary parts of x are by looking it up in the xvalue and
yvalue lists
cx = xvalues[ix]
​
cy = yvalues[iy]
c = complex(cx, cy)
​ now we know what c is for this place in the atlas, apply
#
the mandel() function to return the number of iterations it took to
diverge
# we use 40 maximum iterations to stop and accept the
function didn't diverge
atlas[ix,iy] = mandel(c,40)
​
pass
pass
# set the figure size
figsize(18,18)
# plot the array atlas as an image, with its values represented as
colours, peculiarity of python that we have to transpose the array
imshow(atlas.T, interpolation="nearest")
Exploring the Mandelbrot Set
You should explore the Mandelbrot set, and see the beautiful patterns, the infinite intricate detail
for yourself. You’ll see parts of the set that no­one else has seen precisely because the set is
infinitely detailed.
You could use the Python code we’ve developed, but it’s a little clunky working out the
coordinates of the rectangles you want to zoom into. It would be better if you could just point
and click at the part you were interested in zooming into.
In fact there are many computer applications available for you to explore the Mandelbrot set. I’d
recommend XaoS from ​
http://xaos.sourceforge.net​
, which is really easy to use, and so fast that
it animates the effect of you zooming, or falling, deeper and deeper into the detail of the
Mandelbrot set.
You’ll have seen earlier than I’m a fan of applications which don’t need installing but are purely
usable through a web browser. There’s a developing version of XaoS usable through a modern
web browser at ​
http://jblang.github.io/XaoSjs​
.
Part 3: Even More Fun
In this part we’ll take the ideas we’ve already developed and take them further, just for fun!
We’ll introduce the Julia sets, relatives of the Mandelbrot set. We’ll be uncovering some of the
intimate connections between the two.
We’ll also see if we can construct three dimensional versions of the flat fractals we’ve developed
already. In doing so we’ll be trying to invent some new mathematics. How exciting!
Julia Sets
Julia sets are closely related to the Mandelbrot Set. Before we dive into talking about them, let’s
have a look at a few. All of these are produced by the code we’ve developed here.
You can see that they are different to the Mandelbrot set, and yet there is something about them
that is similar. The patterns are sometimes very different, and sometimes very similar to those of
the Mandelbrot set. You can also see that some Julia sets are a single object like the
Mandelbrot, but some are many pieces, with some even becoming so fragmented they are
almost like dust.
It might not be obvious that whilst there is only one Mandelbrot, there are many, infinitely many,
Julia sets.
Recipe
The recipe for creating Julia sets is the same as that for the Mandelbrot set except for one key
difference.
Let’s remind ourselves of the recipe for the Mandelbrot set. We choose a rectangle on the
complex plane and choose many evenly spaced points to test to see if they are part of the
2​
Mandelbrot set, or not. We do this by seeing if repeated iteration of a function z​
+c, where c is
the point being tested, results in the orbit escaping and diverging, or not. And if it does, we
colour the point according to how fast the point diverges.
This recipe is essentially the same for making Julia sets. The difference is that the roles of the z
and c are reversed. Instead of c representing the point on the complex plane being tested, it is
kept constant for all the points being tested. Similarly instead of z starting from zero it starts as
the complex number representing point being tested.
That’s it! That’s the only difference. The following diagram reinforces this simple difference.
The following shows the calculating function julia(z, c, maxiter), based on the mandel(c, maxiter)
function.
def julia(z, c, maxiter):
for iteration in xrange(maxiter):
z = (z*z) + c
if abs(z) > 4:
break
pass
pass
return iteration
You can see that z doesn’t start at (0+0i) anymore but is a parameter passed to the function.
The parameter c is also passed but is a fixed complex number and not a number representing
the point being tested, because z does that now.
The Python program to plot Julia sets is presented here with the differences to the Mandelbrot
program highlighted in bold or crossed out.
# loads the numerical and plotting extensions to Python
# needed if you're using a locally installed IPython, not for some
online IPython providers
%pylab inline
# set the location and size of the atlas rectangle
xvalues = linspace(​
­2, 2​
, 1000)
yvalues = linspace(​
­2, 2​
, 1000)
# size of these lists of x and y values
xlen = len(xvalues)
ylen = len(yvalues)
# value of c (unique for each Julia set)
c = complex(­0.35, 0.65)
# julia function, takes the fixed parameters z and c and the maximum
number of iterations maxiter, as inputs
def julia(z, c, maxiter):
# starting value of complex z is 0+0i before iterations update it
​
​z = complex(0,0)
​ start iterating and stop when it's done maxiter times
#
for iteration in xrange(maxiter):
​
​ the main function which generates the output value of z
#
from the input values using the formula (z^2) + c
z = (z*z) + c
​
​ check if the (pythagorean) magnitude of the output complex
#
number z is bigger than 4, and if so stop iterating as we've diverged
already
if abs(z) > 4:
​
break
pass
pass
​# return the number of iterations we actually did, not the final
value of z, as this tells us how quickly the values diverged past the
magnitude threshold of 4
return iteration
​
# create an array of the right size to represent the atlas, we use
the number of items in xvalues and yvalues
atlas = empty((xlen,ylen))
# go through each point in this atlas array and test to see how many
iterations are needed to diverge (or reach the maximum iterations
when not diverging)
for ix in xrange(xlen):
for iy in xrange(ylen):
​# at this point in the array, work out what the actual real
and imaginary parts of x are by looking it up in the xvalue and
yvalue lists
zx​= xvalues[ix]
​
zy​= yvalues[iy]
​
z​= complex(​
​
zx, zy​
)
​ now we know what c is for this place in the atlas, apply
#
the mandel() function to return the number of iterations it took to
diverge
# we use 80 maximum iterations to stop and accept the
function didn't diverge
atlas[ix,iy] = ​
​
julia​
(z,c,80)
pass
pass
# set the figure size
figsize(18,18)
# plot the array atlas as an image, with its values represented as
colours, peculiarity of python that we have to transpose the array
imshow(atlas.T, interpolation="nearest")
Try plotting different Julia sets by changing the c parameter. Remember it is the single
parameter c which uniquely defines the Julia set. Is there a connection between the Julia sets
defined by this c parameter and the Mandelbrot set?
The Connection between Julia and Mandelbrot Sets
Experimenting with plotting Julia sets for different values for c reveals that some of them are a
single set and others are fragmented into a few or many pieces. If you remember that the
Mandelbrot set lives on a complex plane you might be tempted to choose your c based on
where the point lies inside or near the Mandelbrot set. If you did, you’d see that the Julia sets
that come from c inside the Mandelbrot set are single unfragmented sets. Similarly, those Julia
sets that come from c outside the Mandelbrot set are fragmented. And the further you move c
away from the Mandelbrot set the more fragmented and like dust the Julia sets get.
In this way, the Mandelbrot set is a kind of guide to Julia sets. The following diagram illustrates
this connection between the Mandelbrot set and Julia sets.
Mandelbrot and Julia Mountains
Let’s try to turn the 2­dimensional flat images of the Mandelbrot and Julia sets into 3
dimensions. One way to do this is to use the colour information in each image array to represent
altitude, the height of a landscape above sea level as it were. We should then see mountainous
landscapes shaped by the values of arrays filled by the Mandelbrot or Julia calculations.
Surface Plot
Let’s try it on a very simple array first. The following code prepares a 3 by 2 array.
a = zeros( [3,2] )
a[0,0] = 1
a[0,1] = 2
a[1,0] = 4
a[2,1] = 3
print a
Plotting a simple flat image based on these values, as earlier, using imshow(a,
interpolation="nearest") results in the following plot as expected.
Now lets plot this same array in 3 dimensions, with the third dimension given by the value of
each cell. To do this we need to use a new extension to Python called mayavi. Sadly the purely
web based IPython services provided remotely can’t plot 3­dimensional plots as they need your
computer’s graphics hardware to do the hard work. You’ll need to use a locally installed IPython.
Telling IPython that we want to use mayavi uses the import instruction, just as before. The
special paylab instruction is to ensure the common set of numerical extensions are
automatically loaded in the local IPython.
%pylab inline
import mayavi.mlab
The instructions to plot a surface are simple.
mayavi.mlab.surf(a, warp_scale="auto")
mayavi.mlab.show()
The first instruction prepares the surface plot and allows IPython to automatically scale the
heights. The second instruction is needed to actually show the plot. This time the result will
appear in a separate window outside your browser. You can do quite a lot to this 3­d plot,
including rotating it around and changing the lighting which illuminates it. The following shows
the above small array plotted in this way, and again rotated using the mouse. Try it!
For easy reference, the entire code for creating a simple 3 by 2 array and plotting a
3­dimensional plot of it as as follows:
%pylab inline
import mayavi.mlab
a = zeros( [3,2] )
a[0,0] = 1
a[0,1] = 2
a[1,0] = 4
a[2,1] = 3
mayavi.mlab.surf(a, warp_scale="auto")
mayavi.mlab.show()
Mandelbrot Mountains
We now apply this same idea to the larger arrays created by the Mandelbrot code we wrote
earlier. We simply add the new instructions to create the 3­dimensional plots, remembering to
import the mayavi extension too. It’s worth keeping the previous imshow() instruction to see the
flat view too. The instructions are as follows. The warp_scale was adjusted to 1.0 to give a
reasonable height, the automatic setting created hills that were a little too steep.
mayavi.mlab.surf(atlas.T, warp_scale=1.0)
mayavi.mlab.show()
The output is quite nice, and the following shows the plot rotated around by dragging with a
mouse.
These 3­dimensional height fields as some people call them, give an interesting perspective of
the Mandelbrot set.
Let’s try that again but with the original rectangle zooming into details of the Mandelbrot set, and
also change the colour palette by using the interactive menus.
Some of these landscapes are quite haunting. Let’s try the same idea with Julia sets.
Julia Mountains
The following shows various Julia fractals rendered as height fields, using a range of zooms and
colour palettes.
Gentler Landscapes
Some of the landscapes we created were a little spiky and noisy. Let’s see if we calm them
down, smooth off the sharp edges, and reduce the cragginess a little, all in the hope of creating
a perhaps more gentle, more appealing, landscape.
One way to do this is to calculate a new image array, based on the original one, but with each
new value somehow smoothed based on it’s neighbours values. A kind of local average, as it
were. Let’s consider this last point a little more. If we calculated the average value for the entire
array, by adding up all the values and dividing by the number of array elements, we would have
just one single value. This would be the same everywhere and make for a flat boring landscape.
Instead we want to calculate the average over a smaller group of neighbouring array elements,
perhaps a 3 by 3 square or a bigger circle. Engineers and scientists who work with images or
signals do this kind of thing fairly often to try to reduce noise.
Python provides a collection of such filters that can be applied to an array of values, often useful
when they are in fact images. A common smoothing filter is a slightly more sophisticated version
of the local average we just described, called a ​
Gaussian​
blur filter. If you work with image or
photo editing applications like Photoshop or GIMP then you’ll be familiar with applying a
Gaussian blur to smooth an image. We won’t go into the details of why Gaussian blur filters are
so useful and common but you can think of them as local averages, just as we described above,
but with greater weight given to those array elements closer to the one you’re calculating.
Enough talk, let’s get on with it. We’ll need to import the Python extension at the top of our code
as follows:
import scipy.ndimage
The following shows how the Gaussian blur filter can be used to create a smoothed image array
from the original, which we call atlas in our previous code. The value 2 is the strength of the
smoothing
smoothed_atlas = scipy.ndimage.gaussian_filter(atlas.T, 2)
We can plot this as a flat image just as before using the imshow function.
# set the figure size
figsize(18,18)
# create a smoothed image of the original by applying a
Gaussian blur filter
smoothed_atlas = scipy.ndimage.gaussian_filter(atlas.T, 2)
# plot the array atlas as an image, with its values represented
as colours, peculiarity of python that we have to transpose the
array
imshow(smoothed_atlas, interpolation="nearest")
Similarly we can plot the 3­dimensional landscape just as before using the mayavi extensions.
mayavi.mlab.surf(smoothed_atlas, warp_scale=0.9)
mayavi.mlab.show()
The following shows images for before and after the Gaussian smoothing filter is applied for the
Julia set generated by c=(­0.768662 + 0.130477i), calculated in the rectangle with bottom left
(­1­1i) and top right (1+1i).
Before Gaussian blur filter:
2­dimensional plot:
3­dimensional landscape plot:
After Gaussian blur filter:
2­dimensional plot:
3­dimensional landscape plot:
The same but zoomed in closer:
Try New Things Yourself
I’d encourage you to explore other filters to see if they make interesting images. Perhaps you
2​
might explore different functions aside from z​
+c which was the basis for both the Mandelbrot
and Julia fractals.
And remember, you can’t do any harm by experimenting and playing. Trying new things and
testing out crazy ideas is how new mathematical discoveries are made. What’s the worst that
can happen?
As a teaser, here’s an image produced by one of the filters provided by the same extension that
we got the Gaussian filter from. It’s beautiful!
Epilogue
What a journey!
We started by opening our eyes to the natural world around us, noticing what kinds of things
held our interest, things that we found beautiful. We realised that fractals tread a fine line
between boring predictable order and the noisy anarchy of disorder.
We then started playing with function, using nothing more than school level mathematics. We
tried iterating these functions, not a difficult idea but one that surprised us with the wide range of
behaviours we saw from apparently simple functions. We saw orbits blowing up (divergence)
and settling down (convergence). We were surprised to discover periodic orbits, surprised
because there was all this richness of behavior from very simple functions that we never saw at
traditional school maths lessons. Then discovered something that mathematicians and
scientists only really appreciated in the last 100 years or so, despite mathematics being
thousands of years old. That thing was chaos and chaotic behaviour.
We then gently applied these ideas of iterating functions to a new kind of number, called
confusingly a complex number. We learned that they were nothing to be afraid of at all but just
two normal numbers joined together. We could do calculations like addition and multiplication
with them just like the normal mathematics we know, except for one simple special rule.
2​
We then focussed only on the z​
+c function, iteratively applied to complex numbers. We found
that the orbits showed a range of behaviours, from divergence, convergence, periodic and
chaotic orbits. We made an atlas, a kind of guide, to show through colour, how each point in the
complex plan behaves ­ and we found the Mandelbrot and Julia sets.
I really hope you found the journey easy, interesting, at times exciting and surprising, and with
results that were pleasing and beautiful, haunting even.
I would welcome your comments, feedback to improve the guide, ideas for exploring in different
directions. I’d especially welcome your feedback on any part of the journey that was difficult or
could have been better explained. If your feedback significantly improves the next edition, I’ll
send you a copy for free.
Join the conversation at:
http://makeyourownmandelbrot.blogspot.com
Resources
XaoS
A free open source fractal explorer for Windows, Linux and Mac. It’s really good at animating a
diving session, as you fall deeper and deeper into some part of the Mandelbrot set.
http://matek.hu/xaos/doku.php
IPython
A free open source package of the Python programming language together with commonly used
extensions for numerical and graphical work.
http://ipython.org/
Wakari.io
An online version of IPython, with free trial accounts. Used for many of the Mandelbrot plotting
examples in this guide.
http://wakari.io
www.allitebooks.com
Download