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\),
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.

[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.]
(a) The inner circle
(b) The outer circle
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
(a) All outside
(b) The first inside
(c) The second inside
(d) The third inside
(e) The first and second inside
(f) The first and third inside
(g) The second and third inside
(h) All inside