The inspiration for this investigation comes from the book Creating Symmetry: the Artful Mathematics of Wallpaper Patterns, in particular implementing functions that create "rosette" symmetry - that is there is a central point around which there is an n-fold rotational symmetry.
I am also following an extension of the ideas behind the images generated from cyclic motions. I want to combine technique to ultimately make images that are somewhat different from those in Dr Farris's book.
The basis of the cyclic motion algorithms were closed curves constructed using epicycles: imagine a small wheel rolling around the circumference of a larger wheel, and that is rolling round the circumference of an even larger wheel etc..
We allow the contact point on the largest circle to make a complete circuit (that is, the rotation angle, θ, goes from 0 to 2*π - remember because we are doing proper maths we always measure angles in radians rather than degrees). From our basic trigonometry we know that we can now work out X and Y coordinates of every point on the circumference using X= R*cos(θ) and Y=R*sin(θ), where R is the radius of the circle.
While we are doing this rotation the smaller wheels are rotating at multiples of this basic rotation rate, and our program calculated the curve by increasing θ in small steps and adding up all the sines and cosines using formulas that were equivalent to:
X = R1*cos(θ + P1) + R2*cos(2(θ+P2)) + R3*cos(3(θ+P3)) .....
Y = R1*sin(θ + P1) + R2*sin(2(θ+P2)) + R3*sin(3(θ+P3)) .....
Here, the Rs are the different radii of the wheels, and the Ps (P1, P2, P3 etc.) are phases which allow us to start each of the different wheels already rotated by a certain amount. In fact we can allow ourselves to be even a bit more general and add in sines and cosine where the value of θ is multiplied by negative integers, which just means that the wheel rotates in the anticlockwise rather than clockwise.
That is all stuff that we have already done. Now think about the curve we have drawn. If we wanted to we could mark each point on that curve with a value of θ between 0 and 2*π. In fact, we could take that curve unbend it and lay it out in a straight line. A mathematician says that have created a mapping from the range 0 to 2*π (which we could think of as the points on a straight line starting at 0 and ending at 2*π) to every point on the curve in our canvas. (Mapping in this context really does just mean that we make a connection between all the points in one place out to points in another place. You could imagine drawing faint lines between points on a straight line and corresponding points on our curve. A mathematician says that all the points on our straight line and all the points on the curve are elements of sets and we can create a one-to-one mapping. The idea of a Set is one of the most fundamental concepts in mathematics, including being the basis of counting.)
Now fasten your safety belts, because we are about to put our foot on the accelerator (but you can close your eyes if you wish). The great mathematician Euler showed that we are allowed to write:
eiθ = cos(θ) + i.sin(θ)
where i is the square root of -1 (an imaginary number), so eiθ is a complex number which has real and imaginary parts. (If you know a bit of calculus you would also know that this equations comes as a real surprise, with a bit of wonder - see my Beautiful Maths page.)
We are now going to identify the value of the real part with the X direction in our drawing plane and the imaginary part with the Y direction. If we allow θ to vary from 0 to 2*π it makes eiθ draw out a circle when we separate out the real and imaginary parts and interpret them as X and Y coordinates.
Our epicyclic curve can now be expressed as:
R1*exp(i(θ + P1) )+ R2*exp(2i(θ+P2)) + R3*exp(3i(θ+P3)) ....
So far, nothing has changed apart from the notation. The equation looks simpler (though it carries more meaning) and in fact if we program this summation we have to write exactly the same algorithm as before because computers don't do imaginary numbers, we always have to separate the real and imaginary parts first and treat them separately. (In some computer languages there are built-in types which can hide this stuff behind the scenes for us - but it is still there!) And, let us remember, θ, stands for a real number (its value runs through the respectable range 0 to 2*π). What we are actually doing with this formula is mapping a line running along the Y axis to a closed curve in the X-Y complex plane.
Suppose now we ask what happens if we replace iθ (a completely imaginary number) by a complex number which has real and imaginary parts. Traditionally we use the symbol z for a complex number (which we usually choose to write it as ix+iy with suitable values of x and y). The expression ez then turns out to have a perfectly good mathematical interpretation. Take the next bit on trust if you cannot follow the maths, but this is very standard stuff for maths students (at about the end of high-school maths).
ez = e(x+iy) = ex.eiy = ex.(cos(y) + i.sin(y))
Equations over! What we now have is a generalisation from the previous mapping of a straight line into a curve into a mapping of any point on a 2D surface into points on another 2D surface.
What is so wonderful about that! Nothing really, except that because we are using expressions that can be written as equations using complex numbers (technically they also have to be analytic - but that terminology is really for the maths buffs among you) it turns out they have some very nice properties for creating visual images. (For example, any lines crossing at right angles in the plane we are transforming from will still cross at right angle in the plane we a transforming to. This is what we mean by a conformal map. It is not all: a vast amount of interesting, important and extremely useful 19th century mathematics grew out of complex analysis - but that is university stuff and a different story.)
It can also be interesting to produce non-conformal maps by using complex conjugates of z - that is replace (x+iy) by (x-iy). We loose the property of preserving angles in the transformation, but can gain freedom from some extreme behaviours (such as going off to infinity).
All we need to know now is that we can take our program for creating epicyclic curves and with relatively minor modifications and a re-interpretation of what the numbers it calculates actually mean we can make something completely different and perhaps more exciting. The reason that I have spent so much time explaining this is to demonstrate that in order to understand what a program is trying to do (and how it does it) you must grasp the intentions in the mind of the person who designed the software. Two programs that were constructed against quite different intentions may in fact differ less than might be expected in the algorithms employed: they are distinguished by the meanings to associated with the data going in and the data coming out. That is why it can be very difficult to read a program's source code and work out what it is supposed to be doing.
I also wanted to show why understanding the mathematics behind a program can be important. We used the abstract idea of epicycles to build one program and then used to connections between abstract mathematical ideas such as trigonometry and complex numbers to find a different way of extending the original software in the confident knowledge that the maths guarantees some interesting properties.
As it happens, the modified version of the epicycles program will not look particularly similar to the earlier version, though the algorithms will be essentially similar. I have decided that it is time to introduce some object-oriented programming techniques to simplify the structure of the software and make it align more obviously with the design ideas. It is a great mistake to believe that one saves time by never rewriting what has already been done. In my (very extensive!) experience, the first attempt to get a program running may more-or-less work but usually turns out to be overly complicated and a bit fragile. You only really understand how to do the job properly having struggled with the prototype. Too many programmers stay with this - and spend the next ten years (or someone does) applying sticking plaster to keep it from falling apart. Throw the prototype away! Build the production version to production standards! I find that this is still good advice even when working with no one to satisfy but myself. I get less frustrated and move faster by securing the foundations before building up.