Placing n equally-sized circles and one larger circle inside the circumference of larger circle

circleseuclidean-geometrygeometry

I'm writing a computer program that needs to display circles in a particular way, but can't seem to figure out how to arrange them given the information I have available. The problem is as follows: (jump to the bottom to see a diagram)

  • There is one large "green" circle with radius $R_g$.
  • There is one smaller "blue" circle with radius $0 \leq R_b \leq G$.
  • There are $n \in \mathbb{Z}_{\geq 1}$ "red" circles, each with radius $0 \leq R_r \leq R_b$.
  • The blue and red circles must be placed on the inside circumference of the green circle.
  • The blue and red circles must touch each other in a specific way (see diagram).

The problem is, given the following:

  • $R_g$, the radius of the green circle,
  • $R_b$, the radius of the blue circle, and
  • $n$, the number of red circles

Calculate:

  • $R_r$, the radius of the red circles
  • $d$, the distance of the center of each of the red circles from the center of the green circle
  • $\theta_r$, the angle between each of the red circles, and
  • $\theta_b$, the angle between the blue circle and the nearest red circle.

I solved this problem when $R_b=R_r$ (when the radius of the blue circle is exactly the size of the red circles). In that case,

\begin{align*}
R_r &= R_b && \text{(given)}\\
d &= \frac{R_g \cdot \sec(\frac{\pi}{2}-\frac{\pi}{n+1})}{\sec(\frac{\pi}{2}-\frac{\pi}{n+1})+1} && \text{solution in link} \\
&= \frac{R_g}{1+\sin(\frac{\pi}{1+n})} && \text{simplified solution} \\
\theta_b = \theta_r &= \frac{2\pi}{n+1}
\end{align*}

A rather large diagram of this problem is here: diagram of problem with varying circle sizes and numbers

Best Answer

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$: Circles with odd and even n 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$$