Suppose we are given a list of positive numbers whose sum is finite. The list might consist of only finite many numbers or they might be the terms of a convergent infinite series. When and how can we inscribe a polygon (potentially, with infinitely many sides) whose side lengths are exactly those numbers?
Let's start with finitely many sides. I guess the question is then, how and when can we inscribe a polygon with given side lengths $\{x_1,x_2,\ldots,x_n\}$ into a circle? If we can, what radius $r$ of the circle will work?
Clearly, the largest $x_k$ must be smaller than the sum of the remaining $x_ks$, for otherwise we can't even make a polygon with the required side lengths. Furthermore, the largest $x_k$ must be smaller than $2r$ to fit that side alone into the circle.
Here's another useful observation and notation: Applying the law of cosines to a chord of length $x$ that subtends an angle of measure $\theta$ in a circle of radius $r$ we get $$x^2 = 2r^2-2r^2\cos(\theta).$$
Solving for $\theta$, we get $$\theta = \arccos\left(\frac{2r^2-x^2}{2r^2}\right).$$ We say that the length $x$ subtends the angle $\theta$ in a circle of radius $r$.
In this case, the sides of the polygon must subtend a total angle of measure $2\pi$. This yields an equation that the side lengths $x_1,x_2,\ldots,x_n$ and the radius $r$ must satisfy: $$\sum_k \arccos\left(\frac{2r^2-x_k^2}{2r^2}\right) = 2\pi.$$ As the $x_ks$ are specified, we can find the value of $r$ that works, if there is one.
In code, we're looking for a root of the function s(r)
below. Note that s
is generally decreasing, so if s(max(side_lengths)/2)
is negative, there's no point in trying.
import numpy as np
side_lengths = [6,3,3,2,3,4,3]
def x(k): return side_lengths[k]
def theta(r,k): return np.arccos((2*r**2 - x(k)**2)/(2*r**2))
def s(r): return sum([theta(r,k) for k in range(len(side_lengths))]) - 2*np.pi
s(max(side_lengths)/2)
Let's make a simple plot of $s$ to find an interval to pass to numerical rootfinder brentq
.
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
x_pts = []
y_pts = []
for r in np.linspace(max(side_lengths)/2,max(side_lengths),20):
try:
y = s(r)
y = float(y)
x_pts.append(r)
y_pts.append(y)
except:
pass
plt.plot(x_pts,y_pts)
Looks like there's a root between 3.5 and 6.
from scipy.optimize import brentq
r_crit = brentq(s,3.5,6)
r_crit
Let's build the circle!
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Circle
r = r_crit
th = 0
xs = [0]
ys = [-r]
for k in range(len(side_lengths)):
th = th + theta(r,k)
ys.append(-r*np.cos(th))
xs.append(r*np.sin(th))
plt.plot(xs,ys, linewidth=1.5, color='black')
plt.plot(xs,ys, 'o', color='black')
ax = plt.gca()
ax.add_patch(Circle((0,0),r, fill=None, alpha=0.5))
ax.set_aspect(1)
ax.set_xlim((-1.1*r,1.1*r))
ax.set_ylim((-1.1*r,1.1*r))
fig = plt.gcf()
fig.set_figwidth(8)
fig.set_figheight(8)
plt.show()
This case relies on similar ideas, but the total angle subtended need not be $2\pi$. However, the angle subtended by the greatest side length must be the same as the angle subtended by the remaining sidelengths, as you'll see in the picture we generate soon.
Note that the code below assumes that the largest number occurs first in the list of side lengths.
import numpy as np
side_lengths = [8,3,3,2,1]
def x(k): return side_lengths[k]
def theta(r,k): return np.arccos((2*r**2 - x(k)**2)/(2*r**2))
def s(r): return sum([theta(r,k) for k in range(1,len(side_lengths))]) - theta(r,0)
s(max(side_lengths)/2)
Again, a plot should help us locate a root.
x_pts = []
y_pts = []
for r in np.linspace(max(side_lengths)/2,2*max(side_lengths),20):
try:
y = s(r)
y = float(y)
x_pts.append(r)
y_pts.append(y)
except:
pass
plt.plot(x_pts,y_pts)
plt.plot([x_pts[0],x_pts[len(x_pts)-1]],[0,0])
Looks like it's around 5 - certainly in $[4.5,10]$.
from scipy.optimize import brentq
r_crit = brentq(s,4.5,10)
r_crit
Finally, we enjoy an illustration.
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Circle
r = r_crit
th = 0
xs = [0]
ys = [-r]
for k in range(len(side_lengths)):
if k == 0:
th = th + theta(r,k)
else:
th = th - theta(r,k)
ys.append(-r*np.cos(th))
xs.append(r*np.sin(th))
plt.plot(xs,ys, linewidth=1.5, color='black')
plt.plot(xs,ys, 'o', color='black')
ax = plt.gca()
ax.add_patch(Circle((0,0),r, fill=None, alpha=0.5))
ax.set_aspect(1)
ax.set_xlim((-1.1*r,1.1*r))
ax.set_ylim((-1.1*r,1.1*r))
fig = plt.gcf()
fig.set_figwidth(8)
fig.set_figheight(8)
plt.show()
The ideas above are largely applicable to polygons with infinitely many sides. The main computational complication is that need a way to approximate an infinite sum, as well as a numerical root finder. There is exactly such a tool in sympy's mpmath package.
Let's try it with the series $$\sum_{n=1}^{\infty} \left(\frac{3}{4}\right)^n.$$
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import sympy.mpmath as mp
def x(k): return 0.75**k
def theta(r,k): return mp.acos((2*r**2 - x(k)**2)/(2*r**2))
def s(r): return mp.sumap(lambda k: theta(r,k), [1,mp.inf]) - 2*mp.pi
x_pts = []
y_pts = []
for r in np.linspace(0,2,20):
try:
y = s(r)
y = float(y)
x_pts.append(r)
y_pts.append(y)
except:
pass
plt.plot(x_pts,y_pts)
Looks like there's a root around $0.5$ - let's find it!
from scipy.optimize import brentq
r_crit = brentq(s,0.4,1)
r_crit
Again, we'll plot the result.
from matplotlib.patches import Circle
r = r_crit
xs = [0]
ys = [-r]
th = 0
for k in range(1,50):
th1 = th
th = th + theta(r,k)
ys.append(-r*mp.cos(th))
xs.append(r*mp.sin(th))
plt.plot(xs,ys, linewidth=1.5, color='black')
plt.plot(xs,ys, 'o', color='black')
ax = plt.gca()
ax.add_patch(Circle((0,0),r, fill=None, alpha=0.5))
ax.set_aspect(1)
ax.set_xlim((-1.1*r,1.1*r))
ax.set_ylim((-1.1*r,1.1*r))
ax.axis('off');
fig = plt.gcf()
fig.set_frameon(False)
fig.set_figwidth(8)
fig.set_figheight(8)