The problem of Apollonius

Which circles touch three given circles? Apollonius of Perga posed the problem, and in general it has eight solutions, one for each choice of which of the three given circles the new circle encloses. For three circles that touch each other, two of the solutions are given by Descartes’ theorem on their curvatures \(k = 1/r\),

\[k_4 = k_1 + k_2 + k_3 \pm 2\sqrt{k_1 k_2 + k_2 k_3 + k_3 k_1},\]

where a negative curvature stands for a circle that encloses the others.

In a planex2d sketch, a circle with three tangency constraints is a problem of Apollonius, and the solver finds the solution near the drawn circle.

The eight circles that touch three given circles

[1]:
import math

import numpy as np
import planex as px

A circle tangent to three

touching draws the given circles, fixed with their radii, and a free circle near start, then makes it tangent to each given circle. A tangency between two circles is a contact from outside or from inside, and the sketch takes the kind that the circle is nearest to when the constraint is added:

[3]:
def touching(given, start, r):
    sk = px.Sketch()
    for k, (center, radius) in enumerate(given):
        sk.Circle(center, radius, id=f"given_{k}")
        sk.Constrain(f"given_{k}.center", px.Fix)
        sk.Constrain(f"given_{k}", px.Radius, radius)
    sk.Circle(start, r, id="touching")
    for k in range(len(given)):
        sk.Constrain("touching", f"given_{k}", px.Tangent)
    m = sk.Measure("touching")
    return sk, np.array(m["center"][:2]), m["radius"]

Descartes’ circles

Three circles of radii 1, 2 and 3 touch each other when their centers lie 3, 4 and 5 apart, at the corners of a right triangle. Descartes’ theorem gives \(k_4 = 11/6 \pm 2\): a small circle of radius \(6/23\) in the gap between them and a large one of radius 6 around them, which a rough circle in the gap and one around all three find (Figure 1):

[4]:
tangent_three = [((0, 0), 1), ((3, 0), 2), ((0, 4), 3)]
k = [1 / r for _, r in tangent_three]
root = 2 * math.sqrt(k[0] * k[1] + k[1] * k[2] + k[2] * k[0])
small, inner, r_inner = touching(tangent_three, (0.8, 0.9), 0.3)
large, outer, r_outer = touching(tangent_three, (1, 1.5), 5.5)
print("inner | radius", r_inner, "| Descartes", 1 / (sum(k) + root), "| center", inner)
print("outer | radius", r_outer, "| Descartes", -1 / (sum(k) - root), "| center", outer)
inner | radius 0.26086956521739124 | Descartes 0.2608695652173913 | center [0.91304348 0.86956522]
outer | radius 5.999999999999999 | Descartes 5.999999999999997 | center [3. 4.]
The inner circle
(a) The inner circle
The outer circle
(b) The outer circle
Figure 1: Descartes' two circles around three that touch

Eight circles

Three separate circles have all eight solutions. Each starts from a rough circle near the one wanted, as a drawing would place it, and the solver takes it to the exact contact with all three (Figure 2):

[6]:
given = [((0, 0), 1), ((6, 0), 1.5), ((2, 5), 0.8)]
rough = {"all outside": ((2.5, 2), 2.5), "the first inside": ((1.5, 1), 3), "the second inside": ((4.5, 1.5), 3.5),
         "the third inside": ((2.5, 3), 3), "the first and second inside": ((3.5, 0), 4),
         "the first and third inside": ((1.5, 2), 3.5), "the second and third inside": ((4.5, 2.5), 4.5),
         "all inside": ((3.5, 1.5), 4.5)}
eight = {}
for name, (start, r) in rough.items():
    eight[name], center, radius = touching(given, start, r)
    gaps = [abs(abs(np.linalg.norm(center - np.array(c)) - radius) - rk) for c, rk in given]
    print(name, "| center", center.round(4), "| radius", round(radius, 4), "| off the contacts by", max(gaps))
all outside | center [2.7016 1.9486] | radius 2.331 | off the contacts by 4.440892098500626e-16
the first inside | center [1.6356 1.1929] | radius 3.0245 | off the contacts by 4.440892098500626e-16
the second inside | center [4.3894 1.3236] | radius 3.5847 | off the contacts by 6.661338147750939e-16
the third inside | center [2.6493 2.9414] | radius 2.9586 | off the contacts by 4.440892098500626e-16
the first and second inside | center [3.2502 0.1053] | radius 4.2519 | off the contacts by 1.1102230246251565e-15
the first and third inside | center [1.3831 2.2375] | radius 3.6305 | off the contacts by 6.661338147750939e-16
the second and third inside | center [4.7372 2.6321] | radius 4.4193 | off the contacts by 8.881784197001252e-16
all inside | center [3.2776 1.4418] | radius 4.5806 | off the contacts by 4.440892098500626e-16
All outside
(a) All outside
The first inside
(b) The first inside
The second inside
(c) The second inside
The third inside
(d) The third inside
The first and second inside
(e) The first and second inside
The first and third inside
(f) The first and third inside
The second and third inside
(g) The second and third inside
All inside
(h) All inside
Figure 2: The eight circles that touch three given circles