Let $R_g$ be the radius of the green circumcircle, $R_b$ be the radius of the single blue circle, and there be $n \ge 1$ red circles. We wish to solve $\theta_r$, the angle between centers of each pair of consecutive red circles, and $R_r$, the radius of each of the red circles.
The radius of the centers of the red circles must be $R_g - R_b$. If we use a coordinate system where the origin is at the center of the green circle, the center of the blue circle is at $(R_b-R_g, 0)$.
If $n = 1$, then the one red circle has radius $R_r = R_g - R_b$ and is centered at $(R_b, 0)$.
If $n = 2$, then the two red circles have radius $R_r$ centered at $(x, \pm y)$,
$$\left\lbrace\begin{aligned}
R_r &= \displaystyle \frac{4 R_g R_b (R_g - R_b)}{(R_g + R_b)^2 } \\
x &= \displaystyle \frac{R_g ( 3 R_b - R_g )}{R_g + R_b} \\
y &= R_r \\
\theta_r &= 2 \operatorname{atan2}(y, x) = 2 \arctan\left(\frac{4 R_b (R_g - R_b)}{(3 R_b - R_g)(R_b + R_g)}\right)\\
\end{aligned}\right.$$
For $n \ge 3$, we need to solve
$$\cos\left((n-1)\frac{\theta_r}{2}\right) - \frac{2 R_b}{R_g - R_b}\sin\left(\frac{\theta_r}{2}\right) + 1 = 0$$
for $\theta_r$; then $$R_r = \displaystyle R_g \frac{\sin\left(\frac{\theta_r}{2}\right)}{1 + \sin\left(\frac{\theta_r}{2}\right)}$$
I recommend using a binary search in range $$0 \le \theta_r \le \frac{2 \pi}{n}$$as there is only one zero for $$f(\theta_r) = \cos\left((n-1)\frac{\theta_r}{2}\right) - \frac{2 R_b}{R_g - R_b}\sin\left(\frac{\theta_r}{2}\right) + 1$$ If $f(\theta_r) \lt 0$, $\theta_r$ is too large; if $f(\theta_r) \gt 0$, $\theta_r$ is too small.
(The upper limit applies if $R_b = 0$, as then the red circles form a closed ring of circles.)
For neighboring red circles to touch, $$R_r = (R_g - R_r) \sin\left(\frac{\theta_r}{2}\right) \quad \iff \quad R_r = R_g \frac{\sin(\theta_r/2)}{1 + \sin(\theta_r/2)}$$
Here is a verified Python implementation, that returns $R_r$ and $\theta_r$ as a tuple:
from math import pi, atan2, sin, cos
def find_r_theta(rG, rB, n):
n = round(n)
if n < 1:
raise ValueError("N must be at least 1, %s given" % n)
if n == 1:
return (rG - rB, 0)
if n == 2:
rR = 4*rG*rB*(rG-rB)/(rG+rB)**2
return (rR, 2*atan2(rR, rG*(3*rB-rG)/(rG+rB)))
theta_max = 2*pi/n
theta_min = 0
ct = 0.5*(n-1)
cs = 2.0*rB / (rG - rB)
# 53 bits of precision
for k in range(0, 53):
theta = 0.5*(theta_min + theta_max)
d = cos(ct*theta) - cs*sin(0.5*theta) + 1
if d > 0.0:
theta_min = theta
elif d < 0.0:
theta_max = theta
else:
break
s = sin(0.5*theta)
rR = rG*s/(s+1)
# Abort if the blue radius is smaller than the red
if rB < rR:
raise ValueError("Blue radius (%f) smaller than the red radius (%d)" % (rB, rR))
return (rG*s/(s+1), theta)
If you add the following code, you can run it via python3 this.py rG rB N out.svg
, specifying the green circle radius, blue circle radius, and the number of red circles, and it'll create an SVG image, out.svg
, for illustration and verification; you can view those in any browser.
if __name__ == '__main__':
from sys import argv, stdout, stderr, exit
from math import ceil
if len(argv) < 4 or len(argv) > 5:
stderr.write("\n")
stderr.write("Usage: %s [ -h | --help ]\n" % argv[0])
stderr.write(" %s GREEN-RADIUS BLUE-RADIUS RED-CIRCLE-COUNT [ OUT.SVG ]\n" % argv[0])
stderr.write("\n")
exit(1)
rG = float(argv[1])
rB = float(argv[2])
n = round(float(argv[3]))
if len(argv) > 4:
svg = open(argv[4], mode="w", encoding="UTF-8")
else:
svg = stdout
rR, theta = find_r_theta(rG, rB, n)
center = int(ceil(rG+2))
svg.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n')
svg.write('<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewbox="0 0 %d %d">\n' % (2*center, 2*center))
svg.write('<rect x="0" y="0" width="%d" height="%d" fill="#ffffff" stroke="none" />\n' % (2*center, 2*center))
svg.write('<circle cx="%.3f" cy="%.3f" r="%.3f" fill="none" stroke="#00cc00" />\n' % (center, center, rG))
svg.write('<circle cx="%.3f" cy="%.3f" r="%.3f" fill="none" stroke="#0000ff" />\n' % (center-rG+rB, center, rB))
if (n & 1):
svg.write('<circle cx="%.3f" cy="%.3f" r="%.3f" fill="none" stroke="#ff0000" />\n' % (center+rG-rR, center, rR))
for i in range(1, int((n+1)/2)):
a = i * theta
x = (rG - rR) * cos(a)
y = (rG - rR) * sin(a)
svg.write('<circle cx="%.3f" cy="%.3f" r="%.3f" fill="none" stroke="#ff0000" />\n' % (center+x, center+y, rR))
svg.write('<circle cx="%.3f" cy="%.3f" r="%.3f" fill="none" stroke="#ff0000" />\n' % (center+x, center-y, rR))
else:
for i in range(0, int(n/2)):
a = (i + 0.5) * theta
x = (rG - rR) * cos(a)
y = (rG - rR) * sin(a)
svg.write('<circle cx="%.3f" cy="%.3f" r="%.3f" fill="none" stroke="#ff0000" />\n' % (center+x, center+y, rR))
svg.write('<circle cx="%.3f" cy="%.3f" r="%.3f" fill="none" stroke="#ff0000" />\n' % (center+x, center-y, rR))
svg.write('</svg>\n')
svg.flush()
if svg != stdout:
svg.close()
How did I find the solution for $n \ge 3$?
Consider the following illustration: The green circle is centered at origin, with radius $R_g$:
The blue circle has radius $R_b$ and is centered at $(R_b - R_g, 0)$. Note that its center $x$ coordinate is actually $c = -R_g + R_b$: blue radius in from the leftmost point on the green circle.
Angle $\theta$ forms an isosceles triangle, with sides $R_g - R_r$ and base $2 R_r$. If we split it into two right triangles, where the hypotenuse length is $R_g - R_r$, short side length is $R_r$, and the angle opposite the short side $\theta/2$. Thus,
$$\sin\left(\frac{\theta}{2}\right) = \frac{R_r}{R_g - R_r} \tag{1a}\label{G1a}$$
Solving this for $R_r$ yields
$$R_r = R_g \frac{\sin\left(\frac{\theta}{2}\right)}{1 + \sin\left(\frac{\theta}{2}\right)} \tag{1b}\label{G1b}$$
This means that the red circle radius $R_r$ is defined by $\theta$, and $\theta$ is our only free variable. And only positive $\theta$ make any sense.
If there was no blue circle at all, then the $n$ red circles would cover the full circle: $n \theta = 2 \pi$. This gives us the possible range for $\theta$,
$$0 \lt \theta \lt \frac{2 \pi}{n} \tag{2}\label{G2}$$
The topmost red circle is always at angle
$$\theta_T = \frac{(n - 1) \theta}{2}$$ counterclockwise from right. Its center is at $(x, y)$,
$$\left\lbrace \begin{aligned}
x = (R_g - R_r) \cos \theta_T &= (R_g - R_r) \cos\left(\frac{(n - 1) \theta}{2}\right) \\
y = (R_g - R_r) \sin \theta_T &= (R_g - R_r) \sin\left(\frac{(n - 1) \theta}{2}\right) \\
\end{aligned} \right. \tag{3a}\label{G3a}$$
For the topmost red circle and the blue circle to touch, we need the distance between their centers to match the sum of their radiuses. Squaring the distances, we have
$$(x - b)^2 + y^2 = (R_b + R_r)^2 \tag{3b}\label{G3b}$$
Next, we subtract the right side from the left side (so we get a function of form $f(\theta)$ whose root we need to find), substitute $x$, $y$, $b$, and $R_r$.
At this point, the expression starts to sprawl, and I for one switch to a Computer Algebra System. I suggest wxMaxima or SageMath; both free and available for all operating systems. In Maxima:
declare(R_g,real, R_b,real, R_r,real, theta,real, n,integer) $
R_r : R_g * sin(theta/2) / (1 + sin(theta/2)) $
x : (R_g - R_r) * cos((n-1)*theta/2) $
y : (R_g - R_r) * sin((n-1)*theta/2) $
b : -R_g + R_b $
EQ : (x-b)^2 + y^2 - (R_b + R_r)^2 = 0;
and after applying rational and trigonometric simplifications (trigsimp(ratsimp(EQ));
), we get
(((2*R_g^2-2*R_b*R_g)*sin(theta/2)+2*R_g^2-2*R_b*R_g)*cos((n-1)*theta/2)
+(2*R_g^2-6*R_b*R_g)*sin(theta/2)
+4*R_b*R_g*cos(theta/2)^2+2*R_g^2-6*R_b*R_g) / (2*sin(theta/2) - cos(theta/2)^2 + 2) = 0
That divisor, $2\sin(\theta/2) - \cos(\theta/2)^2 + 2 \ge 1$ in this context, because we only do this for $n \ge 3$ and therefore $0 \lt \theta/2 \lt \pi/3$, per $\eqref{G2}$. So, we can just omit it, by multiplying the equation with it. There is also a common factor $R_g$, which we can divide out at the same time: trigsimp(ratsimp(% * (2*sin(theta/2) - cos(theta/2)^2 + 2) / R_g));
and we get
((2*R_g-2*R_b)*sin(theta/2)+2*R_g-2*R_b)*cos((n-1)*theta/2)
+ (2*R_g-6*R_b)*sin(theta/2)
+ 4*R_b*cos(theta/2)^2
+ 2*R_g-6*R_b = 0
Replacing $\cos(\theta)^2$ with $1 - \sin(\theta)^2$, we have
((2*R_g-2*R_b)*sin(theta/2)+2*R_g-2*R_b)*cos((n-1)*theta/2)
+ (2*R_g-6*R_b)*sin(theta/2)
+ 4*R_b*(1 - sin(theta/2)^2)
+ 2*R_g-6*R_b = 0;
and finally, if we ask Maxima to solve this for $\theta$, solve(%, theta);
, it gives us two solutions:
[ theta = -%pi, cos((n-1)*theta/2) = -(2*R_b*sin(theta/2)-R_g+R_b)/(R_b-R_g) ]
The first one is garbage, so we grab the second one, cos((n-1)*theta/2) = -(2*R_b*sin(theta/2)-R_g+R_b)/(R_b-R_g);
, and subtract right hand side from left hand side to get a nice function form: ratsimp(lhs(%) - rhs(%)) = 0;
, so we get
((R_g-R_b)*cos((n-1)*theta/2) - 2*R_b*sin(theta/2) + R_g-R_b)/(R_g-R_b) = 0
i.e.
$$\frac{(R_g - R_b) \cos\left(\frac{(n-1) \theta}{2}\right) - 2 R_b \sin\left(\frac{\theta}{2}\right) + R_g - R_b }{R_g - R_b} = 0$$
or, equivalently
$$\cos\left(\frac{(n-1)\theta}{2}\right) - \frac{2 R_b}{R_g - R_b} \sin\left(\frac{\theta}{2}\right) + 1 = 0$$
Best Answer
Draw perpendiculars on the sides of the triangle at the points where the circles touch. Those three lines intersect at a single point P due to symmetry.
Consider the three circles as if they form a single object, and rotate that object around that point P. Clearly every circle can move sideways relative to its touching side because the tangents are all perpendicular to the radial lines from P. As the rotation continues the circles even move away from the sides and no longer touch them.