From Scott Ogawa comes a riddle about rodents of usual size:
Quarantined in your apartment, you decide to entertain yourself by building a large pen for your pet hamster. To create the pen, you have several vertical posts, around which you will wrap a sheet of fabric. The sheet is 1 meter long — meaning the perimeter of your pen can be at most 1 meter — and weighs 1 kilogram, while each post weighs k kilograms.
Ads by Teads Over the course of a typical day, your hamster gets bored and likes to change rooms in your apartment. That means you want your pen to be lightweight and easy to move between rooms. The total weight of the posts and the fabric you use should not exceed 1 kilogram.
For example, if k = 0.2, then you could make an equilateral triangle with a perimeter of 0.4 meters (since 0.4 meters of the sheet would weigh 0.4 kilograms), or you could make a square with perimeter of 0.2 meters. However, you couldn’t make a pentagon, since the weight of five posts would already hit the maximum and leave no room for the sheet.
You want to figure out the best shape in order to enclose the largest area possible. What’s the greatest value of k for which you should use four posts rather than three?
Extra credit: For which values of k should you use five posts, six posts, seven posts, and so on?
Note that as the post weight $k$ approaches $0$, the perimeter of the hamster pen approaches $1$ meter and the polygon approaches a circle. The upper limit for the area is therefore :
$$A = \pi r^2$$$$ = \pi \cdot \left(\frac{1}{2\pi}\right)^2$$$$ = \frac{1}{4\pi} ≈ 0.079577m^2$$As $k$ increases, the number of sides and area decrease.
from IPython.display import Image
Image(filename = 'Triangle.png')
The area of a regular polygon is:
$$A = \frac{apothem\cdot Perimeter}{2}$$The side length in meters is the same as the side weight in kilograms. Given that the total weight is $1$ kg and each of the $n$ posts weigh $k$ kg, the length of a side is:
$$s = \frac{1-kn}{n}$$
The apothem can be found from one of the $n$ triangles in the regular polygon :
$$\tan\frac{180}{n} = \frac{\frac{s}{2}}{a}$$
$$a =\frac{\frac{1-nk}{n}}{2\tan\frac{180}{n}}$$
Substituting yields the following:
$$A=\frac{1}{2} \cdot \frac{\frac{1-nk}{n}}{2\tan\frac{180}{n}} \cdot n \cdot \frac{1-nk}{n}$$
$$ = \frac{(nk-1)^2}{4n\tan\frac{180}{n}}$$
There is a weight $w_n$ for each $n$ such that the area of a regular $n$-gon and regular $(n+1)$-gon will have the same area. This yields :
$$\frac{(w_n n-1)^2}{4n\tan\frac{180}{n}} = \frac{\big(w_n (n+1)-1 \big)^2}{4(n+1)\tan\frac{180}{n+1}}$$Substituting $\tan\frac{180}{n}=t_n$ and $\tan\frac{180}{n+1} = t_{n+1}$ yields:
$$\frac{{w_n}^2n^2 - 2w_n n + 1}{4nt_n} = \frac{{w_n}^2(n+1)^2 - 2w_n (n+1) + 1}{4(n+1)t_{n+1}}$$
$${w_n}^2n^2(n+1)t_{n+1} - 2w_n n (n+1)t_{n+1} +(n+1)t_{n+1} =
{w_n}^2n(n+1)^2t_n - 2w_n n (n+1)t_n + nt_n$$
$${w_n}^2 \Big[ n(n+1) \big( nt_{n+1}-(n+1)t_n \big) \Big] +
{w_n} \big[ 2n(n+1)(-t_{n+1} + t_n) \big] +
\big[ (n+1)t_{n+1} - nt_n \big]= 0$$
The discriminant, $b^2-4ac$, yields :
$$\big[ 2n(n+1)(-t_{n+1} + t_n) \big]^2 - 4\Big[ n(n+1) \big( nt_{n+1}-(n+1)t_n \big) \Big]\big[ (n+1)t_{n+1} - nt_n \big]$$$$ = 4n^2 (n+1)^2 (-t_{n+1} + t_n)^2 - 4n(n+1) \Big[ \big((n^2+n)t_{n+1}^2-(2n^2+2n+1)t_{n+1}t_n +(n^2+n)t_{n}^2 \big) \Big]$$
$$ = 4n(n+1) \Big[ \big((n^2+n)t_{n+1}^2-2(n^2+n)t_{n+1}t_n +(n^2+n)t_{n}^2 \big) -
\big((n^2+n)t_{n+1}^2-(2n^2+2n+1)t_{n+1}t_n +(n^2+n)t_{n}^2 \big) \Big]$$
$$ = 4n(n+1)t_{n+1}t_n$$
Completing the quadratic formula :
$$w_n = \frac{2n(n+1)(t_{n+1}-t_n) + \sqrt{4n(n+1)t_{n+1}t_n}} {2n(n+1)(nt_{n+1}-(n+1)t_n)}$$
import math
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
import pandas as pd
#Calculates the minimum weight of a post for side n before incrementing n.
#See steps above.
def get_min_weight(n) :
t_1 = math.tan(math.pi / n)
t_2 = math.tan(math.pi / (n+1))
a = n * (n + 1) * (n * t_2 - (n+1) * t_1)
b = -2 * n * (n + 1) * (t_2 - t_1)
c = ((n+1) * t_2 - n * t_1)
d = b ** 2 - 4 * a * c
root = (-1 * b + math.sqrt(d)) / (2 * a)
return(root)
#Increases n by 1 if weight drops below minimum weight.
def increment_n(n, post_weight, min_weight) :
if post_weight <= min_weight :
return(n + 1)
else :
return(n)
#The weight of the posts change the number of sides non-linearly.
pw_1 = [w/1000.0 for w in range(333, 40, -1)]
pw_2 = [w/10000.0 for w in range(399, 60, -1)]
pw_3 = [w/100000.0 for w in range(599, 80, -1)]
pw_4 = [w/1000000.0 for w in range(799, 43, -1)]
pw_5 = [w/10000000.0 for w in range(429, 59, -1)]
pw_6 = [w/100000000.0 for w in range(589, -1, -1)]
#List of all post weights to be looped.
pw = pw_1 + pw_2 + pw_3 + pw_4 + pw_5 + pw_6
#Initialize a Triangle
n = 3
#Initialize lists.
sides = []
post_weights = []
min_weights = []
lengths = []
areas = []
polygon = []
#For each post weight and number of sides, the following are computed:
#Minimum weight for n before incrementing n.
#Length of each side.
#Angle, apothem, and radius, for calculations.
#Area of the polygon.
#Polygon vertices to graph.
for post_weight in pw :
min_weight = get_min_weight(n)
n = increment_n(n, post_weight, min_weight)
#Takes the number of sides and post weight, returns polygon side length so that total weight is 1 kilogram).
length = (1 - n * post_weight) / n
#Takes the number of sides and returns the angle between the apothem and radius.
angle = math.pi / n
#Takes the side length and angle and return the apothem length.
apothem = length / (2 * math.tan(angle))
#Takes the side length and angle and returns the radius length.
radius = length / (2 * math.sin(angle))
#Takes the apothem, number of sides, and side length and returns the area.
area = apothem * n * length / 2
#Create list of vertices to graph polygon.
xval = []
yval = []
for v in range(n) :
x = 100 + 600 * radius * math.cos(2 * angle * v)
xval.append(x)
y = 100 + 600 * radius * math.sin(2 * angle * v)
yval.append(y)
xval.append(xval[0])
yval.append(yval[0])
#Update all lists.
sides.append(n)
post_weights.append(post_weight)
min_weights.append(min_weight)
lengths.append(length)
areas.append(area)
polygon.append([xval, yval])
#Sides and post weights.
sides_df = sorted(list(set(sides)))[0:8]
min_pw = sorted(list(set(min_weights)))[-1:-9:-1]
max_pw = [1/3.0] + min_pw[0:7]
data = {"For n-gon" : sides_df,
"Maximum weight of post (kg)" : max_pw,
"Minimum weight of post (kg)" : min_pw}
df = pd.DataFrame(data).set_index("For n-gon")
print(df)
print()
print(round(min_pw[0], 5), "kg is the maximum weight for each post to use four posts instead of three. Other values are provided in the chart and plot below.")
#Create data dictionary for plot.
sides2 = []
for side in sides :
if side < 11 :
sides2.append(side)
else :
sides2.append(11)
pg_data = {"Side" : sides,
"Side2" : sides2,
"Length" : lengths,
"Weight" : post_weights,
"Area" : areas}
pg_df = pd.DataFrame(pg_data)
fig = plt.figure()
fig.set_figheight(8)
fig.set_figwidth(13)
ax = fig.add_subplot(1,1,1)
scatter = ax.scatter(x = pg_df["Length"],
y = pg_df["Area"],
c = pg_df["Side2"],
cmap = 'viridis')
ax.set_title("Maximum Hamster Pen Area vs Length and Number of Sides", fontsize = 24)
ax.set_xlabel("Length of Side (meters)", fontsize = 18)
ax.set_ylabel("Area of Hamster Pen (square meters)", fontsize = 18)
h, l = scatter.legend_elements()
l = ['3', '4', '5', '6', '7', '8', '9', '10', '11 or more']
legend = ax.legend(h, l, title = "Number of sides")
ax.add_artist(legend);
%%capture
fig = plt.figure(figsize = (13, 8))
ax = fig.add_subplot(111,
xlim = (0, 300),
ylim = (0, 200))
#Remove axes and ticks.
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.spines["bottom"].set_visible(False)
ax.spines["left"].set_visible(False)
ax.set_xticks([])
ax.set_yticks([])
font = {'family' : 'normal',
'size' : 16}
#Five artists: Polygon, side text, post weight text, length text, and area text.
pg, = ax.plot([], [], c = 'g', ls = '-')
s_text = ax.text(180, 190, '', **font)
pw_text = ax.text(180, 180, '', **font)
l_text = ax.text(180, 170, '', **font)
a_text = ax.text(180, 160, '', **font)
def init():
"""Initialize five artists."""
pg.set_data([], [])
s_text.set_text('')
pw_text.set_text('')
l_text.set_text('')
a_text.set_text('')
return pg, s_text, pw_text, l_text, a_text
def animate(i):
"""Update five artists."""
pg.set_data(polygon[i][0], polygon[i][1])
s_text.set_text("Number of Sides : %.0f" % sides[i])
pw_text.set_text("Post Weight : %.7f kg" % post_weights[i])
l_text.set_text("Side Length : %.7f meters" % lengths[i])
a_text.set_text("Pen Area : %.7f square meters" % areas[i])
return pg, s_text, pw_text, l_text, a_text
#Run animation.
anim = animation.FuncAnimation(fig, animate, init_func = init, frames = 2867, interval = 10, blit = True)
#Save animation.
anim.save('hamster_pens.mp4')
#Embed animation.
from IPython.display import Video
Video("hamster_pens.mp4", width = 624)