Skip to content
scipython.com

P8.X.2Overlapping circles

Consider two circles of radii \(R\) and \(r\) whose centres are separated by a distance \(d\).

The area of overlap between them (assuming \(|R-r| \le d \le R+r\)) is given by the following formula, derived below.

\[A(d; R, r) = \beta R^2 + \alpha r^2 - \frac{1}{2}r^2\sin 2\alpha - \frac{1}{2}R^2\sin 2\beta,\]

where

\[\cos \alpha = \frac{r^2 + d^2 - R^2}{2rd} \quad \mathrm{and} \quad \cos \beta = \frac{R^2 + d^2 - r^2}{2Rd}.\]

Inverting this formula to find \(d\) for a given overlap area \(A=A_0\) cannot be achieved analytically, but a numerical solution can be found as the root of the function \(A(d; R, r) - A_0\).

Write a Python function which takes arguments A, the target overlap area, and R and r the two circle radii and returns d, the distance between the circle centres giving overlap area A. Use, for example, brentq.

Derivation

First note that \(A=0\) if \(d \ge R+r\): the circles do not intersect at all in this case. Also, \(A = \pi (\mathrm{min}(R,r))^2\) if \(d \le |R-r|\): the smaller circle is entirely enclosed in the larger in this case. For the case of partial overlap, \(|R-r| < d < R+r\) the area to find is shaded in the diagram below.

Geometry of ovelapping circles highlighting their area of intersection
Geometry of ovelapping circles highlighting their area of intersection.

The cosine formula gives the angles \(\alpha\) and \(\beta\):

\[\cos \alpha = \frac{r^2 + d^2 - R^2}{2rd} \quad \mathrm{and} \quad \cos \beta = \frac{R^2 + d^2 - r^2}{2Rd}.\]

The required area may be found as the sum of the two circle segments cut off by the chord CD. For the circle centred at A in the diagram above, its segment is the area of the circular sector ACD minus the area of the triangle ACD:

With \(x=\mathrm{AE} = R\cos\beta\) and \(h=\mathrm{CE}=R\sin\beta\), triangle ACD has area \(xh=R^2\cos\beta\sin\beta=\frac{1}{2}R^2\sin2\beta\). The area of the circular sector ACD is simply \(\beta R^2\) with \(\beta\) measured in radians (the entire circle has area \(\pi R^2\) and we want the fraction \(\beta/2\pi\) of it).

Therefore, the shaded circular segment has area \(\beta R^2 - \frac{1}{2}R^2\sin2\beta\). Similarly, the area of the segment of the circle centred at B cut off by chord CD is \(\alpha r^2 - \frac{1}{2}r^2\sin2\alpha\). The total intersection area is therefore

\[A = \alpha r^2 + \beta R^2 - \frac{1}{2}r^2\sin2\alpha - \frac{1}{2}R^2\sin2\beta.\]
Show solution

Here's one solution. Note that if you are using Python 2 you should ensure that d, R and r are passed as floats, perhaps by adding the line

d, R, r = float(d), float(R), float(r)

at the beginning of the intersection_area function definition.

overlappingcircles
import numpy as np
from scipy.optimize import brentq


def intersection_area(d, R, r):
    """Return the area of intersection of two circles.

    The circles have radii R and r, and their centres are separated by d.

    """

    if d <= abs(R - r):
        # One circle is entirely enclosed in the other.
        return np.pi * min(R, r) ** 2
    if d >= r + R:
        # The circles don't overlap at all.
        return 0

    r2, R2, d2 = r**2, R**2, d**2
    alpha = np.arccos((d2 + r2 - R2) / (2 * d * r))
    beta = np.arccos((d2 + R2 - r2) / (2 * d * R))
    return (
        r2 * alpha
        + R2 * beta
        - 0.5 * (r2 * np.sin(2 * alpha) + R2 * np.sin(2 * beta))
    )


def find_d(A, R, r):
    """
    Find the distance between the centres of two circles giving overlap area A.

    """

    # A cannot be larger than the area of the smallest circle!
    if A > np.pi * min(r, R) ** 2:
        raise ValueError(
            "Intersection area can't be larger than the area"
            " of the smallest circle"
        )
    if A == 0:
        # If the circles don't overlap, place them next to each other
        return R + r

    if A < 0:
        raise ValueError("Negative intersection area")

    def f(d, A, R, r):
        return intersection_area(d, R, r) - A

    a, b = abs(R - r), R + r
    d = brentq(f, a, b, args=(A, R, r))
    return d

For example,

In [x]: print(find_d(0.35, 1, 0.4))
        0.8489711717559927