A small tribute to the number pi

(Pi is this number that is needed to play with circles.)

March, the 14th, is called the pi-day by those people who both write dates in a specific (weird? who said weird?) order (month-day) (in France it's day-month, which cannot create a pi-day in any day of the year) and don't care that the value of pi is not exactly 3.14.

Every year I see this pi-day pop up here and there. That gave me the idea for a small tribute to pi.

The idea to plot the greek symbol for pi (π) using rotating circles popped up in my brain. The circle is pi, pi is the circle, plotting pi with circles is a nice circular reference. And a circular reference is a nice concept when dealing with pi, isn't it?

Using rotating circles to represent something is an obvious reference to the Fourier transform. The Fourier transform takes a function and represents it using a sum of sinus functions. The theory has many variations. The one we will be interested in is called the discrete Fourier transform, that takes a finite number of points and gives us a finite number of sinus, which, summed together, gives a curve that passes through all the points. The Fourier transform is defined over complex numbers, so it super-easily deals with 2D points (complex numbers may be used to represent points in a 2D plane).

For a bit more fun we won't use all the sinus given by the Fourier transform, only the first "big" ones.

So, to celebrate pi, I wrote a small program in the C language that should work on any environment where X Window and the cairo library are available (that includes: linux): pi.c

pi.c can output videos (raw, to be processed with for example ffmpeg to generate files in some format more appropriate for video players). I could have put those videos here but that would have been too huge to my taste.

So I decided to translate the little C program into javascript, for a bit more fun.

This webpage contains some javascript that produces more or less the same output as the C program. It was a funny journey to translate from C to javascript. I suspect the javascript version to be severly sub-optimal, with any given definition of optimality (speed, beauty, conciseness, whatever). I am not a javascript programmer, sorry. Note that the C version is also not super elegant, but so be it. It does the job. More or less. Hey it's a tribute to pi, not a tutorial on how to write nice and elegant C code! (even less javascript code)

To see the javascript code, simply look at the source of this webpage (ctrl+u with firefox for example, or download it with curl or wget, or whatever).

Below each animation (or image) you have the command to run the C program to produce the same animation (or image). Yes, there is a timing problem with the C code, so the animations run slower than they should. The fix is easy, exercise left to the reader.

Enough blah blah blah! Here is the result of a few hours of programming. An approximative representation of π with a few (eight) coefficients of a Fourier transform of the shape of pi in the 2D plane.

[click to start/stop animation]
[canvas does not work]

    ./pi -slow

And now, let's do a step-by-step description of how we get this animation.

We start with this image. Very simple pi shape.

[canvas does not work]

    ./pi -frame 0 -no-stroke -stroke-pi

From it we pick up 64 points. Why 64? Well, we can pick as much as we want but "usually" Fourier transforms use power of 2 number of points for efficiency (not that we care about efficiency here, let's say it's because I'm used to it).

Ah no, also the initial idea was to output a video at 25 images per second, so 64 images makes for like 3 seconds and then it loops.

Then the code evolved and we have more than 3 seconds. Okay, well, that was the thinking.

[canvas does not work]

    ./pi -frame 0 -no-stroke -stroke-pi -stroke-dots

Then from those 64 points, we compute a Fourier transform, leading to 64 items (rotation speed, initial phase, magnitude) that each represent a point rotating at a given speed with a given radius (magnitude). We take as much items as we want from these, say the ones with the higher magnitudes and we produce images for t from 0 to 63. Then we loop. (Yes, circles loop.)

Here is what happens with only one circle.

[click to start/stop animation]
[canvas does not work]

    ./pi -stroke-pi -stroke-dots -n 1

Not super interesting.

With two circles we get this.

[click to start/stop animation]
[canvas does not work]

    ./pi -stroke-pi -stroke-dots -n 2

It's starting to look funny.

Three circles.

[click to start/stop animation]
[canvas does not work]

    ./pi -stroke-pi -stroke-dots -n 3

A bit more funny, but we are far from the target shape.

Now, here is what we get with seven circles. We're almost there, but not yet, I didn't like it. It's a matter of aesthetics, so some people may like it... To me it was not satisfying, that's why I took eight circles.

[click to start/stop animation]
[canvas does not work]

    ./pi -stroke-pi -stroke-dots -n 7

And here is with 64 circles, the max. As you see it's not perfect (we don't match the pi shape perfectly) but we pass through all the 64 chosen dots, which is what the discrete Fourier transform gives you.

[click to start/stop animation]
[canvas does not work]

    ./pi -stroke-pi -stroke-dots -n 64

Let me show you without the circles.

[click to start/stop animation]
[canvas does not work]

    ./pi -no-stroke -stroke-pi -stroke-dots -stroke-path -n 64

And now only the generated path.

[click to start/stop animation]
[canvas does not work]

    ./pi -no-stroke -stroke-path -n 64

We can order the circles as we want because we add them all and addition is commutative. For example here we start with the smallest and finish with the biggest.

Very shaky.

[click to start/stop animation]
[canvas does not work]

    ./pi -reverse

Here comes a version where you can play with parameters.

This one doesn't have an equivalent C command line. It is illegal to conclude that javascript is better than C based solely on the previous sentence.

[click to start/stop animation]
[canvas does not work]

And that's it. My little tribute to the number pi, which, together with e and i, is one of the most important creations of the human global rational process.

One question I have is: what is the error of the generated path with respect to the original simple pi shape? It's some kind of integral, the surface between the two curves. Can we have an analytical form of this error? I guess we can. But I'm super lazy (and super dumb with maths to be honest) so I won't dig into this. (Wait, for two circles for example the path passes over itself, how do you deal with that?)

And then, obviously, this other question pops up immediately: is the discrete Fourier transform the best approximation possible? If we stick with our 64 rotating circles, we then have an optimization problem: find the best items (rotation speed, initial phase, magnitude) (rotation speed is fixed when using the discrete Fourier transform, we may want to keep this constraint) that minimize the error defined proviously. We could use whatever technique to find the best approximation. And some more magic to prove that the discrete Fourier transform is the best (or not). But this is too much for me. Hey! it's just a few lines of code to draw π, that's all!

Related work

I saw some funky drawings using those rotating circles a few years ago on some website I can't remember. That's probably (this probability is severely close to one) what gave me the idea for all of this. Looking for this website using some reasonable keywords on the popular index of the internet of the day, I reached myfourierepicycles.com, which has a similar look (but I doubt it was the website I first saw, it was many many years ago, as far as I remember, the past tends to blur into itself with the time passing, and this website looks too young).

This tweet also looks like what I first saw.

They use two sets of rotating circles in there, which actually was what I first had in mind. One set controls the x part and the other one controls the y and the intersection defines the path.

But thinking a bit more leads to some weirdness. If you do a discrete Fourier transform of a real function (instead of a complex function), and to decompose the path into its x and y components creates two real functions, then you should obtain a discrete Fourier transform where negative and positive frequencies are symmetrical so that when you reconstruct the path from the circles you obtain an end point (the last point of the last circle) moving on a line, not a point rotating all around as we see in the links I give.

So there is something strange going on there. They don't compute the discrete Fourier transform? They remove some frequencies?

If you look at the result obtained by this guy (look at minute 45) you see that both end points of the two sets of rotating circles indeed move following a line. They don't jump all around.

myfourierepicycles.com links to some source code hosted on the popular free code hosting website of the day (github will die one day, you know that, right?) and reading this, and trying to figure out what is going on in there, I think I slightly understand why the end points are not moving following a straight line but jump all over the 2D plane.

We can read:

       //TODO: Add scale for users to dictate how many epicycles.
        const scale = 0.9; //A number in the interval (0, 1].
        const minAmplitude = 0.01;
        const maxAmplitude = 120;

        fourierX = dft(pFIVE, x).filter(
          (f) => f.amp > minAmplitude && f.amp < maxAmplitude
        fourierY = dft(pFIVE, y).filter(
          (f) => f.amp > minAmplitude && f.amp < maxAmplitude

        //The cycles are being drawn in order of frequency. e.g. C0, C1, ... Where C1 has a frequency. Recall frequency is k and we are calulating Ck.

        fourierX = fourierX.slice(0, Math.floor(scale * fourierX.length));
        fourierY = fourierY.slice(0, Math.floor(scale * fourierY.length));

        fourierX.sort((a, b) => b.amp - a.amp);
        fourierY.sort((a, b) => b.amp - a.amp);

The function dft() seems to compute, well, the discrete Fourier transform (except that the computation looks a bit weird to me, look by yourself, the phi should be negative, but that's not a big deal, and the im -= xxxx thing is weird, but let's say in the end it works, does not really matter if you compute the right frequency or the opposite). Then there is some filtering and slicing done, which will remove some frequencies! For the filtering, since both positive and negative frequencies are the opposite for a real function they both should disappear and we're still good, but the slicing removes the end of the array, which should mean low negative frequencies (provided the dft() function does what it should).

That may explain why we obtain end dots moving hectically all around.

Or not.

If I was less lazy I would experiment this x/y decomposition and see what I get. But it's gone too far already. It's a small tribute, and it's about pi, not the discrete Fourier transform!

Anyway, that was the inspiration. There is pi in there and rotating cirles, no? Yes.

Here come some more links, just in case I want to look deeper at it at some point in the future (my lazy brain just woke up and is screaming super loudly right now; you don't want to hear what it says).

The links were valid 2022-03-22. They may have gone or the code may have changed at the time you access them. The internet is super wild, but you know that already.

Contact: see this

Created: 2022-03-22
Last update (more or less accurate): 2022-03-22