A simple website hosting a demo on apollonian circles, accessible at https://halbfettkaese.github.io/apollonian-circles/.
Click anywhere on the canvas to toggle interactivity.
The math is based on https://arxiv.org/abs/math/0101066. However, just reading this still does not make it easy to understand how to turn the math written there into useful formulas or how to derive an algorithm from them.
The fundamental equations that we will start with are known as Descartes' theorem.
Defining a circle's bend or curvature in relation to its radius
With this being a quadratic equation, it's easy to become inspired to finally find a use for the quadratic formula and solve for the fourth variable. Indeed, exactly this is what the Wikipedia article suggests, as it gives this formula for finding the bend of the 4th circle, while taking the first three circles as given:
Note how with this, every three mutually tangent circles have two different candidates for a fourth circle that would be tangent to all of the original circles. Furthermore, declaring
Seeing these equations above each other suggests something that might otherwise not be immediately obvious: When you add
This means that all of the pain that comes with applying the quadratic formula wasn't necessary after all. You simply need to start with four mutually tangent circles, declare three of them to be the "original" circles with bends
Note that the particular choice of which of the four starting circles to pick as the outsider to reflect over the other three circles was mostly arbitrary. You could have picked any one of the four circles to reflect over the others, and each would have given a different new circle that is tangent to the other three of the four original circles.
It is only after starting to iterate this process that you need to be slightly careful about which choice to make: For one of the four circles, replacing it with a "new" circle just undoes the previous move that added the exact circle that the current move would be removing, and would get you back to a state that you previously explored. So an algorithm should always remember which circle is the newest within any group of four, and make sure to only make moves that would replace one of the older three with yet another new circle.
As such, an algorithm for generating more circle bends connecting to the previous ones may look like this:
// Initialize queue of four-circle-groups. Each group has the last added circle as the fourth element.
let queue = [ [-3, 5, 8, 8] ];
let result = [...queue[0]];
function add_group(b1, b2, b3, b4) {
if (b4 > 80) return; // Skip over the small circles
result.push(b4);
queue.push([b1, b2, b3, b4]);
}
for (let i = 0; i < 10 && queue.length > 0; i++) {
let [b1, b2, b3, b4] = queue.shift();
add_group(b2, b3, b4, 2*(b2+b3+b4)-b1); // Reflect b1 over b2,b3,b4
add_group(b1, b3, b4, 2*(b1+b3+b4)-b2); // Reflect b2 over b1,b3,b4
add_group(b1, b2, b4, 2*(b1+b2+b4)-b3); // Reflect b3 over b1,b2,b4
// Don't reflect b4 over the others, as that would undo the reflection that just created b4
}
console.log(...result); // -3 5 8 8 45 21 12 77 44 44 53 29 20 77 77 68 56 77 53 32This recreates the numbers found on this Wikipedia image:
As it turned out, all of this is fine and easy, but knowing just the circle bends is useless if we still don't know where the circles should actually be placed. This is where the complex Descartes' theorem comes in. It owes its name to the fact that it is completely analogous to the original Descartes' theorem:
In this,
This makes it clear that getting the previous algorithm to also work for computing circle centers just means adding more dimensions to the respective linear equations. Once
The explanations so far should have made clear how much easier the math is when you start with a group of four mutually tangent circles and use those to generate more such groups of four.
However, this still doesn't answer the question of how you find four of these circles to start with. Finding examples of just three starting circles is easy, for example you could have one big circle of radius
For this, we can again use Descartes' theorem, but this time, we can actually use the results from the quadratic formula on the regular and the complex Descartes' theorem:
However, this naive approach quickly runs into problems: The first equation has 2 different solutions depending on whether you choose the
What saves us here is an equation given relatively close to the start of the paper I referenced earlier, Beyond the Descartes circle theorem:
The way it arrives at this equation is by first substituting
This resulting equation is particularly convenient because we now have a linear equation that can be solved for a unique value of
What stands out here is that we're dividing by
With this, we have found a way to always get the 2 different circles that each are tangent to the three original mutually tangent circles.
Getting the two circles that are tangent to 3 given circles:
- Start with 3 mutually tangent circles. Their properties can be known through intuition or simple algebra/geometry.
- Use the quadratic formula to find the two different bends
$b4$ and$b4'$ of the two different circles that are tangent to the original 3 circles:
$$b_4=b_1+b_2+b_3\pm 2\sqrt{b_1b_2+b_1b_3+b_2b_3}$$
- If the two bends are the same (i.e. the discriminant was 0), use the quadratic formula on the complex Descartes' theorem to find the two different circle centers matching that same bend, where
$u_i=z_ib_i$ , with$z_i$ being the complex number corresponding to the circle center:
$$u_4=u_1+u_2+u_3\pm2\sqrt{u_1u_2+u_1u_3+u_2u_3}$$
- Otherwise, use the solution to the linear equation to find the unique circle centers matching each of the two found bends:
$$S=\frac{1}{2}(b_1+b_2+b_3+b_4)$$
$$u_4=\frac{-\sum_{j=1}^3 (b_j-S)u_j}{b_4-S}$$
Getting the one circle that is tangent to 3 out of 4 given circles:
- Start with 4 mutually tangent circles with bends
$b_1, b_2, b_3, b_4$ and centers$z_1, z_2, z_3, z_4$ , with$u_i:=z_ib_i$ . - A new circle that is tangent to the first three circles is given by
$b_4' = 2(b_1+b_2+b_3)-b_4$ and$u_4'=2(u_1+u_2+u_3)-u_4$ .
Footnotes
-
You may notice that the circle bends are sometimes negative. This simply determines whether other circles should be on a given circle's inside or outside. In general, two touching circles have one inside the other if the signs of their bends are different. For drawing a circle to the screen, you can always draw the circle using the absolute value of its bend.
Straight lines can also occur as part of these calculations, as they are degenerate forms of a circle in the case where there is 0 bend, or in other words, infinitely large circles. Beyond the Descartes circle theorem describes a method to deal with this using circle inversion, which doesn't even seem computationally complex, but my code worked well enough by just hiding the circles when they became too large, as there were already precision issues before the gigantic circles became straight lines. ↩

