Designing a Cup

Problem Description

We would like to design a cup that is shaped like a tapered cylinder. The cup is to be of the following dimensions:

$$height = 100\text{ mm}$$ $$volume = 300\text{ mL}$$

The radii can only have integer milimeters, even if up to $10 \text{ mL}$ of the volume is lost. What should the upper and lower radii be?

It aims to explore the following:

  1. Evaluating a function over a region with itertools.product()
  2. Searching within lists with list.index() for values that satisfy certain conditions.
  3. Root Finding: scipy.optimize.root()

1. Evaluating a function over a region with itertools.product()

This is typical application when evaluating a function of two variables $f(x,y)$ such as the volume of a tapered cylinder:

$$V(r,R)=\frac{\pi l}{3}(R^2+Rr+r^2)$$

See previous posts for volume calculation.

In [30]:
from math import*
import itertools

# Looking for solutions in range(x1,x2)
r=[j for j in range(18,42)] 
R=r
g=itertools.product(r,R) # this is like a nested for-loop

l=100 #mm

rr,RR,VV=[],[],[] #lists: rr for the small radius, RR for the large radius, VV for volume 
vdiff=[]
for k in g:
    r=k[0] #element
    rr.append(r) #list
    R=k[1]
    RR.append(R)
    V=pi*l/3*(R**2+r*R+r**2) ## mm^3
    VV.append(V) 
    vdiff.append(abs(V/1000-300)) #deviation from desired volume
    #print(' {:6.2f}   {:6.2f}   {:6.2f} {:6.2f}'.format(r,R,V/1000,abs(V/1000-300))) #mL 

2. Searching within Lists

Searching with the list.index() method returns the first value that satifies the condition you set. Which is good for things like getting the closest point to the desired volume.

In [25]:
# closest point to desired volume
condition=min(vdiff)
index=vdiff.index(condition)
print('\nThe closest value to 300 mL is:')
print('r={} mm  R={} mm  V={:6.2f} mL'.format(rr[index],RR[index],VV[index]/1000))
The closest value to 300 mL is:
r=22 mm  R=39 mm  V=299.81 mL

Caution: The list.index() Method

Be careful, it only returns the first index that satisfy a value.

So, what if we want multiple values, such as all values within 10 mL from the desired volume?

In [26]:
print('Total datapoints')
print(len(vdiff))

indices=[]
for j in vdiff:
    if j<10: #"condition"
        indices.append(vdiff.index(j)) #get the index that satisfies "condition" in vdiff
        vdiff[vdiff.index(j)]=vdiff[vdiff.index(j)]+1000 #altered the value so i can search for the next index

for j in indices:
    vdiff[j]=vdiff[j]-1000 #restored altered value to what it was

datapoints=[]        
for j in indices:
    datapoint=(j,rr[j],RR[j],VV[j]/1000,vdiff[j]) #get the datapoints at the indices that satisfy "condition"
    datapoints.append(datapoint)

print('Total datapoints satisfying "condition"')
s=set(datapoints)
if (len(s)==len(datapoints)):
    print('{} unique points'.format(len(s)))
else:
    print('check your data!')
    
#for j in datapoints: # to view the data
    #print(j)
Total datapoints
576
Total datapoints satisfying "condition"
45 unique points

Plot 1

In [27]:
import matplotlib.pyplot as plt

# extract datapoints
x=[]
y=[]
for u in datapoints:
    x.append(u[1])
    y.append(u[2])

x=[u[1] for u in datapoints]
y=[u[2] for u in datapoints]

# plot 1

plt.plot(x,y,'o')
plt.grid()
plt.xlabel('r')
plt.ylabel('R')
plt.show()

3. Root Finding: scipy.optimize.root()

What if we want to know the exact solution at $r$?

In [28]:
from scipy.optimize import root

def f(R):
    return pi*l/3*(R**2+R*r+r**2)-300000

y2=[]
for u in datapoints:
    r=u[1]
    sol=root(f,20) #solves the quadratic eq
    y2.append(sol.x[0])  #takes the positive root
    

Plot 2

In [29]:
# plot 2

plt.plot(x,y,'o')
plt.plot(x,y2,'-*r')
plt.grid()
plt.xlabel('r, mm')
plt.ylabel('R, mm')
plt.xticks(range(19,42))
plt.yticks(range(19,42))
plt.show()