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