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.
where
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.
The cosine formula gives the angles \(\alpha\) and \(\beta\):
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
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.
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